vanilla-agent 1.22.0 → 1.23.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/dist/index.cjs +26 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +258 -63
- package/dist/index.d.ts +258 -63
- package/dist/index.global.js +57 -53
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +26 -22
- package/dist/index.js.map +1 -1
- package/dist/widget.css +224 -0
- package/package.json +2 -2
- package/src/client.ts +180 -5
- package/src/components/feedback.ts +377 -0
- package/src/index.ts +13 -1
- package/src/session.ts +55 -3
- package/src/styles/widget.css +224 -0
- package/src/types.ts +25 -0
- package/src/ui.ts +95 -1
- package/src/utils/message-id.ts +35 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feedback UI components for CSAT and NPS collection
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type CSATFeedbackOptions = {
|
|
6
|
+
/** Callback when user submits CSAT feedback */
|
|
7
|
+
onSubmit: (rating: number, comment?: string) => void | Promise<void>;
|
|
8
|
+
/** Callback when user dismisses the feedback form */
|
|
9
|
+
onDismiss?: () => void;
|
|
10
|
+
/** Title text */
|
|
11
|
+
title?: string;
|
|
12
|
+
/** Subtitle/question text */
|
|
13
|
+
subtitle?: string;
|
|
14
|
+
/** Placeholder for optional comment field */
|
|
15
|
+
commentPlaceholder?: string;
|
|
16
|
+
/** Submit button text */
|
|
17
|
+
submitText?: string;
|
|
18
|
+
/** Skip button text */
|
|
19
|
+
skipText?: string;
|
|
20
|
+
/** Show comment field */
|
|
21
|
+
showComment?: boolean;
|
|
22
|
+
/** Rating labels (5 items for ratings 1-5) */
|
|
23
|
+
ratingLabels?: [string, string, string, string, string];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type NPSFeedbackOptions = {
|
|
27
|
+
/** Callback when user submits NPS feedback */
|
|
28
|
+
onSubmit: (rating: number, comment?: string) => void | Promise<void>;
|
|
29
|
+
/** Callback when user dismisses the feedback form */
|
|
30
|
+
onDismiss?: () => void;
|
|
31
|
+
/** Title text */
|
|
32
|
+
title?: string;
|
|
33
|
+
/** Subtitle/question text */
|
|
34
|
+
subtitle?: string;
|
|
35
|
+
/** Placeholder for optional comment field */
|
|
36
|
+
commentPlaceholder?: string;
|
|
37
|
+
/** Submit button text */
|
|
38
|
+
submitText?: string;
|
|
39
|
+
/** Skip button text */
|
|
40
|
+
skipText?: string;
|
|
41
|
+
/** Show comment field */
|
|
42
|
+
showComment?: boolean;
|
|
43
|
+
/** Low label (left side) */
|
|
44
|
+
lowLabel?: string;
|
|
45
|
+
/** High label (right side) */
|
|
46
|
+
highLabel?: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const defaultCSATLabels: [string, string, string, string, string] = [
|
|
50
|
+
'Very dissatisfied',
|
|
51
|
+
'Dissatisfied',
|
|
52
|
+
'Neutral',
|
|
53
|
+
'Satisfied',
|
|
54
|
+
'Very satisfied'
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Create a CSAT (Customer Satisfaction) feedback form
|
|
59
|
+
* Rating scale: 1-5
|
|
60
|
+
*/
|
|
61
|
+
export function createCSATFeedback(options: CSATFeedbackOptions): HTMLElement {
|
|
62
|
+
const {
|
|
63
|
+
onSubmit,
|
|
64
|
+
onDismiss,
|
|
65
|
+
title = 'How satisfied are you?',
|
|
66
|
+
subtitle = 'Please rate your experience',
|
|
67
|
+
commentPlaceholder = 'Share your thoughts (optional)...',
|
|
68
|
+
submitText = 'Submit',
|
|
69
|
+
skipText = 'Skip',
|
|
70
|
+
showComment = true,
|
|
71
|
+
ratingLabels = defaultCSATLabels,
|
|
72
|
+
} = options;
|
|
73
|
+
|
|
74
|
+
const container = document.createElement('div');
|
|
75
|
+
container.className = 'tvw-feedback-container tvw-feedback-csat';
|
|
76
|
+
container.setAttribute('role', 'dialog');
|
|
77
|
+
container.setAttribute('aria-label', 'Customer satisfaction feedback');
|
|
78
|
+
|
|
79
|
+
let selectedRating: number | null = null;
|
|
80
|
+
|
|
81
|
+
// Create inner content
|
|
82
|
+
const content = document.createElement('div');
|
|
83
|
+
content.className = 'tvw-feedback-content';
|
|
84
|
+
|
|
85
|
+
// Header
|
|
86
|
+
const header = document.createElement('div');
|
|
87
|
+
header.className = 'tvw-feedback-header';
|
|
88
|
+
|
|
89
|
+
const titleEl = document.createElement('h3');
|
|
90
|
+
titleEl.className = 'tvw-feedback-title';
|
|
91
|
+
titleEl.textContent = title;
|
|
92
|
+
header.appendChild(titleEl);
|
|
93
|
+
|
|
94
|
+
const subtitleEl = document.createElement('p');
|
|
95
|
+
subtitleEl.className = 'tvw-feedback-subtitle';
|
|
96
|
+
subtitleEl.textContent = subtitle;
|
|
97
|
+
header.appendChild(subtitleEl);
|
|
98
|
+
|
|
99
|
+
content.appendChild(header);
|
|
100
|
+
|
|
101
|
+
// Rating buttons (1-5 stars or numbers)
|
|
102
|
+
const ratingContainer = document.createElement('div');
|
|
103
|
+
ratingContainer.className = 'tvw-feedback-rating tvw-feedback-rating-csat';
|
|
104
|
+
ratingContainer.setAttribute('role', 'radiogroup');
|
|
105
|
+
ratingContainer.setAttribute('aria-label', 'Satisfaction rating from 1 to 5');
|
|
106
|
+
|
|
107
|
+
const ratingButtons: HTMLButtonElement[] = [];
|
|
108
|
+
|
|
109
|
+
for (let i = 1; i <= 5; i++) {
|
|
110
|
+
const ratingButton = document.createElement('button');
|
|
111
|
+
ratingButton.type = 'button';
|
|
112
|
+
ratingButton.className = 'tvw-feedback-rating-btn tvw-feedback-star-btn';
|
|
113
|
+
ratingButton.setAttribute('role', 'radio');
|
|
114
|
+
ratingButton.setAttribute('aria-checked', 'false');
|
|
115
|
+
ratingButton.setAttribute('aria-label', `${i} star${i > 1 ? 's' : ''}: ${ratingLabels[i - 1]}`);
|
|
116
|
+
ratingButton.title = ratingLabels[i - 1];
|
|
117
|
+
ratingButton.dataset.rating = String(i);
|
|
118
|
+
|
|
119
|
+
// Star icon (filled when selected)
|
|
120
|
+
ratingButton.innerHTML = `
|
|
121
|
+
<svg class="tvw-feedback-star" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
122
|
+
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
|
|
123
|
+
</svg>
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
ratingButton.addEventListener('click', () => {
|
|
127
|
+
selectedRating = i;
|
|
128
|
+
ratingButtons.forEach((btn, index) => {
|
|
129
|
+
const isSelected = index < i;
|
|
130
|
+
btn.classList.toggle('selected', isSelected);
|
|
131
|
+
btn.setAttribute('aria-checked', index === i - 1 ? 'true' : 'false');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
ratingButtons.push(ratingButton);
|
|
136
|
+
ratingContainer.appendChild(ratingButton);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
content.appendChild(ratingContainer);
|
|
140
|
+
|
|
141
|
+
// Comment field
|
|
142
|
+
let commentTextarea: HTMLTextAreaElement | null = null;
|
|
143
|
+
if (showComment) {
|
|
144
|
+
const commentContainer = document.createElement('div');
|
|
145
|
+
commentContainer.className = 'tvw-feedback-comment-container';
|
|
146
|
+
|
|
147
|
+
commentTextarea = document.createElement('textarea');
|
|
148
|
+
commentTextarea.className = 'tvw-feedback-comment';
|
|
149
|
+
commentTextarea.placeholder = commentPlaceholder;
|
|
150
|
+
commentTextarea.rows = 3;
|
|
151
|
+
commentTextarea.setAttribute('aria-label', 'Additional comments');
|
|
152
|
+
|
|
153
|
+
commentContainer.appendChild(commentTextarea);
|
|
154
|
+
content.appendChild(commentContainer);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Action buttons
|
|
158
|
+
const actions = document.createElement('div');
|
|
159
|
+
actions.className = 'tvw-feedback-actions';
|
|
160
|
+
|
|
161
|
+
const skipButton = document.createElement('button');
|
|
162
|
+
skipButton.type = 'button';
|
|
163
|
+
skipButton.className = 'tvw-feedback-btn tvw-feedback-btn-skip';
|
|
164
|
+
skipButton.textContent = skipText;
|
|
165
|
+
skipButton.addEventListener('click', () => {
|
|
166
|
+
onDismiss?.();
|
|
167
|
+
container.remove();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const submitButton = document.createElement('button');
|
|
171
|
+
submitButton.type = 'button';
|
|
172
|
+
submitButton.className = 'tvw-feedback-btn tvw-feedback-btn-submit';
|
|
173
|
+
submitButton.textContent = submitText;
|
|
174
|
+
submitButton.addEventListener('click', async () => {
|
|
175
|
+
if (selectedRating === null) {
|
|
176
|
+
// Shake the rating container to indicate selection required
|
|
177
|
+
ratingContainer.classList.add('tvw-feedback-shake');
|
|
178
|
+
setTimeout(() => ratingContainer.classList.remove('tvw-feedback-shake'), 500);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
submitButton.disabled = true;
|
|
183
|
+
submitButton.textContent = 'Submitting...';
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const comment = commentTextarea?.value.trim() || undefined;
|
|
187
|
+
await onSubmit(selectedRating, comment);
|
|
188
|
+
container.remove();
|
|
189
|
+
} catch (error) {
|
|
190
|
+
submitButton.disabled = false;
|
|
191
|
+
submitButton.textContent = submitText;
|
|
192
|
+
// eslint-disable-next-line no-console
|
|
193
|
+
console.error('[CSAT Feedback] Failed to submit:', error);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
actions.appendChild(skipButton);
|
|
198
|
+
actions.appendChild(submitButton);
|
|
199
|
+
content.appendChild(actions);
|
|
200
|
+
|
|
201
|
+
container.appendChild(content);
|
|
202
|
+
|
|
203
|
+
return container;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Create an NPS (Net Promoter Score) feedback form
|
|
208
|
+
* Rating scale: 0-10
|
|
209
|
+
*/
|
|
210
|
+
export function createNPSFeedback(options: NPSFeedbackOptions): HTMLElement {
|
|
211
|
+
const {
|
|
212
|
+
onSubmit,
|
|
213
|
+
onDismiss,
|
|
214
|
+
title = 'How likely are you to recommend us?',
|
|
215
|
+
subtitle = 'On a scale of 0 to 10',
|
|
216
|
+
commentPlaceholder = 'What could we do better? (optional)...',
|
|
217
|
+
submitText = 'Submit',
|
|
218
|
+
skipText = 'Skip',
|
|
219
|
+
showComment = true,
|
|
220
|
+
lowLabel = 'Not likely',
|
|
221
|
+
highLabel = 'Very likely',
|
|
222
|
+
} = options;
|
|
223
|
+
|
|
224
|
+
const container = document.createElement('div');
|
|
225
|
+
container.className = 'tvw-feedback-container tvw-feedback-nps';
|
|
226
|
+
container.setAttribute('role', 'dialog');
|
|
227
|
+
container.setAttribute('aria-label', 'Net Promoter Score feedback');
|
|
228
|
+
|
|
229
|
+
let selectedRating: number | null = null;
|
|
230
|
+
|
|
231
|
+
// Create inner content
|
|
232
|
+
const content = document.createElement('div');
|
|
233
|
+
content.className = 'tvw-feedback-content';
|
|
234
|
+
|
|
235
|
+
// Header
|
|
236
|
+
const header = document.createElement('div');
|
|
237
|
+
header.className = 'tvw-feedback-header';
|
|
238
|
+
|
|
239
|
+
const titleEl = document.createElement('h3');
|
|
240
|
+
titleEl.className = 'tvw-feedback-title';
|
|
241
|
+
titleEl.textContent = title;
|
|
242
|
+
header.appendChild(titleEl);
|
|
243
|
+
|
|
244
|
+
const subtitleEl = document.createElement('p');
|
|
245
|
+
subtitleEl.className = 'tvw-feedback-subtitle';
|
|
246
|
+
subtitleEl.textContent = subtitle;
|
|
247
|
+
header.appendChild(subtitleEl);
|
|
248
|
+
|
|
249
|
+
content.appendChild(header);
|
|
250
|
+
|
|
251
|
+
// Rating buttons (0-10)
|
|
252
|
+
const ratingContainer = document.createElement('div');
|
|
253
|
+
ratingContainer.className = 'tvw-feedback-rating tvw-feedback-rating-nps';
|
|
254
|
+
ratingContainer.setAttribute('role', 'radiogroup');
|
|
255
|
+
ratingContainer.setAttribute('aria-label', 'Likelihood rating from 0 to 10');
|
|
256
|
+
|
|
257
|
+
// Labels row
|
|
258
|
+
const labelsRow = document.createElement('div');
|
|
259
|
+
labelsRow.className = 'tvw-feedback-labels';
|
|
260
|
+
|
|
261
|
+
const lowLabelEl = document.createElement('span');
|
|
262
|
+
lowLabelEl.className = 'tvw-feedback-label-low';
|
|
263
|
+
lowLabelEl.textContent = lowLabel;
|
|
264
|
+
|
|
265
|
+
const highLabelEl = document.createElement('span');
|
|
266
|
+
highLabelEl.className = 'tvw-feedback-label-high';
|
|
267
|
+
highLabelEl.textContent = highLabel;
|
|
268
|
+
|
|
269
|
+
labelsRow.appendChild(lowLabelEl);
|
|
270
|
+
labelsRow.appendChild(highLabelEl);
|
|
271
|
+
|
|
272
|
+
// Numbers row
|
|
273
|
+
const numbersRow = document.createElement('div');
|
|
274
|
+
numbersRow.className = 'tvw-feedback-numbers';
|
|
275
|
+
|
|
276
|
+
const ratingButtons: HTMLButtonElement[] = [];
|
|
277
|
+
|
|
278
|
+
for (let i = 0; i <= 10; i++) {
|
|
279
|
+
const ratingButton = document.createElement('button');
|
|
280
|
+
ratingButton.type = 'button';
|
|
281
|
+
ratingButton.className = 'tvw-feedback-rating-btn tvw-feedback-number-btn';
|
|
282
|
+
ratingButton.setAttribute('role', 'radio');
|
|
283
|
+
ratingButton.setAttribute('aria-checked', 'false');
|
|
284
|
+
ratingButton.setAttribute('aria-label', `Rating ${i} out of 10`);
|
|
285
|
+
ratingButton.textContent = String(i);
|
|
286
|
+
ratingButton.dataset.rating = String(i);
|
|
287
|
+
|
|
288
|
+
// Color coding: detractors (0-6), passives (7-8), promoters (9-10)
|
|
289
|
+
if (i <= 6) {
|
|
290
|
+
ratingButton.classList.add('tvw-feedback-detractor');
|
|
291
|
+
} else if (i <= 8) {
|
|
292
|
+
ratingButton.classList.add('tvw-feedback-passive');
|
|
293
|
+
} else {
|
|
294
|
+
ratingButton.classList.add('tvw-feedback-promoter');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
ratingButton.addEventListener('click', () => {
|
|
298
|
+
selectedRating = i;
|
|
299
|
+
ratingButtons.forEach((btn, index) => {
|
|
300
|
+
btn.classList.toggle('selected', index === i);
|
|
301
|
+
btn.setAttribute('aria-checked', index === i ? 'true' : 'false');
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
ratingButtons.push(ratingButton);
|
|
306
|
+
numbersRow.appendChild(ratingButton);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
ratingContainer.appendChild(labelsRow);
|
|
310
|
+
ratingContainer.appendChild(numbersRow);
|
|
311
|
+
content.appendChild(ratingContainer);
|
|
312
|
+
|
|
313
|
+
// Comment field
|
|
314
|
+
let commentTextarea: HTMLTextAreaElement | null = null;
|
|
315
|
+
if (showComment) {
|
|
316
|
+
const commentContainer = document.createElement('div');
|
|
317
|
+
commentContainer.className = 'tvw-feedback-comment-container';
|
|
318
|
+
|
|
319
|
+
commentTextarea = document.createElement('textarea');
|
|
320
|
+
commentTextarea.className = 'tvw-feedback-comment';
|
|
321
|
+
commentTextarea.placeholder = commentPlaceholder;
|
|
322
|
+
commentTextarea.rows = 3;
|
|
323
|
+
commentTextarea.setAttribute('aria-label', 'Additional comments');
|
|
324
|
+
|
|
325
|
+
commentContainer.appendChild(commentTextarea);
|
|
326
|
+
content.appendChild(commentContainer);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Action buttons
|
|
330
|
+
const actions = document.createElement('div');
|
|
331
|
+
actions.className = 'tvw-feedback-actions';
|
|
332
|
+
|
|
333
|
+
const skipButton = document.createElement('button');
|
|
334
|
+
skipButton.type = 'button';
|
|
335
|
+
skipButton.className = 'tvw-feedback-btn tvw-feedback-btn-skip';
|
|
336
|
+
skipButton.textContent = skipText;
|
|
337
|
+
skipButton.addEventListener('click', () => {
|
|
338
|
+
onDismiss?.();
|
|
339
|
+
container.remove();
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const submitButton = document.createElement('button');
|
|
343
|
+
submitButton.type = 'button';
|
|
344
|
+
submitButton.className = 'tvw-feedback-btn tvw-feedback-btn-submit';
|
|
345
|
+
submitButton.textContent = submitText;
|
|
346
|
+
submitButton.addEventListener('click', async () => {
|
|
347
|
+
if (selectedRating === null) {
|
|
348
|
+
// Shake the rating container to indicate selection required
|
|
349
|
+
numbersRow.classList.add('tvw-feedback-shake');
|
|
350
|
+
setTimeout(() => numbersRow.classList.remove('tvw-feedback-shake'), 500);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
submitButton.disabled = true;
|
|
355
|
+
submitButton.textContent = 'Submitting...';
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
const comment = commentTextarea?.value.trim() || undefined;
|
|
359
|
+
await onSubmit(selectedRating, comment);
|
|
360
|
+
container.remove();
|
|
361
|
+
} catch (error) {
|
|
362
|
+
submitButton.disabled = false;
|
|
363
|
+
submitButton.textContent = submitText;
|
|
364
|
+
// eslint-disable-next-line no-console
|
|
365
|
+
console.error('[NPS Feedback] Failed to submit:', error);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
actions.appendChild(skipButton);
|
|
370
|
+
actions.appendChild(submitButton);
|
|
371
|
+
content.appendChild(actions);
|
|
372
|
+
|
|
373
|
+
container.appendChild(content);
|
|
374
|
+
|
|
375
|
+
return container;
|
|
376
|
+
}
|
|
377
|
+
|
package/src/index.ts
CHANGED
|
@@ -39,7 +39,9 @@ export type {
|
|
|
39
39
|
// Client token types
|
|
40
40
|
ClientSession,
|
|
41
41
|
ClientInitResponse,
|
|
42
|
-
ClientChatRequest
|
|
42
|
+
ClientChatRequest,
|
|
43
|
+
ClientFeedbackRequest,
|
|
44
|
+
ClientFeedbackType
|
|
43
45
|
} from "./types";
|
|
44
46
|
|
|
45
47
|
export { initAgentWidgetFn as initAgentWidget };
|
|
@@ -74,6 +76,11 @@ export {
|
|
|
74
76
|
createRegexJsonParser,
|
|
75
77
|
createXmlParser
|
|
76
78
|
} from "./utils/formatting";
|
|
79
|
+
export {
|
|
80
|
+
generateMessageId,
|
|
81
|
+
generateUserMessageId,
|
|
82
|
+
generateAssistantMessageId
|
|
83
|
+
} from "./utils/message-id";
|
|
77
84
|
export { generateCodeSnippet } from "./utils/code-generators";
|
|
78
85
|
export type { CodeFormat } from "./utils/code-generators";
|
|
79
86
|
export type { AgentWidgetInitHandle };
|
|
@@ -131,5 +138,10 @@ export {
|
|
|
131
138
|
createMessageActions
|
|
132
139
|
} from "./components/message-bubble";
|
|
133
140
|
export type { MessageTransform, MessageActionCallbacks } from "./components/message-bubble";
|
|
141
|
+
export {
|
|
142
|
+
createCSATFeedback,
|
|
143
|
+
createNPSFeedback
|
|
144
|
+
} from "./components/feedback";
|
|
145
|
+
export type { CSATFeedbackOptions, NPSFeedbackOptions } from "./components/feedback";
|
|
134
146
|
|
|
135
147
|
export default initAgentWidgetFn;
|
package/src/session.ts
CHANGED
|
@@ -5,6 +5,10 @@ import {
|
|
|
5
5
|
AgentWidgetMessage,
|
|
6
6
|
ClientSession
|
|
7
7
|
} from "./types";
|
|
8
|
+
import {
|
|
9
|
+
generateUserMessageId,
|
|
10
|
+
generateAssistantMessageId
|
|
11
|
+
} from "./utils/message-id";
|
|
8
12
|
|
|
9
13
|
export type AgentWidgetSessionStatus =
|
|
10
14
|
| "idle"
|
|
@@ -119,6 +123,49 @@ export class AgentWidgetSession {
|
|
|
119
123
|
this.client.clearClientSession();
|
|
120
124
|
}
|
|
121
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Get the underlying client instance (for advanced use cases like feedback)
|
|
128
|
+
*/
|
|
129
|
+
public getClient(): AgentWidgetClient {
|
|
130
|
+
return this.client;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Submit message feedback (upvote, downvote, or copy) to the API.
|
|
135
|
+
* Only available in client token mode.
|
|
136
|
+
*
|
|
137
|
+
* @param messageId - The ID of the message to provide feedback for
|
|
138
|
+
* @param type - The feedback type: 'upvote', 'downvote', or 'copy'
|
|
139
|
+
*/
|
|
140
|
+
public async submitMessageFeedback(
|
|
141
|
+
messageId: string,
|
|
142
|
+
type: 'upvote' | 'downvote' | 'copy'
|
|
143
|
+
): Promise<void> {
|
|
144
|
+
return this.client.submitMessageFeedback(messageId, type);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Submit CSAT (Customer Satisfaction) feedback to the API.
|
|
149
|
+
* Only available in client token mode.
|
|
150
|
+
*
|
|
151
|
+
* @param rating - Rating from 1 to 5
|
|
152
|
+
* @param comment - Optional comment
|
|
153
|
+
*/
|
|
154
|
+
public async submitCSATFeedback(rating: number, comment?: string): Promise<void> {
|
|
155
|
+
return this.client.submitCSATFeedback(rating, comment);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Submit NPS (Net Promoter Score) feedback to the API.
|
|
160
|
+
* Only available in client token mode.
|
|
161
|
+
*
|
|
162
|
+
* @param rating - Rating from 0 to 10
|
|
163
|
+
* @param comment - Optional comment
|
|
164
|
+
*/
|
|
165
|
+
public async submitNPSFeedback(rating: number, comment?: string): Promise<void> {
|
|
166
|
+
return this.client.submitNPSFeedback(rating, comment);
|
|
167
|
+
}
|
|
168
|
+
|
|
122
169
|
public updateConfig(next: AgentWidgetConfig) {
|
|
123
170
|
this.config = { ...this.config, ...next };
|
|
124
171
|
this.client = new AgentWidgetClient(this.config);
|
|
@@ -146,8 +193,12 @@ export class AgentWidgetSession {
|
|
|
146
193
|
|
|
147
194
|
this.abortController?.abort();
|
|
148
195
|
|
|
196
|
+
// Generate IDs for both user message and expected assistant response
|
|
197
|
+
const userMessageId = generateUserMessageId();
|
|
198
|
+
const assistantMessageId = generateAssistantMessageId();
|
|
199
|
+
|
|
149
200
|
const userMessage: AgentWidgetMessage = {
|
|
150
|
-
id:
|
|
201
|
+
id: userMessageId,
|
|
151
202
|
role: "user",
|
|
152
203
|
content: input,
|
|
153
204
|
createdAt: new Date().toISOString(),
|
|
@@ -167,13 +218,14 @@ export class AgentWidgetSession {
|
|
|
167
218
|
await this.client.dispatch(
|
|
168
219
|
{
|
|
169
220
|
messages: snapshot,
|
|
170
|
-
signal: controller.signal
|
|
221
|
+
signal: controller.signal,
|
|
222
|
+
assistantMessageId // Pass expected assistant message ID for tracking
|
|
171
223
|
},
|
|
172
224
|
this.handleEvent
|
|
173
225
|
);
|
|
174
226
|
} catch (error) {
|
|
175
227
|
const fallback: AgentWidgetMessage = {
|
|
176
|
-
id:
|
|
228
|
+
id: assistantMessageId, // Use the pre-generated ID for fallback too
|
|
177
229
|
role: "assistant",
|
|
178
230
|
createdAt: new Date().toISOString(),
|
|
179
231
|
content:
|