@memberjunction/ng-conversations 5.36.0 → 5.37.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 (42) hide show
  1. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +62 -2
  2. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
  3. package/dist/lib/components/conversation/conversation-chat-area.component.js +300 -24
  4. package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
  5. package/dist/lib/components/dialogs/rating-dialog.component.d.ts +31 -0
  6. package/dist/lib/components/dialogs/rating-dialog.component.d.ts.map +1 -0
  7. package/dist/lib/components/dialogs/rating-dialog.component.js +290 -0
  8. package/dist/lib/components/dialogs/rating-dialog.component.js.map +1 -0
  9. package/dist/lib/components/mention/mention-editor.component.d.ts +1 -1
  10. package/dist/lib/components/mention/mention-editor.component.d.ts.map +1 -1
  11. package/dist/lib/components/mention/mention-editor.component.js +1 -0
  12. package/dist/lib/components/mention/mention-editor.component.js.map +1 -1
  13. package/dist/lib/components/message/conversation-message-rating.component.d.ts +43 -18
  14. package/dist/lib/components/message/conversation-message-rating.component.d.ts.map +1 -1
  15. package/dist/lib/components/message/conversation-message-rating.component.js +235 -193
  16. package/dist/lib/components/message/conversation-message-rating.component.js.map +1 -1
  17. package/dist/lib/components/message/message-input-box.component.d.ts +1 -1
  18. package/dist/lib/components/message/message-input-box.component.d.ts.map +1 -1
  19. package/dist/lib/components/message/message-input-box.component.js +1 -1
  20. package/dist/lib/components/message/message-input-box.component.js.map +1 -1
  21. package/dist/lib/components/message/message-input.component.d.ts +7 -1
  22. package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
  23. package/dist/lib/components/message/message-input.component.js +28 -3
  24. package/dist/lib/components/message/message-input.component.js.map +1 -1
  25. package/dist/lib/components/message/message-item.component.js +20 -20
  26. package/dist/lib/components/message/message-item.component.js.map +1 -1
  27. package/dist/lib/components/overlay/chat-overlay.component.d.ts +73 -5
  28. package/dist/lib/components/overlay/chat-overlay.component.d.ts.map +1 -1
  29. package/dist/lib/components/overlay/chat-overlay.component.js +202 -37
  30. package/dist/lib/components/overlay/chat-overlay.component.js.map +1 -1
  31. package/dist/lib/conversations.module.d.ts +24 -23
  32. package/dist/lib/conversations.module.d.ts.map +1 -1
  33. package/dist/lib/conversations.module.js +4 -0
  34. package/dist/lib/conversations.module.js.map +1 -1
  35. package/dist/lib/services/data-cache.service.d.ts.map +1 -1
  36. package/dist/lib/services/data-cache.service.js +0 -1
  37. package/dist/lib/services/data-cache.service.js.map +1 -1
  38. package/dist/lib/services/dialog.service.d.ts +24 -0
  39. package/dist/lib/services/dialog.service.d.ts.map +1 -1
  40. package/dist/lib/services/dialog.service.js +45 -0
  41. package/dist/lib/services/dialog.service.js.map +1 -1
  42. package/package.json +22 -22
@@ -1,255 +1,297 @@
1
1
  import { Component, Input } from '@angular/core';
2
2
  import { BaseAngularComponent } from '@memberjunction/ng-base-types';
3
3
  import { RunView } from '@memberjunction/core';
4
- import { UUIDsEqual } from '@memberjunction/global';
4
+ import { UserInfoEngine } from '@memberjunction/core-entities';
5
5
  import * as i0 from "@angular/core";
6
- function ConversationMessageRatingComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
7
- i0.ɵɵelementStart(0, "div", 1)(1, "span", 5);
6
+ import * as i1 from "../../services/dialog.service";
7
+ function ConversationMessageRatingComponent_Conditional_2_Conditional_1_Template(rf, ctx) { if (rf & 1) {
8
+ i0.ɵɵelement(0, "i", 5);
9
+ i0.ɵɵelementStart(1, "span", 6);
8
10
  i0.ɵɵtext(2);
9
11
  i0.ɵɵelementEnd();
10
- i0.ɵɵelementStart(3, "span", 6);
11
- i0.ɵɵtext(4);
12
+ } if (rf & 2) {
13
+ const ctx_r1 = i0.ɵɵnextContext(2);
14
+ i0.ɵɵadvance(2);
15
+ i0.ɵɵtextInterpolate1("My rating: ", ctx_r1.currentUserRating, "/10");
16
+ } }
17
+ function ConversationMessageRatingComponent_Conditional_2_Conditional_2_Template(rf, ctx) { if (rf & 1) {
18
+ i0.ɵɵelement(0, "i", 7);
19
+ i0.ɵɵelementStart(1, "span");
20
+ i0.ɵɵtext(2, "Rate response");
21
+ i0.ɵɵelementEnd();
22
+ } }
23
+ function ConversationMessageRatingComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
24
+ const _r1 = i0.ɵɵgetCurrentView();
25
+ i0.ɵɵelementStart(0, "button", 4);
26
+ i0.ɵɵlistener("click", function ConversationMessageRatingComponent_Conditional_2_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OpenRatingDialog()); });
27
+ i0.ɵɵconditionalCreate(1, ConversationMessageRatingComponent_Conditional_2_Conditional_1_Template, 3, 1)(2, ConversationMessageRatingComponent_Conditional_2_Conditional_2_Template, 3, 0);
12
28
  i0.ɵɵelementEnd();
13
- i0.ɵɵelementStart(5, "span", 7);
14
- i0.ɵɵtext(6);
15
- i0.ɵɵelementEnd()();
16
29
  } if (rf & 2) {
17
- const ctx_r0 = i0.ɵɵnextContext();
18
- i0.ɵɵproperty("title", ctx_r0.getRatingsTooltip());
19
- i0.ɵɵadvance();
20
- i0.ɵɵclassProp("has-votes", ctx_r0.thumbsUpCount > 0);
21
- i0.ɵɵadvance();
22
- i0.ɵɵtextInterpolate1(" \uD83D\uDC4D ", ctx_r0.thumbsUpCount, " ");
30
+ const ctx_r1 = i0.ɵɵnextContext();
31
+ i0.ɵɵclassProp("has-rated", ctx_r1.currentUserRating != null);
32
+ i0.ɵɵproperty("disabled", ctx_r1.isSaving)("title", ctx_r1.currentUserRating != null ? "Edit your rating" : "Rate this response");
23
33
  i0.ɵɵadvance();
24
- i0.ɵɵclassProp("has-votes", ctx_r0.thumbsDownCount > 0);
25
- i0.ɵɵadvance();
26
- i0.ɵɵtextInterpolate1(" \uD83D\uDC4E ", ctx_r0.thumbsDownCount, " ");
27
- i0.ɵɵadvance(2);
28
- i0.ɵɵtextInterpolate2("(", ctx_r0.totalRatings, " ", ctx_r0.totalRatings === 1 ? "rating" : "ratings", ")");
34
+ i0.ɵɵconditional(ctx_r1.currentUserRating != null ? 1 : 2);
29
35
  } }
36
+ function ConversationMessageRatingComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
37
+ i0.ɵɵelementStart(0, "span", 3);
38
+ i0.ɵɵelement(1, "i", 8);
39
+ i0.ɵɵelementStart(2, "span");
40
+ i0.ɵɵtext(3);
41
+ i0.ɵɵelementEnd()();
42
+ } if (rf & 2) {
43
+ const ctx_r1 = i0.ɵɵnextContext();
44
+ i0.ɵɵadvance(3);
45
+ i0.ɵɵtextInterpolate1("Rated ", ctx_r1.currentUserRating, "/10");
46
+ } }
47
+ const FEEDBACK_CONSENT_KEY = 'agent-feedback-share-consent';
48
+ const CONVERSATIONS_RESOURCE_TYPE_ID = '81D4BC3D-9FEB-EF11-B01A-286B35C04427';
49
+ const FEEDBACK_ROLE_IDS = [
50
+ 'deafccec-6a37-ef11-86d4-000d3a4e707e', // Developer
51
+ 'dfafccec-6a37-ef11-86d4-000d3a4e707e' // Integration
52
+ ];
30
53
  /**
31
- * Component for displaying and managing multi-user ratings on conversation messages.
32
- * Shows aggregate ratings and allows users to provide their own rating.
33
- * Uses optimistic updates UI reflects the change immediately while DB saves in background.
54
+ * Single-rating-per-message rating UI for an AI conversation response.
55
+ *
56
+ * Storage model: the rating + free-form comment live directly on the
57
+ * ConversationDetail row, in `UserRating` (1-10, nullable) and `UserFeedback`
58
+ * (nvarchar(max), nullable). This matches MJ's own pattern on
59
+ * `__mj.ConversationDetail` and Skip's matching columns on
60
+ * `Skip.ConversationDetail`. The host app picks the entity via @Input.
34
61
  */
35
62
  export class ConversationMessageRatingComponent extends BaseAngularComponent {
36
63
  cdr;
64
+ dialogService;
37
65
  conversationDetailId;
38
66
  currentUser;
67
+ /**
68
+ * When false, the rating UI renders as a read-only badge instead of an editable
69
+ * button. Set by the parent (MessageItemComponent) to `false` when the current
70
+ * user is not the conversation owner — the rating storage is a single field on
71
+ * the ConversationDetail row, so only the owner is allowed to mutate it.
72
+ */
73
+ canEdit = true;
74
+ /**
75
+ * Entity name to load/update for the rating. Defaults to MJ's
76
+ * `MJ: Conversation Details` (which has `UserRating`/`UserFeedback`).
77
+ * Skip-Brain hosts pass `'Conversation Details__Skip'`.
78
+ */
79
+ ratingEntityName = 'MJ: Conversation Details';
80
+ /** Column on the entity that stores the 1-10 rating. */
81
+ ratingField = 'UserRating';
82
+ /** Column on the entity that stores the free-form feedback. */
83
+ ratingCommentField = 'UserFeedback';
84
+ /**
85
+ * Optional: legacy pre-loaded ratings array (no longer used by storage; kept
86
+ * to preserve the input shape used by existing call sites like
87
+ * MessageItemComponent).
88
+ */
39
89
  ratingsData;
40
- thumbsUpCount = 0;
41
- thumbsDownCount = 0;
42
- totalRatings = 0;
43
90
  currentUserRating = null;
91
+ currentUserComments = '';
44
92
  isSaving = false;
45
- allRatings = [];
46
- currentUserRatingId = null;
47
- get currentUserId() {
48
- return this.currentUser?.ID || '';
49
- }
50
- constructor(cdr) {
93
+ constructor(cdr, dialogService) {
51
94
  super();
52
95
  this.cdr = cdr;
96
+ this.dialogService = dialogService;
53
97
  }
54
98
  async ngOnInit() {
55
- if (this.ratingsData) {
56
- this.processRatings(this.ratingsData);
99
+ await this.loadRating();
100
+ }
101
+ async OpenRatingDialog() {
102
+ if (this.isSaving)
103
+ return;
104
+ if (!this.canEdit)
105
+ return;
106
+ let consentAcknowledged = false;
107
+ try {
108
+ await UserInfoEngine.Instance.Config();
109
+ consentAcknowledged = UserInfoEngine.Instance.GetSetting(FEEDBACK_CONSENT_KEY) === 'true';
57
110
  }
58
- else {
59
- await this.loadRatings();
111
+ catch (e) {
112
+ console.warn('[Rating] Could not read consent flag, will require acknowledgement', e);
113
+ }
114
+ const result = await this.dialogService.rating({
115
+ title: this.currentUserRating != null ? 'Edit your rating' : 'Rate this response',
116
+ message: 'Rate the response on a 1-10 scale and (optionally) describe what was good or bad.',
117
+ initialRating: this.currentUserRating,
118
+ initialComments: this.currentUserComments,
119
+ okText: 'Submit',
120
+ requireConsent: !consentAcknowledged
121
+ });
122
+ if (result == null)
123
+ return;
124
+ const conversationID = await this.saveRating(result.rating, result.comments);
125
+ if (result.consentNewlyAcknowledged) {
126
+ try {
127
+ const saved = await UserInfoEngine.Instance.SetSetting(FEEDBACK_CONSENT_KEY, 'true');
128
+ if (!saved) {
129
+ console.error(`[Rating] Failed to persist consent acknowledgement under key "${FEEDBACK_CONSENT_KEY}" — ` +
130
+ `the dialog will re-prompt on next rating. Likely cause: the current user lacks ` +
131
+ `create/update permission on the "MJ: User Settings" entity.`);
132
+ }
133
+ }
134
+ catch (e) {
135
+ console.error('[Rating] Could not persist consent flag', e);
136
+ }
137
+ }
138
+ if (conversationID) {
139
+ await this.grantConversationAccess(conversationID);
60
140
  }
61
141
  }
62
- processRatings(ratings) {
63
- this.allRatings = ratings;
64
- this.thumbsUpCount = ratings.filter(r => r.Rating != null && r.Rating >= 8).length;
65
- this.thumbsDownCount = ratings.filter(r => r.Rating != null && r.Rating <= 3).length;
66
- this.totalRatings = ratings.length;
67
- const mine = ratings.find(r => UUIDsEqual(r.UserID, this.currentUserId));
68
- this.currentUserRating = mine?.Rating ?? null;
69
- this.currentUserRatingId = mine ? mine.ID ?? null : null;
70
- }
71
- getRatingsTooltip() {
72
- if (this.allRatings.length === 0)
73
- return '';
74
- const upUsers = this.allRatings
75
- .filter(r => r.Rating != null && r.Rating >= 8)
76
- .map(r => r.UserName || 'Unknown')
77
- .join(', ');
78
- const downUsers = this.allRatings
79
- .filter(r => r.Rating != null && r.Rating <= 3)
80
- .map(r => r.UserName || 'Unknown')
81
- .join(', ');
82
- const parts = [];
83
- if (upUsers)
84
- parts.push(`👍 ${upUsers}`);
85
- if (downUsers)
86
- parts.push(`👎 ${downUsers}`);
87
- return parts.join('\n');
88
- }
89
- async RateThumbsUp() {
90
- await this.saveRating(10);
91
- }
92
- async RateThumbsDown() {
93
- await this.saveRating(1);
94
- }
95
- async loadRatings() {
142
+ /**
143
+ * Grants the Integrations and Developer roles View access to the parent
144
+ * Conversation via `MJ: Resource Permissions`. Idempotent existing
145
+ * permissions for the same role are not duplicated. Best-effort; any error
146
+ * is logged but does not roll back the rating itself.
147
+ */
148
+ async grantConversationAccess(conversationID) {
96
149
  try {
97
- const rv = RunView.FromMetadataProvider(this.ProviderToUse);
98
- const result = await rv.RunView({
99
- EntityName: 'MJ: Conversation Detail Ratings',
100
- ExtraFilter: `ConversationDetailID='${this.conversationDetailId}'`,
101
- ResultType: 'entity_object'
150
+ const md = this.ProviderToUse;
151
+ const rv = new RunView();
152
+ const existing = await rv.RunView({
153
+ EntityName: 'MJ: Resource Permissions',
154
+ ExtraFilter: `ResourceTypeID='${CONVERSATIONS_RESOURCE_TYPE_ID}' AND ` +
155
+ `ResourceRecordID='${conversationID}' AND Type='Role' AND ` +
156
+ `RoleID IN ('${FEEDBACK_ROLE_IDS.join("','")}')`
102
157
  });
103
- if (result.Success && result.Results) {
104
- this.processRatings(result.Results);
158
+ const granted = new Set((existing.Results || []).map((r) => (r.RoleID || '').toLowerCase()));
159
+ for (const roleID of FEEDBACK_ROLE_IDS) {
160
+ if (granted.has(roleID.toLowerCase()))
161
+ continue;
162
+ const perm = await md.GetEntityObject('MJ: Resource Permissions');
163
+ perm.NewRecord();
164
+ perm.Set('ResourceTypeID', CONVERSATIONS_RESOURCE_TYPE_ID);
165
+ perm.Set('ResourceRecordID', conversationID);
166
+ perm.Set('Type', 'Role');
167
+ perm.Set('RoleID', roleID);
168
+ perm.Set('PermissionLevel', 'View');
169
+ perm.Set('Status', 'Approved');
170
+ const ok = await perm.Save();
171
+ if (!ok) {
172
+ console.warn('[Rating] Failed to create ResourcePermission for role', roleID, perm.LatestResult);
173
+ }
174
+ }
175
+ }
176
+ catch (e) {
177
+ console.warn('[Rating] grantConversationAccess failed:', e);
178
+ }
179
+ }
180
+ async loadRating() {
181
+ if (!this.conversationDetailId)
182
+ return;
183
+ try {
184
+ const md = this.ProviderToUse;
185
+ const entity = (await md.GetEntityObject(this.ratingEntityName));
186
+ const loaded = await entity.Load(this.conversationDetailId);
187
+ if (loaded) {
188
+ const rating = entity.Get(this.ratingField);
189
+ this.currentUserRating = rating != null ? Number(rating) : null;
190
+ this.currentUserComments = (entity.Get(this.ratingCommentField) ?? '');
105
191
  this.cdr.detectChanges();
106
192
  }
107
193
  }
108
194
  catch (error) {
109
- console.error('Failed to load ratings:', error);
195
+ // Silently swallow — message may not be persisted yet, or entity lookup failed.
196
+ console.warn('[ConversationMessageRating] Could not load existing rating:', error);
110
197
  }
111
198
  }
112
- applyOptimisticUpdate(rating) {
113
- const wasThumbsUp = this.currentUserRating != null && this.currentUserRating >= 8;
114
- const wasThumbsDown = this.currentUserRating != null && this.currentUserRating <= 3;
115
- // Adjust counts when switching from an existing rating
116
- if (wasThumbsUp)
117
- this.thumbsUpCount--;
118
- if (wasThumbsDown)
119
- this.thumbsDownCount--;
120
- if (!wasThumbsUp && !wasThumbsDown)
121
- this.totalRatings++;
122
- this.currentUserRating = rating;
123
- if (rating >= 8)
124
- this.thumbsUpCount++;
125
- else
126
- this.thumbsDownCount++;
127
- }
128
- async saveRating(rating) {
199
+ async saveRating(rating, comments) {
129
200
  if (this.isSaving)
130
- return;
131
- // Ignore if user clicks the same rating they already have
132
- if (this.currentUserRating === rating)
133
- return;
134
- // Snapshot for rollback
201
+ return null;
202
+ if (rating < 1 || rating > 10)
203
+ return null;
204
+ if (!this.conversationDetailId)
205
+ return null;
135
206
  const prevRating = this.currentUserRating;
136
- const prevRatingId = this.currentUserRatingId;
137
- const prevThumbsUp = this.thumbsUpCount;
138
- const prevThumbsDown = this.thumbsDownCount;
139
- const prevTotal = this.totalRatings;
140
- // Optimistic update — UI reflects change instantly
207
+ const prevComments = this.currentUserComments;
141
208
  this.isSaving = true;
142
- this.applyOptimisticUpdate(rating);
209
+ this.currentUserRating = rating;
210
+ this.currentUserComments = comments;
143
211
  this.cdr.detectChanges();
144
212
  try {
145
- await this.persistRating(rating, prevRatingId);
213
+ const md = this.ProviderToUse;
214
+ const entity = (await md.GetEntityObject(this.ratingEntityName));
215
+ const loaded = await entity.Load(this.conversationDetailId);
216
+ if (!loaded) {
217
+ throw new Error(`ConversationDetail ${this.conversationDetailId} not found in ${this.ratingEntityName}`);
218
+ }
219
+ entity.Set(this.ratingField, rating);
220
+ entity.Set(this.ratingCommentField, comments || null);
221
+ const saveResult = await entity.Save();
222
+ if (!saveResult) {
223
+ throw new Error(`Save failed: ${entity.LatestResult?.Message ?? 'unknown'}`);
224
+ }
225
+ const conversationID = entity.Get('ConversationID');
226
+ return typeof conversationID === 'string' ? conversationID : null;
146
227
  }
147
228
  catch (error) {
148
- // Roll back to previous state on failure
149
- console.error('Failed to save rating:', error);
229
+ console.error('[Rating] save failed:', error);
150
230
  this.currentUserRating = prevRating;
151
- this.currentUserRatingId = prevRatingId;
152
- this.thumbsUpCount = prevThumbsUp;
153
- this.thumbsDownCount = prevThumbsDown;
154
- this.totalRatings = prevTotal;
231
+ this.currentUserComments = prevComments;
155
232
  this.cdr.detectChanges();
233
+ return null;
156
234
  }
157
235
  finally {
158
236
  this.isSaving = false;
159
237
  this.cdr.detectChanges();
160
238
  }
161
239
  }
162
- async persistRating(rating, prevRatingId) {
163
- if (prevRatingId) {
164
- // Update existing rating (switch thumbs up ↔ down)
165
- const md = this.ProviderToUse;
166
- const entity = await md.GetEntityObject('MJ: Conversation Detail Ratings');
167
- const loaded = await entity.Load(prevRatingId);
168
- if (loaded) {
169
- entity.Rating = rating;
170
- await entity.Save();
171
- }
172
- }
173
- else {
174
- // Create new rating
175
- const md = this.ProviderToUse;
176
- const entity = await md.GetEntityObject('MJ: Conversation Detail Ratings');
177
- entity.ConversationDetailID = this.conversationDetailId;
178
- entity.UserID = this.currentUserId;
179
- entity.Rating = rating;
180
- await entity.Save();
181
- this.currentUserRatingId = entity.ID;
182
- }
183
- }
184
- static ɵfac = function ConversationMessageRatingComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ConversationMessageRatingComponent)(i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
185
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ConversationMessageRatingComponent, selectors: [["mj-conversation-message-rating"]], inputs: { conversationDetailId: "conversationDetailId", currentUser: "currentUser", ratingsData: "ratingsData" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 7, vars: 9, consts: [[1, "rating-container"], [1, "aggregate-rating", 3, "title"], [1, "user-rating"], ["title", "This was helpful", "type", "button", 1, "rating-button", "thumbs-up-btn", 3, "click", "disabled"], ["title", "This was not helpful", "type", "button", 1, "rating-button", "thumbs-down-btn", 3, "click", "disabled"], [1, "thumbs-up"], [1, "thumbs-down"], [1, "total-count"]], template: function ConversationMessageRatingComponent_Template(rf, ctx) { if (rf & 1) {
186
- i0.ɵɵelementStart(0, "div", 0);
187
- i0.ɵɵconditionalCreate(1, ConversationMessageRatingComponent_Conditional_1_Template, 7, 9, "div", 1);
188
- i0.ɵɵelementStart(2, "div", 2)(3, "button", 3);
189
- i0.ɵɵlistener("click", function ConversationMessageRatingComponent_Template_button_click_3_listener() { return ctx.RateThumbsUp(); });
190
- i0.ɵɵtext(4, " \uD83D\uDC4D ");
191
- i0.ɵɵelementEnd();
192
- i0.ɵɵelementStart(5, "button", 4);
193
- i0.ɵɵlistener("click", function ConversationMessageRatingComponent_Template_button_click_5_listener() { return ctx.RateThumbsDown(); });
194
- i0.ɵɵtext(6, " \uD83D\uDC4E ");
195
- i0.ɵɵelementEnd()()();
240
+ static ɵfac = function ConversationMessageRatingComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ConversationMessageRatingComponent)(i0.ɵɵdirectiveInject(i0.ChangeDetectorRef), i0.ɵɵdirectiveInject(i1.DialogService)); };
241
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ConversationMessageRatingComponent, selectors: [["mj-conversation-message-rating"]], inputs: { conversationDetailId: "conversationDetailId", currentUser: "currentUser", canEdit: "canEdit", ratingEntityName: "ratingEntityName", ratingField: "ratingField", ratingCommentField: "ratingCommentField", ratingsData: "ratingsData" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 4, vars: 1, consts: [[1, "rating-container"], [1, "user-rating"], ["type", "button", 1, "rating-button", 3, "has-rated", "disabled", "title"], ["title", "Rating left by the conversation owner \u2014 read-only", 1, "rating-readonly"], ["type", "button", 1, "rating-button", 3, "click", "disabled", "title"], [1, "fa-solid", "fa-pen-to-square"], [1, "my-rating"], [1, "fa-regular", "fa-comment-dots"], [1, "fa-solid", "fa-comment-dots"]], template: function ConversationMessageRatingComponent_Template(rf, ctx) { if (rf & 1) {
242
+ i0.ɵɵelementStart(0, "div", 0)(1, "div", 1);
243
+ i0.ɵɵconditionalCreate(2, ConversationMessageRatingComponent_Conditional_2_Template, 3, 5, "button", 2)(3, ConversationMessageRatingComponent_Conditional_3_Template, 4, 1, "span", 3);
244
+ i0.ɵɵelementEnd()();
196
245
  } if (rf & 2) {
197
- i0.ɵɵadvance();
198
- i0.ɵɵconditional(ctx.totalRatings > 0 ? 1 : -1);
199
- i0.ɵɵadvance();
200
- i0.ɵɵclassProp("has-rated", ctx.currentUserRating != null);
201
- i0.ɵɵadvance();
202
- i0.ɵɵclassProp("active", ctx.currentUserRating != null && ctx.currentUserRating >= 8);
203
- i0.ɵɵproperty("disabled", ctx.isSaving);
204
246
  i0.ɵɵadvance(2);
205
- i0.ɵɵclassProp("active", ctx.currentUserRating != null && ctx.currentUserRating <= 3);
206
- i0.ɵɵproperty("disabled", ctx.isSaving);
207
- } }, styles: [".rating-container[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 4px 0;\n }\n\n .aggregate-rating[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: var(--mj-text-muted);\n }\n\n .thumbs-up[_ngcontent-%COMP%], .thumbs-down[_ngcontent-%COMP%] {\n opacity: 0.5;\n }\n\n .thumbs-up.has-votes[_ngcontent-%COMP%], .thumbs-down.has-votes[_ngcontent-%COMP%] {\n opacity: 1;\n }\n\n .total-count[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-text-disabled);\n }\n\n .user-rating[_ngcontent-%COMP%] {\n display: flex;\n gap: 4px;\n margin-left: auto;\n }\n\n .rating-button[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong);\n border-radius: 6px;\n padding: 6px 10px;\n cursor: pointer;\n font-size: 16px;\n opacity: 0.6;\n transition: all 0.2s;\n min-width: 36px;\n }\n\n .rating-button[_ngcontent-%COMP%]:hover:not(:disabled) {\n opacity: 1;\n border-color: var(--mj-text-muted);\n }\n\n .rating-button[_ngcontent-%COMP%]:disabled {\n cursor: not-allowed;\n opacity: 0.4;\n }\n\n .rating-button.active[_ngcontent-%COMP%] {\n opacity: 1;\n }\n\n .thumbs-up-btn[_ngcontent-%COMP%] {\n color: var(--mj-status-success);\n }\n\n .thumbs-up-btn[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-success) 8%, var(--mj-bg-surface));\n }\n\n .thumbs-up-btn.active[_ngcontent-%COMP%] {\n border-color: var(--mj-status-success);\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n }\n\n .thumbs-down-btn[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n }\n\n .thumbs-down-btn[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-error) 8%, var(--mj-bg-surface));\n }\n\n .thumbs-down-btn.active[_ngcontent-%COMP%] {\n border-color: var(--mj-status-error);\n background: color-mix(in srgb, var(--mj-status-error) 15%, var(--mj-bg-surface));\n }"] });
247
+ i0.ɵɵconditional(ctx.canEdit ? 2 : ctx.currentUserRating != null ? 3 : -1);
248
+ } }, styles: [".rating-container[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 4px 0;\n }\n\n .user-rating[_ngcontent-%COMP%] {\n display: flex;\n gap: 4px;\n margin-left: auto;\n }\n\n .rating-button[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong);\n border-radius: 6px;\n padding: 6px 12px;\n cursor: pointer;\n font-size: 13px;\n color: var(--mj-text-primary);\n opacity: 0.85;\n transition: all 0.15s ease;\n }\n\n .rating-button[_ngcontent-%COMP%]:hover:not(:disabled) {\n opacity: 1;\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 6%, var(--mj-bg-surface));\n }\n\n .rating-button[_ngcontent-%COMP%]:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n }\n\n .rating-button.has-rated[_ngcontent-%COMP%] {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n }\n\n .my-rating[_ngcontent-%COMP%] {\n font-weight: 500;\n }\n\n .rating-readonly[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 10px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n font-size: 12px;\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-sunken);\n cursor: default;\n }"] });
208
249
  }
209
250
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ConversationMessageRatingComponent, [{
210
251
  type: Component,
211
252
  args: [{ standalone: false, selector: 'mj-conversation-message-rating', template: `
212
- <div class="rating-container">
213
- @if (totalRatings > 0) {
214
- <div class="aggregate-rating" [title]="getRatingsTooltip()">
215
- <span class="thumbs-up" [class.has-votes]="thumbsUpCount > 0">
216
- 👍 {{ thumbsUpCount }}
217
- </span>
218
- <span class="thumbs-down" [class.has-votes]="thumbsDownCount > 0">
219
- 👎 {{ thumbsDownCount }}
220
- </span>
221
- <span class="total-count">({{ totalRatings }} {{ totalRatings === 1 ? 'rating' : 'ratings' }})</span>
222
- </div>
223
- }
224
-
225
- <div class="user-rating" [class.has-rated]="currentUserRating != null">
226
- <button
227
- class="rating-button thumbs-up-btn"
228
- [class.active]="currentUserRating != null && currentUserRating >= 8"
229
- [disabled]="isSaving"
230
- (click)="RateThumbsUp()"
231
- title="This was helpful"
232
- type="button">
233
- 👍
234
- </button>
235
- <button
236
- class="rating-button thumbs-down-btn"
237
- [class.active]="currentUserRating != null && currentUserRating <= 3"
238
- [disabled]="isSaving"
239
- (click)="RateThumbsDown()"
240
- title="This was not helpful"
241
- type="button">
242
- 👎
243
- </button>
244
- </div>
245
- </div>
246
- `, styles: ["\n .rating-container {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 4px 0;\n }\n\n .aggregate-rating {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: var(--mj-text-muted);\n }\n\n .thumbs-up, .thumbs-down {\n opacity: 0.5;\n }\n\n .thumbs-up.has-votes, .thumbs-down.has-votes {\n opacity: 1;\n }\n\n .total-count {\n font-size: 12px;\n color: var(--mj-text-disabled);\n }\n\n .user-rating {\n display: flex;\n gap: 4px;\n margin-left: auto;\n }\n\n .rating-button {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong);\n border-radius: 6px;\n padding: 6px 10px;\n cursor: pointer;\n font-size: 16px;\n opacity: 0.6;\n transition: all 0.2s;\n min-width: 36px;\n }\n\n .rating-button:hover:not(:disabled) {\n opacity: 1;\n border-color: var(--mj-text-muted);\n }\n\n .rating-button:disabled {\n cursor: not-allowed;\n opacity: 0.4;\n }\n\n .rating-button.active {\n opacity: 1;\n }\n\n .thumbs-up-btn {\n color: var(--mj-status-success);\n }\n\n .thumbs-up-btn:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-success) 8%, var(--mj-bg-surface));\n }\n\n .thumbs-up-btn.active {\n border-color: var(--mj-status-success);\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n }\n\n .thumbs-down-btn {\n color: var(--mj-status-error);\n }\n\n .thumbs-down-btn:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-error) 8%, var(--mj-bg-surface));\n }\n\n .thumbs-down-btn.active {\n border-color: var(--mj-status-error);\n background: color-mix(in srgb, var(--mj-status-error) 15%, var(--mj-bg-surface));\n }\n "] }]
247
- }], () => [{ type: i0.ChangeDetectorRef }], { conversationDetailId: [{
253
+ <div class="rating-container">
254
+ <div class="user-rating">
255
+ @if (canEdit) {
256
+ <button
257
+ class="rating-button"
258
+ [class.has-rated]="currentUserRating != null"
259
+ [disabled]="isSaving"
260
+ (click)="OpenRatingDialog()"
261
+ [title]="currentUserRating != null ? 'Edit your rating' : 'Rate this response'"
262
+ type="button">
263
+ @if (currentUserRating != null) {
264
+ <i class="fa-solid fa-pen-to-square"></i>
265
+ <span class="my-rating">My rating: {{ currentUserRating }}/10</span>
266
+ } @else {
267
+ <i class="fa-regular fa-comment-dots"></i>
268
+ <span>Rate response</span>
269
+ }
270
+ </button>
271
+ } @else if (currentUserRating != null) {
272
+ <span class="rating-readonly"
273
+ title="Rating left by the conversation owner — read-only">
274
+ <i class="fa-solid fa-comment-dots"></i>
275
+ <span>Rated {{ currentUserRating }}/10</span>
276
+ </span>
277
+ }
278
+ </div>
279
+ </div>
280
+ `, styles: ["\n .rating-container {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 4px 0;\n }\n\n .user-rating {\n display: flex;\n gap: 4px;\n margin-left: auto;\n }\n\n .rating-button {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong);\n border-radius: 6px;\n padding: 6px 12px;\n cursor: pointer;\n font-size: 13px;\n color: var(--mj-text-primary);\n opacity: 0.85;\n transition: all 0.15s ease;\n }\n\n .rating-button:hover:not(:disabled) {\n opacity: 1;\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 6%, var(--mj-bg-surface));\n }\n\n .rating-button:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n }\n\n .rating-button.has-rated {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n }\n\n .my-rating {\n font-weight: 500;\n }\n\n .rating-readonly {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 10px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n font-size: 12px;\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-sunken);\n cursor: default;\n }\n "] }]
281
+ }], () => [{ type: i0.ChangeDetectorRef }, { type: i1.DialogService }], { conversationDetailId: [{
248
282
  type: Input
249
283
  }], currentUser: [{
250
284
  type: Input
285
+ }], canEdit: [{
286
+ type: Input
287
+ }], ratingEntityName: [{
288
+ type: Input
289
+ }], ratingField: [{
290
+ type: Input
291
+ }], ratingCommentField: [{
292
+ type: Input
251
293
  }], ratingsData: [{
252
294
  type: Input
253
295
  }] }); })();
254
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ConversationMessageRatingComponent, { className: "ConversationMessageRatingComponent", filePath: "src/lib/components/message/conversation-message-rating.component.ts", lineNumber: 139 }); })();
296
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ConversationMessageRatingComponent, { className: "ConversationMessageRatingComponent", filePath: "src/lib/components/message/conversation-message-rating.component.ts", lineNumber: 126 }); })();
255
297
  //# sourceMappingURL=conversation-message-rating.component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"conversation-message-rating.component.js","sourceRoot":"","sources":["../../../../src/lib/components/message/conversation-message-rating.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, OnInit, ChangeDetectorRef } from '@angular/core';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { Metadata, RunView, UserInfo } from '@memberjunction/core';\nimport { MJConversationDetailRatingEntity, RatingJSON } from '@memberjunction/core-entities';\nimport { UUIDsEqual } from '@memberjunction/global';\n\n/**\n * Component for displaying and managing multi-user ratings on conversation messages.\n * Shows aggregate ratings and allows users to provide their own rating.\n * Uses optimistic updates — UI reflects the change immediately while DB saves in background.\n */\n@Component({\n standalone: false,\n selector: 'mj-conversation-message-rating',\n template: `\n <div class=\"rating-container\">\n @if (totalRatings > 0) {\n <div class=\"aggregate-rating\" [title]=\"getRatingsTooltip()\">\n <span class=\"thumbs-up\" [class.has-votes]=\"thumbsUpCount > 0\">\n 👍 {{ thumbsUpCount }}\n </span>\n <span class=\"thumbs-down\" [class.has-votes]=\"thumbsDownCount > 0\">\n 👎 {{ thumbsDownCount }}\n </span>\n <span class=\"total-count\">({{ totalRatings }} {{ totalRatings === 1 ? 'rating' : 'ratings' }})</span>\n </div>\n }\n\n <div class=\"user-rating\" [class.has-rated]=\"currentUserRating != null\">\n <button\n class=\"rating-button thumbs-up-btn\"\n [class.active]=\"currentUserRating != null && currentUserRating >= 8\"\n [disabled]=\"isSaving\"\n (click)=\"RateThumbsUp()\"\n title=\"This was helpful\"\n type=\"button\">\n 👍\n </button>\n <button\n class=\"rating-button thumbs-down-btn\"\n [class.active]=\"currentUserRating != null && currentUserRating <= 3\"\n [disabled]=\"isSaving\"\n (click)=\"RateThumbsDown()\"\n title=\"This was not helpful\"\n type=\"button\">\n 👎\n </button>\n </div>\n </div>\n `,\n styles: [`\n .rating-container {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 4px 0;\n }\n\n .aggregate-rating {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: var(--mj-text-muted);\n }\n\n .thumbs-up, .thumbs-down {\n opacity: 0.5;\n }\n\n .thumbs-up.has-votes, .thumbs-down.has-votes {\n opacity: 1;\n }\n\n .total-count {\n font-size: 12px;\n color: var(--mj-text-disabled);\n }\n\n .user-rating {\n display: flex;\n gap: 4px;\n margin-left: auto;\n }\n\n .rating-button {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong);\n border-radius: 6px;\n padding: 6px 10px;\n cursor: pointer;\n font-size: 16px;\n opacity: 0.6;\n transition: all 0.2s;\n min-width: 36px;\n }\n\n .rating-button:hover:not(:disabled) {\n opacity: 1;\n border-color: var(--mj-text-muted);\n }\n\n .rating-button:disabled {\n cursor: not-allowed;\n opacity: 0.4;\n }\n\n .rating-button.active {\n opacity: 1;\n }\n\n .thumbs-up-btn {\n color: var(--mj-status-success);\n }\n\n .thumbs-up-btn:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-success) 8%, var(--mj-bg-surface));\n }\n\n .thumbs-up-btn.active {\n border-color: var(--mj-status-success);\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n }\n\n .thumbs-down-btn {\n color: var(--mj-status-error);\n }\n\n .thumbs-down-btn:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-error) 8%, var(--mj-bg-surface));\n }\n\n .thumbs-down-btn.active {\n border-color: var(--mj-status-error);\n background: color-mix(in srgb, var(--mj-status-error) 15%, var(--mj-bg-surface));\n }\n `]\n})\nexport class ConversationMessageRatingComponent extends BaseAngularComponent implements OnInit {\n @Input() conversationDetailId!: string;\n @Input() currentUser!: UserInfo;\n @Input() ratingsData?: RatingJSON[];\n\n thumbsUpCount = 0;\n thumbsDownCount = 0;\n totalRatings = 0;\n currentUserRating: number | null = null;\n isSaving = false;\n\n private allRatings: RatingJSON[] = [];\n private currentUserRatingId: string | null = null;\n\n private get currentUserId(): string {\n return this.currentUser?.ID || '';\n }\n\n constructor(private cdr: ChangeDetectorRef) {\n super();}\n\n async ngOnInit() {\n if (this.ratingsData) {\n this.processRatings(this.ratingsData);\n } else {\n await this.loadRatings();\n }\n }\n\n private processRatings(ratings: RatingJSON[] | MJConversationDetailRatingEntity[]): void {\n this.allRatings = ratings as RatingJSON[];\n this.thumbsUpCount = ratings.filter(r => r.Rating != null && r.Rating >= 8).length;\n this.thumbsDownCount = ratings.filter(r => r.Rating != null && r.Rating <= 3).length;\n this.totalRatings = ratings.length;\n\n const mine = ratings.find(r => UUIDsEqual(r.UserID, this.currentUserId));\n this.currentUserRating = mine?.Rating ?? null;\n this.currentUserRatingId = mine ? (mine as RatingJSON).ID ?? null : null;\n }\n\n getRatingsTooltip(): string {\n if (this.allRatings.length === 0) return '';\n\n const upUsers = this.allRatings\n .filter(r => r.Rating != null && r.Rating >= 8)\n .map(r => (r as RatingJSON).UserName || 'Unknown')\n .join(', ');\n\n const downUsers = this.allRatings\n .filter(r => r.Rating != null && r.Rating <= 3)\n .map(r => (r as RatingJSON).UserName || 'Unknown')\n .join(', ');\n\n const parts: string[] = [];\n if (upUsers) parts.push(`👍 ${upUsers}`);\n if (downUsers) parts.push(`👎 ${downUsers}`);\n return parts.join('\\n');\n }\n\n async RateThumbsUp(): Promise<void> {\n await this.saveRating(10);\n }\n\n async RateThumbsDown(): Promise<void> {\n await this.saveRating(1);\n }\n\n private async loadRatings(): Promise<void> {\n try {\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const result = await rv.RunView<MJConversationDetailRatingEntity>({\n EntityName: 'MJ: Conversation Detail Ratings',\n ExtraFilter: `ConversationDetailID='${this.conversationDetailId}'`,\n ResultType: 'entity_object'\n });\n if (result.Success && result.Results) {\n this.processRatings(result.Results);\n this.cdr.detectChanges();\n }\n } catch (error) {\n console.error('Failed to load ratings:', error);\n }\n }\n\n private applyOptimisticUpdate(rating: number): void {\n const wasThumbsUp = this.currentUserRating != null && this.currentUserRating >= 8;\n const wasThumbsDown = this.currentUserRating != null && this.currentUserRating <= 3;\n\n // Adjust counts when switching from an existing rating\n if (wasThumbsUp) this.thumbsUpCount--;\n if (wasThumbsDown) this.thumbsDownCount--;\n if (!wasThumbsUp && !wasThumbsDown) this.totalRatings++;\n\n this.currentUserRating = rating;\n if (rating >= 8) this.thumbsUpCount++;\n else this.thumbsDownCount++;\n }\n\n private async saveRating(rating: number): Promise<void> {\n if (this.isSaving) return;\n\n // Ignore if user clicks the same rating they already have\n if (this.currentUserRating === rating) return;\n\n // Snapshot for rollback\n const prevRating = this.currentUserRating;\n const prevRatingId = this.currentUserRatingId;\n const prevThumbsUp = this.thumbsUpCount;\n const prevThumbsDown = this.thumbsDownCount;\n const prevTotal = this.totalRatings;\n\n // Optimistic update — UI reflects change instantly\n this.isSaving = true;\n this.applyOptimisticUpdate(rating);\n this.cdr.detectChanges();\n\n try {\n await this.persistRating(rating, prevRatingId);\n } catch (error) {\n // Roll back to previous state on failure\n console.error('Failed to save rating:', error);\n this.currentUserRating = prevRating;\n this.currentUserRatingId = prevRatingId;\n this.thumbsUpCount = prevThumbsUp;\n this.thumbsDownCount = prevThumbsDown;\n this.totalRatings = prevTotal;\n this.cdr.detectChanges();\n } finally {\n this.isSaving = false;\n this.cdr.detectChanges();\n }\n }\n\n private async persistRating(rating: number, prevRatingId: string | null): Promise<void> {\n if (prevRatingId) {\n // Update existing rating (switch thumbs up ↔ down)\n const md = this.ProviderToUse;\n const entity = await md.GetEntityObject<MJConversationDetailRatingEntity>('MJ: Conversation Detail Ratings');\n const loaded = await entity.Load(prevRatingId);\n if (loaded) {\n entity.Rating = rating;\n await entity.Save();\n }\n } else {\n // Create new rating\n const md = this.ProviderToUse;\n const entity = await md.GetEntityObject<MJConversationDetailRatingEntity>('MJ: Conversation Detail Ratings');\n entity.ConversationDetailID = this.conversationDetailId;\n entity.UserID = this.currentUserId;\n entity.Rating = rating;\n await entity.Save();\n this.currentUserRatingId = entity.ID;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"conversation-message-rating.component.js","sourceRoot":"","sources":["../../../../src/lib/components/message/conversation-message-rating.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAA6B,MAAM,eAAe,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAA4B,OAAO,EAAY,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAc,cAAc,EAAE,MAAM,+BAA+B,CAAC;;;;IA0C7D,uBAAyC;IACzC,+BAAwB;IAAA,YAAqC;IAAA,iBAAO;;;IAA5C,eAAqC;IAArC,qEAAqC;;;IAE7D,uBAA0C;IAC1C,4BAAM;IAAA,6BAAa;IAAA,iBAAO;;;;IAZ9B,iCAMgB;IAFd,sMAAS,yBAAkB,KAAC;IAM1B,AAHF,wGAAiC,kFAGxB;IAIX,iBAAS;;;IAZP,6DAA6C;IAG7C,AAFA,0CAAqB,uFAE0D;IAE/E,cAMC;IAND,0DAMC;;;IAGH,+BACgE;IAC9D,uBAAwC;IACxC,4BAAM;IAAA,YAAgC;IACxC,AADwC,iBAAO,EACxC;;;IADC,eAAgC;IAAhC,gEAAgC;;AAlDlD,MAAM,oBAAoB,GAAG,8BAA8B,CAAC;AAC5D,MAAM,8BAA8B,GAAG,sCAAsC,CAAC;AAC9E,MAAM,iBAAiB,GAAG;IACxB,sCAAsC,EAAE,YAAY;IACpD,sCAAsC,CAAE,cAAc;CACvD,CAAC;AAUF;;;;;;;;GAQG;AAgGH,MAAM,OAAO,kCAAmC,SAAQ,oBAAoB;IAqChE;IACA;IArCD,oBAAoB,CAAU;IAC9B,WAAW,CAAY;IAEhC;;;;;OAKG;IACM,OAAO,GAAY,IAAI,CAAC;IAEjC;;;;OAIG;IACM,gBAAgB,GAAW,0BAA0B,CAAC;IAE/D,wDAAwD;IAC/C,WAAW,GAAW,YAAY,CAAC;IAE5C,+DAA+D;IACtD,kBAAkB,GAAW,cAAc,CAAC;IAErD;;;;OAIG;IACM,WAAW,CAAgB;IAEpC,iBAAiB,GAAkB,IAAI,CAAC;IACxC,mBAAmB,GAAW,EAAE,CAAC;IACjC,QAAQ,GAAG,KAAK,CAAC;IAEjB,YACU,GAAsB,EACtB,aAA4B;QAEpC,KAAK,EAAE,CAAC;QAHA,QAAG,GAAH,GAAG,CAAmB;QACtB,kBAAa,GAAb,aAAa,CAAe;IAGtC,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,gBAAgB;QACpB,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAE1B,IAAI,mBAAmB,GAAG,KAAK,CAAC;QAChC,IAAI,CAAC;YACH,MAAM,cAAc,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;YACvC,mBAAmB,GAAG,cAAc,CAAC,QAAQ,CAAC,UAAU,CAAC,oBAAoB,CAAC,KAAK,MAAM,CAAC;QAC5F,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC,oEAAoE,EAAE,CAAC,CAAC,CAAC;QACxF,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC;YAC7C,KAAK,EAAE,IAAI,CAAC,iBAAiB,IAAI,IAAI,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,oBAAoB;YACjF,OAAO,EAAE,mFAAmF;YAC5F,aAAa,EAAE,IAAI,CAAC,iBAAiB;YACrC,eAAe,EAAE,IAAI,CAAC,mBAAmB;YACzC,MAAM,EAAE,QAAQ;YAChB,cAAc,EAAE,CAAC,mBAAmB;SACrC,CAAC,CAAC;QAEH,IAAI,MAAM,IAAI,IAAI;YAAE,OAAO;QAE3B,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;QAE7E,IAAI,MAAM,CAAC,wBAAwB,EAAE,CAAC;YACpC,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,UAAU,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC;gBACrF,IAAI,CAAC,KAAK,EAAE,CAAC;oBACX,OAAO,CAAC,KAAK,CACX,iEAAiE,oBAAoB,MAAM;wBAC3F,iFAAiF;wBACjF,6DAA6D,CAC9D,CAAC;gBACJ,CAAC;YACH,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CAAC,yCAAyC,EAAE,CAAC,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC;QAED,IAAI,cAAc,EAAE,CAAC;YACnB,MAAM,IAAI,CAAC,uBAAuB,CAAC,cAAc,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACK,KAAK,CAAC,uBAAuB,CAAC,cAAsB;QAC1D,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC;YAC9B,MAAM,EAAE,GAAG,IAAI,OAAO,EAAE,CAAC;YACzB,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC;gBAChC,UAAU,EAAE,0BAA0B;gBACtC,WAAW,EACT,mBAAmB,8BAA8B,QAAQ;oBACzD,qBAAqB,cAAc,wBAAwB;oBAC3D,eAAe,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI;aACnD,CAAC,CAAC;YAEH,MAAM,OAAO,GAAG,IAAI,GAAG,CACrB,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CACzE,CAAC;YAEF,KAAK,MAAM,MAAM,IAAI,iBAAiB,EAAE,CAAC;gBACvC,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;oBAAE,SAAS;gBAChD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,eAAe,CAAC,0BAA0B,CAAC,CAAC;gBAClE,IAAI,CAAC,SAAS,EAAE,CAAC;gBACjB,IAAI,CAAC,GAAG,CAAC,gBAAgB,EAAE,8BAA8B,CAAC,CAAC;gBAC3D,IAAI,CAAC,GAAG,CAAC,kBAAkB,EAAE,cAAc,CAAC,CAAC;gBAC7C,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;gBACzB,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;gBAC3B,IAAI,CAAC,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;gBACpC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;gBAC/B,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;gBAC7B,IAAI,CAAC,EAAE,EAAE,CAAC;oBACR,OAAO,CAAC,IAAI,CAAC,uDAAuD,EAAE,MAAM,EAAG,IAAY,CAAC,YAAY,CAAC,CAAC;gBAC5G,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC,0CAA0C,EAAE,CAAC,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,UAAU;QACtB,IAAI,CAAC,IAAI,CAAC,oBAAoB;YAAE,OAAO;QACvC,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC;YAC9B,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAmB,CAAC;YACnF,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;YAC5D,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;gBAC5C,IAAI,CAAC,iBAAiB,GAAG,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;gBAChE,IAAI,CAAC,mBAAmB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAAI,EAAE,CAAW,CAAC;gBACjF,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;YAC3B,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,gFAAgF;YAChF,OAAO,CAAC,IAAI,CAAC,6DAA6D,EAAE,KAAK,CAAC,CAAC;QACrF,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,UAAU,CAAC,MAAc,EAAE,QAAgB;QACvD,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC/B,IAAI,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,EAAE;YAAE,OAAO,IAAI,CAAC;QAC3C,IAAI,CAAC,IAAI,CAAC,oBAAoB;YAAE,OAAO,IAAI,CAAC;QAE5C,MAAM,UAAU,GAAG,IAAI,CAAC,iBAAiB,CAAC;QAC1C,MAAM,YAAY,GAAG,IAAI,CAAC,mBAAmB,CAAC;QAE9C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,iBAAiB,GAAG,MAAM,CAAC;QAChC,IAAI,CAAC,mBAAmB,GAAG,QAAQ,CAAC;QACpC,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;QAEzB,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC;YAC9B,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAmB,CAAC;YACnF,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;YAC5D,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CAAC,sBAAsB,IAAI,CAAC,oBAAoB,iBAAiB,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC;YAC3G,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;YACrC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,kBAAkB,EAAE,QAAQ,IAAI,IAAI,CAAC,CAAC;YACtD,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YACvC,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,MAAM,IAAI,KAAK,CAAC,gBAAiB,MAAc,CAAC,YAAY,EAAE,OAAO,IAAI,SAAS,EAAE,CAAC,CAAC;YACxF,CAAC;YACD,MAAM,cAAc,GAAG,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;YACpD,OAAO,OAAO,cAAc,KAAK,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC;QACpE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,KAAK,CAAC,CAAC;YAC9C,IAAI,CAAC,iBAAiB,GAAG,UAAU,CAAC;YACpC,IAAI,CAAC,mBAAmB,GAAG,YAAY,CAAC;YACxC,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;YACtB,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;QAC3B,CAAC;IACH,CAAC;4HA9LU,kCAAkC;6DAAlC,kCAAkC;YA1FzC,AADF,8BAA8B,aACH;YAiBrB,AAhBF,uGAAe,+EAgByB;YAQ5C,AADE,iBAAM,EACF;;YAxBF,eAsBC;YAtBD,0EAsBC;;;iFAmEI,kCAAkC;cA/F9C,SAAS;6BACI,KAAK,YACP,gCAAgC,YAChC;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BT;;kBAiEA,KAAK;;kBACL,KAAK;;kBAQL,KAAK;;kBAOL,KAAK;;kBAGL,KAAK;;kBAGL,KAAK;;kBAOL,KAAK;;kFA9BK,kCAAkC","sourcesContent":["import { Component, Input, OnInit, ChangeDetectorRef } from '@angular/core';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { BaseEntity, CompositeKey, RunView, UserInfo } from '@memberjunction/core';\nimport { RatingJSON, UserInfoEngine } from '@memberjunction/core-entities';\nimport { DialogService } from '../../services/dialog.service';\n\nconst FEEDBACK_CONSENT_KEY = 'agent-feedback-share-consent';\nconst CONVERSATIONS_RESOURCE_TYPE_ID = '81D4BC3D-9FEB-EF11-B01A-286B35C04427';\nconst FEEDBACK_ROLE_IDS = [\n 'deafccec-6a37-ef11-86d4-000d3a4e707e', // Developer\n 'dfafccec-6a37-ef11-86d4-000d3a4e707e' // Integration\n];\n\n/** Minimal shape we use against the loaded entity. All MJ entity subclasses\n * implement `Load(ID)` via codegen but the base `BaseEntity<unknown>` type\n * doesn't expose it, so we narrow here to keep the call generic across\n * entity names without resorting to `any`. */\ntype LoadableEntity = BaseEntity<unknown> & {\n Load(KeyValuePairs: CompositeKey | string, EntityRelationshipsToLoad?: string[]): Promise<boolean>;\n};\n\n/**\n * Single-rating-per-message rating UI for an AI conversation response.\n *\n * Storage model: the rating + free-form comment live directly on the\n * ConversationDetail row, in `UserRating` (1-10, nullable) and `UserFeedback`\n * (nvarchar(max), nullable). This matches MJ's own pattern on\n * `__mj.ConversationDetail` and Skip's matching columns on\n * `Skip.ConversationDetail`. The host app picks the entity via @Input.\n */\n@Component({\n standalone: false,\n selector: 'mj-conversation-message-rating',\n template: `\n <div class=\"rating-container\">\n <div class=\"user-rating\">\n @if (canEdit) {\n <button\n class=\"rating-button\"\n [class.has-rated]=\"currentUserRating != null\"\n [disabled]=\"isSaving\"\n (click)=\"OpenRatingDialog()\"\n [title]=\"currentUserRating != null ? 'Edit your rating' : 'Rate this response'\"\n type=\"button\">\n @if (currentUserRating != null) {\n <i class=\"fa-solid fa-pen-to-square\"></i>\n <span class=\"my-rating\">My rating: {{ currentUserRating }}/10</span>\n } @else {\n <i class=\"fa-regular fa-comment-dots\"></i>\n <span>Rate response</span>\n }\n </button>\n } @else if (currentUserRating != null) {\n <span class=\"rating-readonly\"\n title=\"Rating left by the conversation owner — read-only\">\n <i class=\"fa-solid fa-comment-dots\"></i>\n <span>Rated {{ currentUserRating }}/10</span>\n </span>\n }\n </div>\n </div>\n `,\n styles: [`\n .rating-container {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 4px 0;\n }\n\n .user-rating {\n display: flex;\n gap: 4px;\n margin-left: auto;\n }\n\n .rating-button {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong);\n border-radius: 6px;\n padding: 6px 12px;\n cursor: pointer;\n font-size: 13px;\n color: var(--mj-text-primary);\n opacity: 0.85;\n transition: all 0.15s ease;\n }\n\n .rating-button:hover:not(:disabled) {\n opacity: 1;\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 6%, var(--mj-bg-surface));\n }\n\n .rating-button:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n }\n\n .rating-button.has-rated {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n }\n\n .my-rating {\n font-weight: 500;\n }\n\n .rating-readonly {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 10px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n font-size: 12px;\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-sunken);\n cursor: default;\n }\n `]\n})\nexport class ConversationMessageRatingComponent extends BaseAngularComponent implements OnInit {\n @Input() conversationDetailId!: string;\n @Input() currentUser!: UserInfo;\n\n /**\n * When false, the rating UI renders as a read-only badge instead of an editable\n * button. Set by the parent (MessageItemComponent) to `false` when the current\n * user is not the conversation owner — the rating storage is a single field on\n * the ConversationDetail row, so only the owner is allowed to mutate it.\n */\n @Input() canEdit: boolean = true;\n\n /**\n * Entity name to load/update for the rating. Defaults to MJ's\n * `MJ: Conversation Details` (which has `UserRating`/`UserFeedback`).\n * Skip-Brain hosts pass `'Conversation Details__Skip'`.\n */\n @Input() ratingEntityName: string = 'MJ: Conversation Details';\n\n /** Column on the entity that stores the 1-10 rating. */\n @Input() ratingField: string = 'UserRating';\n\n /** Column on the entity that stores the free-form feedback. */\n @Input() ratingCommentField: string = 'UserFeedback';\n\n /**\n * Optional: legacy pre-loaded ratings array (no longer used by storage; kept\n * to preserve the input shape used by existing call sites like\n * MessageItemComponent).\n */\n @Input() ratingsData?: RatingJSON[];\n\n currentUserRating: number | null = null;\n currentUserComments: string = '';\n isSaving = false;\n\n constructor(\n private cdr: ChangeDetectorRef,\n private dialogService: DialogService\n ) {\n super();\n }\n\n async ngOnInit() {\n await this.loadRating();\n }\n\n async OpenRatingDialog(): Promise<void> {\n if (this.isSaving) return;\n if (!this.canEdit) return;\n\n let consentAcknowledged = false;\n try {\n await UserInfoEngine.Instance.Config();\n consentAcknowledged = UserInfoEngine.Instance.GetSetting(FEEDBACK_CONSENT_KEY) === 'true';\n } catch (e) {\n console.warn('[Rating] Could not read consent flag, will require acknowledgement', e);\n }\n\n const result = await this.dialogService.rating({\n title: this.currentUserRating != null ? 'Edit your rating' : 'Rate this response',\n message: 'Rate the response on a 1-10 scale and (optionally) describe what was good or bad.',\n initialRating: this.currentUserRating,\n initialComments: this.currentUserComments,\n okText: 'Submit',\n requireConsent: !consentAcknowledged\n });\n\n if (result == null) return;\n\n const conversationID = await this.saveRating(result.rating, result.comments);\n\n if (result.consentNewlyAcknowledged) {\n try {\n const saved = await UserInfoEngine.Instance.SetSetting(FEEDBACK_CONSENT_KEY, 'true');\n if (!saved) {\n console.error(\n `[Rating] Failed to persist consent acknowledgement under key \"${FEEDBACK_CONSENT_KEY}\" — ` +\n `the dialog will re-prompt on next rating. Likely cause: the current user lacks ` +\n `create/update permission on the \"MJ: User Settings\" entity.`\n );\n }\n } catch (e) {\n console.error('[Rating] Could not persist consent flag', e);\n }\n }\n\n if (conversationID) {\n await this.grantConversationAccess(conversationID);\n }\n }\n\n /**\n * Grants the Integrations and Developer roles View access to the parent\n * Conversation via `MJ: Resource Permissions`. Idempotent — existing\n * permissions for the same role are not duplicated. Best-effort; any error\n * is logged but does not roll back the rating itself.\n */\n private async grantConversationAccess(conversationID: string): Promise<void> {\n try {\n const md = this.ProviderToUse;\n const rv = new RunView();\n const existing = await rv.RunView({\n EntityName: 'MJ: Resource Permissions',\n ExtraFilter:\n `ResourceTypeID='${CONVERSATIONS_RESOURCE_TYPE_ID}' AND ` +\n `ResourceRecordID='${conversationID}' AND Type='Role' AND ` +\n `RoleID IN ('${FEEDBACK_ROLE_IDS.join(\"','\")}')`\n });\n\n const granted = new Set<string>(\n (existing.Results || []).map((r: any) => (r.RoleID || '').toLowerCase())\n );\n\n for (const roleID of FEEDBACK_ROLE_IDS) {\n if (granted.has(roleID.toLowerCase())) continue;\n const perm = await md.GetEntityObject('MJ: Resource Permissions');\n perm.NewRecord();\n perm.Set('ResourceTypeID', CONVERSATIONS_RESOURCE_TYPE_ID);\n perm.Set('ResourceRecordID', conversationID);\n perm.Set('Type', 'Role');\n perm.Set('RoleID', roleID);\n perm.Set('PermissionLevel', 'View');\n perm.Set('Status', 'Approved');\n const ok = await perm.Save();\n if (!ok) {\n console.warn('[Rating] Failed to create ResourcePermission for role', roleID, (perm as any).LatestResult);\n }\n }\n } catch (e) {\n console.warn('[Rating] grantConversationAccess failed:', e);\n }\n }\n\n private async loadRating(): Promise<void> {\n if (!this.conversationDetailId) return;\n try {\n const md = this.ProviderToUse;\n const entity = (await md.GetEntityObject(this.ratingEntityName)) as LoadableEntity;\n const loaded = await entity.Load(this.conversationDetailId);\n if (loaded) {\n const rating = entity.Get(this.ratingField);\n this.currentUserRating = rating != null ? Number(rating) : null;\n this.currentUserComments = (entity.Get(this.ratingCommentField) ?? '') as string;\n this.cdr.detectChanges();\n }\n } catch (error) {\n // Silently swallow — message may not be persisted yet, or entity lookup failed.\n console.warn('[ConversationMessageRating] Could not load existing rating:', error);\n }\n }\n\n private async saveRating(rating: number, comments: string): Promise<string | null> {\n if (this.isSaving) return null;\n if (rating < 1 || rating > 10) return null;\n if (!this.conversationDetailId) return null;\n\n const prevRating = this.currentUserRating;\n const prevComments = this.currentUserComments;\n\n this.isSaving = true;\n this.currentUserRating = rating;\n this.currentUserComments = comments;\n this.cdr.detectChanges();\n\n try {\n const md = this.ProviderToUse;\n const entity = (await md.GetEntityObject(this.ratingEntityName)) as LoadableEntity;\n const loaded = await entity.Load(this.conversationDetailId);\n if (!loaded) {\n throw new Error(`ConversationDetail ${this.conversationDetailId} not found in ${this.ratingEntityName}`);\n }\n entity.Set(this.ratingField, rating);\n entity.Set(this.ratingCommentField, comments || null);\n const saveResult = await entity.Save();\n if (!saveResult) {\n throw new Error(`Save failed: ${(entity as any).LatestResult?.Message ?? 'unknown'}`);\n }\n const conversationID = entity.Get('ConversationID');\n return typeof conversationID === 'string' ? conversationID : null;\n } catch (error) {\n console.error('[Rating] save failed:', error);\n this.currentUserRating = prevRating;\n this.currentUserComments = prevComments;\n this.cdr.detectChanges();\n return null;\n } finally {\n this.isSaving = false;\n this.cdr.detectChanges();\n }\n }\n}\n"]}
@@ -114,7 +114,7 @@ export declare class MessageInputBoxComponent {
114
114
  mimeType: string;
115
115
  sizeBytes: number;
116
116
  artifactVersionId?: string;
117
- }): void;
117
+ }): PendingAttachment | undefined;
118
118
  static ɵfac: i0.ɵɵFactoryDeclaration<MessageInputBoxComponent, never>;
119
119
  static ɵcmp: i0.ɵɵComponentDeclaration<MessageInputBoxComponent, "mj-message-input-box", never, { "placeholder": { "alias": "placeholder"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; "value": { "alias": "value"; "required": false; }; "showCharacterCount": { "alias": "showCharacterCount"; "required": false; }; "enableMentions": { "alias": "enableMentions"; "required": false; }; "currentUser": { "alias": "currentUser"; "required": false; }; "rows": { "alias": "rows"; "required": false; }; "enableAttachments": { "alias": "enableAttachments"; "required": false; }; "maxAttachments": { "alias": "maxAttachments"; "required": false; }; "maxAttachmentSizeBytes": { "alias": "maxAttachmentSizeBytes"; "required": false; }; "acceptedFileTypes": { "alias": "acceptedFileTypes"; "required": false; }; }, { "textSubmitted": "textSubmitted"; "valueChange": "valueChange"; "attachmentsChanged": "attachmentsChanged"; "attachmentError": "attachmentError"; "attachmentClicked": "attachmentClicked"; "artifactPickerRequested": "artifactPickerRequested"; }, never, never, false, never>;
120
120
  }
@@ -1 +1 @@
1
- {"version":3,"file":"message-input-box.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/message/message-input-box.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,YAAY,EAAa,MAAM,eAAe,CAAC;AAClF,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,6CAA6C,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;;AAEhG;;;;;;;;;;;;;;GAcG;AACH,qBAMa,wBAAwB;IACP,aAAa,CAAC,EAAE,sBAAsB,CAAC;IAE1D,WAAW,EAAE,MAAM,CAAsD;IACzE,QAAQ,EAAE,OAAO,CAAS;IAC1B,KAAK,EAAE,MAAM,CAAM;IACnB,kBAAkB,EAAE,OAAO,CAAS;IACpC,cAAc,EAAE,OAAO,CAAQ;IAC/B,WAAW,CAAC,EAAE,QAAQ,CAAC;IACvB,IAAI,EAAE,MAAM,CAAK;IAGjB,iBAAiB,EAAE,OAAO,CAAQ;IAClC,cAAc,EAAE,MAAM,CAAM;IAC5B,sBAAsB,EAAE,MAAM,CAAoB;IAClD,iBAAiB,EAAE,MAAM,CAAa;IAErC,aAAa,uBAA8B;IAC3C,WAAW,uBAA8B;IACzC,kBAAkB,oCAA2C;IAC7D,eAAe,uBAA8B;IAC7C,iBAAiB,kCAAyC;IAC1D,uBAAuB,qBAA4B;IAE7D,IAAI,OAAO,IAAI,OAAO,CAIrB;IAED;;OAEG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKrC;;OAEG;IACH,oBAAoB,CAAC,WAAW,EAAE,iBAAiB,EAAE,GAAG,IAAI;IAI5D;;OAEG;IACH,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAItC;;OAEG;IACH,mBAAmB,CAAC,UAAU,EAAE,iBAAiB,GAAG,IAAI;IAIxD;;;OAGG;IACH,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAInC;;OAEG;IACH,iBAAiB,CAAC,UAAU,EAAE,iBAAiB,GAAG,IAAI;IAKtD;;;OAGG;IACH,WAAW,IAAI,IAAI;IAgBnB;;;OAGG;IACH,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IA6BzC;;OAEG;IACH,KAAK,IAAI,IAAI;IAOb;;OAEG;IACH,mBAAmB,IAAI,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAIhH;;OAEG;IACH,qBAAqB,IAAI,iBAAiB,EAAE;IAI5C;;OAEG;IACH,cAAc,IAAI,IAAI;IAItB;;;;;;;;OAQG;IACH,kBAAkB,IAAI,IAAI;IAI1B;;OAEG;IACH,qBAAqB,CAAC,QAAQ,EAAE;QAC9B,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QACnD,SAAS,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;KAC/C,GAAG,IAAI;yCAlLG,wBAAwB;2CAAxB,wBAAwB;CAqLpC"}
1
+ {"version":3,"file":"message-input-box.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/message/message-input-box.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,YAAY,EAAa,MAAM,eAAe,CAAC;AAClF,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,6CAA6C,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;;AAEhG;;;;;;;;;;;;;;GAcG;AACH,qBAMa,wBAAwB;IACP,aAAa,CAAC,EAAE,sBAAsB,CAAC;IAE1D,WAAW,EAAE,MAAM,CAAsD;IACzE,QAAQ,EAAE,OAAO,CAAS;IAC1B,KAAK,EAAE,MAAM,CAAM;IACnB,kBAAkB,EAAE,OAAO,CAAS;IACpC,cAAc,EAAE,OAAO,CAAQ;IAC/B,WAAW,CAAC,EAAE,QAAQ,CAAC;IACvB,IAAI,EAAE,MAAM,CAAK;IAGjB,iBAAiB,EAAE,OAAO,CAAQ;IAClC,cAAc,EAAE,MAAM,CAAM;IAC5B,sBAAsB,EAAE,MAAM,CAAoB;IAClD,iBAAiB,EAAE,MAAM,CAAa;IAErC,aAAa,uBAA8B;IAC3C,WAAW,uBAA8B;IACzC,kBAAkB,oCAA2C;IAC7D,eAAe,uBAA8B;IAC7C,iBAAiB,kCAAyC;IAC1D,uBAAuB,qBAA4B;IAE7D,IAAI,OAAO,IAAI,OAAO,CAIrB;IAED;;OAEG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKrC;;OAEG;IACH,oBAAoB,CAAC,WAAW,EAAE,iBAAiB,EAAE,GAAG,IAAI;IAI5D;;OAEG;IACH,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAItC;;OAEG;IACH,mBAAmB,CAAC,UAAU,EAAE,iBAAiB,GAAG,IAAI;IAIxD;;;OAGG;IACH,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAInC;;OAEG;IACH,iBAAiB,CAAC,UAAU,EAAE,iBAAiB,GAAG,IAAI;IAKtD;;;OAGG;IACH,WAAW,IAAI,IAAI;IAgBnB;;;OAGG;IACH,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IA6BzC;;OAEG;IACH,KAAK,IAAI,IAAI;IAOb;;OAEG;IACH,mBAAmB,IAAI,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAIhH;;OAEG;IACH,qBAAqB,IAAI,iBAAiB,EAAE;IAI5C;;OAEG;IACH,cAAc,IAAI,IAAI;IAItB;;;;;;;;OAQG;IACH,kBAAkB,IAAI,IAAI;IAI1B;;OAEG;IACH,qBAAqB,CAAC,QAAQ,EAAE;QAC9B,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QACnD,SAAS,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;KAC/C,GAAG,iBAAiB,GAAG,SAAS;yCAlLtB,wBAAwB;2CAAxB,wBAAwB;CAqLpC"}
@@ -187,7 +187,7 @@ export class MessageInputBoxComponent {
187
187
  * Add an artifact as a pending attachment (called by parent after artifact selection)
188
188
  */
189
189
  AddArtifactAttachment(artifact) {
190
- this.mentionEditor?.AddArtifactAttachment(artifact);
190
+ return this.mentionEditor?.AddArtifactAttachment(artifact);
191
191
  }
192
192
  static ɵfac = function MessageInputBoxComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MessageInputBoxComponent)(); };
193
193
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: MessageInputBoxComponent, selectors: [["mj-message-input-box"]], viewQuery: function MessageInputBoxComponent_Query(rf, ctx) { if (rf & 1) {