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
package/dist/widget.css
CHANGED
|
@@ -1400,3 +1400,227 @@ form:focus-within textarea {
|
|
|
1400
1400
|
height: 14px;
|
|
1401
1401
|
flex-shrink: 0;
|
|
1402
1402
|
}
|
|
1403
|
+
|
|
1404
|
+
/* ============================================================================
|
|
1405
|
+
* Feedback UI Components (CSAT/NPS)
|
|
1406
|
+
* ============================================================================ */
|
|
1407
|
+
|
|
1408
|
+
.tvw-feedback-container {
|
|
1409
|
+
background: var(--cw-surface, #ffffff);
|
|
1410
|
+
border: 1px solid var(--cw-border, #e5e7eb);
|
|
1411
|
+
border-radius: var(--tvw-cw-radius-lg, 12px);
|
|
1412
|
+
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
|
|
1413
|
+
padding: 1.25rem;
|
|
1414
|
+
max-width: 100%;
|
|
1415
|
+
margin: 0.75rem;
|
|
1416
|
+
animation: tvw-feedback-fade-in 0.3s ease-out;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
@keyframes tvw-feedback-fade-in {
|
|
1420
|
+
from {
|
|
1421
|
+
opacity: 0;
|
|
1422
|
+
transform: translateY(10px);
|
|
1423
|
+
}
|
|
1424
|
+
to {
|
|
1425
|
+
opacity: 1;
|
|
1426
|
+
transform: translateY(0);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
.tvw-feedback-content {
|
|
1431
|
+
display: flex;
|
|
1432
|
+
flex-direction: column;
|
|
1433
|
+
gap: 1rem;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
.tvw-feedback-header {
|
|
1437
|
+
text-align: center;
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
.tvw-feedback-title {
|
|
1441
|
+
margin: 0 0 0.25rem 0;
|
|
1442
|
+
font-size: 1rem;
|
|
1443
|
+
font-weight: 600;
|
|
1444
|
+
color: var(--cw-primary, #111827);
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
.tvw-feedback-subtitle {
|
|
1448
|
+
margin: 0;
|
|
1449
|
+
font-size: 0.875rem;
|
|
1450
|
+
color: var(--cw-muted, #6b7280);
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
/* CSAT Star Rating */
|
|
1454
|
+
.tvw-feedback-rating-csat {
|
|
1455
|
+
display: flex;
|
|
1456
|
+
justify-content: center;
|
|
1457
|
+
gap: 0.5rem;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
.tvw-feedback-star-btn {
|
|
1461
|
+
background: transparent;
|
|
1462
|
+
border: none;
|
|
1463
|
+
cursor: pointer;
|
|
1464
|
+
padding: 0.25rem;
|
|
1465
|
+
color: var(--cw-border, #d1d5db);
|
|
1466
|
+
transition: color 0.2s ease, transform 0.15s ease;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
.tvw-feedback-star-btn:hover {
|
|
1470
|
+
transform: scale(1.15);
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
.tvw-feedback-star-btn.selected {
|
|
1474
|
+
color: #fbbf24;
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
.tvw-feedback-star-btn .tvw-feedback-star {
|
|
1478
|
+
width: 32px;
|
|
1479
|
+
height: 32px;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
.tvw-feedback-star-btn.selected .tvw-feedback-star {
|
|
1483
|
+
fill: #fbbf24;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
/* NPS Number Rating */
|
|
1487
|
+
.tvw-feedback-rating-nps {
|
|
1488
|
+
display: flex;
|
|
1489
|
+
flex-direction: column;
|
|
1490
|
+
gap: 0.5rem;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
.tvw-feedback-labels {
|
|
1494
|
+
display: flex;
|
|
1495
|
+
justify-content: space-between;
|
|
1496
|
+
font-size: 0.75rem;
|
|
1497
|
+
color: var(--cw-muted, #6b7280);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
.tvw-feedback-numbers {
|
|
1501
|
+
display: flex;
|
|
1502
|
+
gap: 0.25rem;
|
|
1503
|
+
justify-content: center;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
.tvw-feedback-number-btn {
|
|
1507
|
+
width: 28px;
|
|
1508
|
+
height: 28px;
|
|
1509
|
+
display: flex;
|
|
1510
|
+
align-items: center;
|
|
1511
|
+
justify-content: center;
|
|
1512
|
+
font-size: 0.75rem;
|
|
1513
|
+
font-weight: 500;
|
|
1514
|
+
border-radius: var(--tvw-cw-radius-sm, 6px);
|
|
1515
|
+
border: 1px solid var(--cw-border, #e5e7eb);
|
|
1516
|
+
background: var(--cw-surface, #ffffff);
|
|
1517
|
+
color: var(--cw-primary, #111827);
|
|
1518
|
+
cursor: pointer;
|
|
1519
|
+
transition: all 0.2s ease;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
.tvw-feedback-number-btn:hover {
|
|
1523
|
+
border-color: var(--cw-accent, #1d4ed8);
|
|
1524
|
+
background: var(--cw-container, #f3f4f6);
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
.tvw-feedback-number-btn.selected {
|
|
1528
|
+
color: #ffffff;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
/* NPS Color coding */
|
|
1532
|
+
.tvw-feedback-number-btn.tvw-feedback-detractor.selected {
|
|
1533
|
+
background: #ef4444;
|
|
1534
|
+
border-color: #ef4444;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
.tvw-feedback-number-btn.tvw-feedback-passive.selected {
|
|
1538
|
+
background: #f59e0b;
|
|
1539
|
+
border-color: #f59e0b;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
.tvw-feedback-number-btn.tvw-feedback-promoter.selected {
|
|
1543
|
+
background: #22c55e;
|
|
1544
|
+
border-color: #22c55e;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
/* Comment textarea */
|
|
1548
|
+
.tvw-feedback-comment-container {
|
|
1549
|
+
width: 100%;
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
.tvw-feedback-comment {
|
|
1553
|
+
width: 100%;
|
|
1554
|
+
padding: 0.625rem;
|
|
1555
|
+
font-size: 0.875rem;
|
|
1556
|
+
font-family: inherit;
|
|
1557
|
+
border: 1px solid var(--cw-border, #e5e7eb);
|
|
1558
|
+
border-radius: var(--tvw-cw-radius-sm, 6px);
|
|
1559
|
+
background: var(--cw-surface, #ffffff);
|
|
1560
|
+
color: var(--cw-primary, #111827);
|
|
1561
|
+
resize: vertical;
|
|
1562
|
+
box-sizing: border-box;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
.tvw-feedback-comment:focus {
|
|
1566
|
+
outline: none;
|
|
1567
|
+
border-color: var(--cw-accent, #1d4ed8);
|
|
1568
|
+
box-shadow: 0 0 0 2px rgba(29, 78, 216, 0.15);
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
.tvw-feedback-comment::placeholder {
|
|
1572
|
+
color: var(--cw-muted, #9ca3af);
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
/* Action buttons */
|
|
1576
|
+
.tvw-feedback-actions {
|
|
1577
|
+
display: flex;
|
|
1578
|
+
gap: 0.5rem;
|
|
1579
|
+
justify-content: flex-end;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
.tvw-feedback-btn {
|
|
1583
|
+
padding: 0.5rem 1rem;
|
|
1584
|
+
font-size: 0.875rem;
|
|
1585
|
+
font-weight: 500;
|
|
1586
|
+
border-radius: var(--tvw-cw-radius-sm, 6px);
|
|
1587
|
+
cursor: pointer;
|
|
1588
|
+
transition: all 0.2s ease;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
.tvw-feedback-btn-skip {
|
|
1592
|
+
background: transparent;
|
|
1593
|
+
border: 1px solid var(--cw-border, #e5e7eb);
|
|
1594
|
+
color: var(--cw-muted, #6b7280);
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
.tvw-feedback-btn-skip:hover {
|
|
1598
|
+
background: var(--cw-container, #f3f4f6);
|
|
1599
|
+
color: var(--cw-primary, #111827);
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
.tvw-feedback-btn-submit {
|
|
1603
|
+
background: var(--cw-accent, #1d4ed8);
|
|
1604
|
+
border: 1px solid var(--cw-accent, #1d4ed8);
|
|
1605
|
+
color: #ffffff;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
.tvw-feedback-btn-submit:hover:not(:disabled) {
|
|
1609
|
+
opacity: 0.9;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
.tvw-feedback-btn-submit:disabled {
|
|
1613
|
+
opacity: 0.6;
|
|
1614
|
+
cursor: not-allowed;
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
/* Shake animation for validation */
|
|
1618
|
+
@keyframes tvw-feedback-shake {
|
|
1619
|
+
0%, 100% { transform: translateX(0); }
|
|
1620
|
+
10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
|
|
1621
|
+
20%, 40%, 60%, 80% { transform: translateX(4px); }
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
.tvw-feedback-shake {
|
|
1625
|
+
animation: tvw-feedback-shake 0.5s ease-in-out;
|
|
1626
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vanilla-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.23.0",
|
|
4
4
|
"description": "Themeable, plugable streaming agent widget for websites, in plain JS with support for voice input and reasoning / tool output.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
],
|
|
53
53
|
"repository": {
|
|
54
54
|
"type": "git",
|
|
55
|
-
"url": "git+https://github.com/becomevocal/
|
|
55
|
+
"url": "git+https://github.com/becomevocal/vanilla-agent.git"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"build": "rimraf dist && npm run build:styles && npm run build:client && npm run build:installer",
|
package/src/client.ts
CHANGED
|
@@ -12,7 +12,9 @@ import {
|
|
|
12
12
|
AgentWidgetSSEEventResult,
|
|
13
13
|
ClientSession,
|
|
14
14
|
ClientInitResponse,
|
|
15
|
-
ClientChatRequest
|
|
15
|
+
ClientChatRequest,
|
|
16
|
+
ClientFeedbackRequest,
|
|
17
|
+
ClientFeedbackType
|
|
16
18
|
} from "./types";
|
|
17
19
|
import {
|
|
18
20
|
extractTextFromJson,
|
|
@@ -25,6 +27,8 @@ import {
|
|
|
25
27
|
type DispatchOptions = {
|
|
26
28
|
messages: AgentWidgetMessage[];
|
|
27
29
|
signal?: AbortSignal;
|
|
30
|
+
/** Pre-generated ID for the expected assistant response (for feedback tracking) */
|
|
31
|
+
assistantMessageId?: string;
|
|
28
32
|
};
|
|
29
33
|
|
|
30
34
|
type SSEHandler = (event: AgentWidgetEvent) => void;
|
|
@@ -178,6 +182,166 @@ export class AgentWidgetClient {
|
|
|
178
182
|
this.sessionInitPromise = null;
|
|
179
183
|
}
|
|
180
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Get the feedback API URL
|
|
187
|
+
*/
|
|
188
|
+
private getFeedbackApiUrl(): string {
|
|
189
|
+
const baseUrl = this.config.apiUrl?.replace(/\/+$/, '').replace(/\/v1\/dispatch$/, '') || DEFAULT_CLIENT_API_BASE;
|
|
190
|
+
return `${baseUrl}/v1/client/feedback`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Send feedback for a message (client token mode only).
|
|
195
|
+
* Supports upvote, downvote, copy, csat, and nps feedback types.
|
|
196
|
+
*
|
|
197
|
+
* @param feedback - The feedback request payload
|
|
198
|
+
* @returns Promise that resolves when feedback is sent successfully
|
|
199
|
+
* @throws Error if not in client token mode or if session is invalid
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```typescript
|
|
203
|
+
* // Message feedback (upvote/downvote/copy)
|
|
204
|
+
* await client.sendFeedback({
|
|
205
|
+
* session_id: sessionId,
|
|
206
|
+
* message_id: messageId,
|
|
207
|
+
* type: 'upvote'
|
|
208
|
+
* });
|
|
209
|
+
*
|
|
210
|
+
* // CSAT feedback (1-5 rating)
|
|
211
|
+
* await client.sendFeedback({
|
|
212
|
+
* session_id: sessionId,
|
|
213
|
+
* type: 'csat',
|
|
214
|
+
* rating: 5,
|
|
215
|
+
* comment: 'Great experience!'
|
|
216
|
+
* });
|
|
217
|
+
*
|
|
218
|
+
* // NPS feedback (0-10 rating)
|
|
219
|
+
* await client.sendFeedback({
|
|
220
|
+
* session_id: sessionId,
|
|
221
|
+
* type: 'nps',
|
|
222
|
+
* rating: 9
|
|
223
|
+
* });
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
public async sendFeedback(feedback: ClientFeedbackRequest): Promise<void> {
|
|
227
|
+
if (!this.isClientTokenMode()) {
|
|
228
|
+
throw new Error('sendFeedback() only available in client token mode');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const session = this.getClientSession();
|
|
232
|
+
if (!session) {
|
|
233
|
+
throw new Error('No active session. Please initialize session first.');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Validate message_id is provided for message-level feedback types
|
|
237
|
+
const messageFeedbackTypes: ClientFeedbackType[] = ['upvote', 'downvote', 'copy'];
|
|
238
|
+
if (messageFeedbackTypes.includes(feedback.type) && !feedback.message_id) {
|
|
239
|
+
throw new Error(`message_id is required for ${feedback.type} feedback type`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Validate rating is provided for csat/nps feedback types
|
|
243
|
+
if (feedback.type === 'csat') {
|
|
244
|
+
if (feedback.rating === undefined || feedback.rating < 1 || feedback.rating > 5) {
|
|
245
|
+
throw new Error('CSAT rating must be between 1 and 5');
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (feedback.type === 'nps') {
|
|
249
|
+
if (feedback.rating === undefined || feedback.rating < 0 || feedback.rating > 10) {
|
|
250
|
+
throw new Error('NPS rating must be between 0 and 10');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (this.debug) {
|
|
255
|
+
// eslint-disable-next-line no-console
|
|
256
|
+
console.debug("[AgentWidgetClient] sending feedback", feedback);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const response = await fetch(this.getFeedbackApiUrl(), {
|
|
260
|
+
method: 'POST',
|
|
261
|
+
headers: {
|
|
262
|
+
'Content-Type': 'application/json',
|
|
263
|
+
},
|
|
264
|
+
body: JSON.stringify(feedback),
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
if (!response.ok) {
|
|
268
|
+
const errorData = await response.json().catch(() => ({ error: 'Feedback submission failed' }));
|
|
269
|
+
|
|
270
|
+
if (response.status === 401) {
|
|
271
|
+
this.clientSession = null;
|
|
272
|
+
this.config.onSessionExpired?.();
|
|
273
|
+
throw new Error('Session expired. Please refresh to continue.');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
throw new Error(errorData.error || 'Failed to submit feedback');
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Submit message feedback (upvote, downvote, or copy).
|
|
282
|
+
* Convenience method for sendFeedback with message-level feedback.
|
|
283
|
+
*
|
|
284
|
+
* @param messageId - The ID of the message to provide feedback for
|
|
285
|
+
* @param type - The feedback type: 'upvote', 'downvote', or 'copy'
|
|
286
|
+
*/
|
|
287
|
+
public async submitMessageFeedback(
|
|
288
|
+
messageId: string,
|
|
289
|
+
type: 'upvote' | 'downvote' | 'copy'
|
|
290
|
+
): Promise<void> {
|
|
291
|
+
const session = this.getClientSession();
|
|
292
|
+
if (!session) {
|
|
293
|
+
throw new Error('No active session. Please initialize session first.');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return this.sendFeedback({
|
|
297
|
+
session_id: session.sessionId,
|
|
298
|
+
message_id: messageId,
|
|
299
|
+
type,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Submit CSAT (Customer Satisfaction) feedback.
|
|
305
|
+
* Convenience method for sendFeedback with CSAT feedback.
|
|
306
|
+
*
|
|
307
|
+
* @param rating - Rating from 1 to 5
|
|
308
|
+
* @param comment - Optional comment
|
|
309
|
+
*/
|
|
310
|
+
public async submitCSATFeedback(rating: number, comment?: string): Promise<void> {
|
|
311
|
+
const session = this.getClientSession();
|
|
312
|
+
if (!session) {
|
|
313
|
+
throw new Error('No active session. Please initialize session first.');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return this.sendFeedback({
|
|
317
|
+
session_id: session.sessionId,
|
|
318
|
+
type: 'csat',
|
|
319
|
+
rating,
|
|
320
|
+
comment,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Submit NPS (Net Promoter Score) feedback.
|
|
326
|
+
* Convenience method for sendFeedback with NPS feedback.
|
|
327
|
+
*
|
|
328
|
+
* @param rating - Rating from 0 to 10
|
|
329
|
+
* @param comment - Optional comment
|
|
330
|
+
*/
|
|
331
|
+
public async submitNPSFeedback(rating: number, comment?: string): Promise<void> {
|
|
332
|
+
const session = this.getClientSession();
|
|
333
|
+
if (!session) {
|
|
334
|
+
throw new Error('No active session. Please initialize session first.');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return this.sendFeedback({
|
|
338
|
+
session_id: session.sessionId,
|
|
339
|
+
type: 'nps',
|
|
340
|
+
rating,
|
|
341
|
+
comment,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
181
345
|
/**
|
|
182
346
|
* Send a message - handles both proxy and client token modes
|
|
183
347
|
*/
|
|
@@ -213,13 +377,22 @@ export class AgentWidgetClient {
|
|
|
213
377
|
throw error;
|
|
214
378
|
}
|
|
215
379
|
|
|
216
|
-
// Build the
|
|
380
|
+
// Build the standard payload to get context/metadata from middleware
|
|
381
|
+
const basePayload = await this.buildPayload(options.messages);
|
|
382
|
+
|
|
383
|
+
// Build the chat request payload with message IDs for feedback tracking
|
|
217
384
|
const chatRequest: ClientChatRequest = {
|
|
218
385
|
session_id: session.sessionId,
|
|
219
386
|
messages: options.messages.map(m => ({
|
|
387
|
+
id: m.id, // Include message ID for tracking
|
|
220
388
|
role: m.role,
|
|
221
389
|
content: m.rawContent || m.content,
|
|
222
390
|
})),
|
|
391
|
+
// Include pre-generated assistant message ID if provided
|
|
392
|
+
...(options.assistantMessageId && { assistant_message_id: options.assistantMessageId }),
|
|
393
|
+
// Include metadata/context from middleware if present
|
|
394
|
+
...(basePayload.metadata && { metadata: basePayload.metadata }),
|
|
395
|
+
...(basePayload.context && { context: basePayload.context }),
|
|
223
396
|
};
|
|
224
397
|
|
|
225
398
|
if (this.debug) {
|
|
@@ -269,7 +442,7 @@ export class AgentWidgetClient {
|
|
|
269
442
|
|
|
270
443
|
// Stream the response (same SSE handling as proxy mode)
|
|
271
444
|
try {
|
|
272
|
-
await this.streamResponse(response.body, onEvent);
|
|
445
|
+
await this.streamResponse(response.body, onEvent, options.assistantMessageId);
|
|
273
446
|
} finally {
|
|
274
447
|
onEvent({ type: "status", status: "idle" });
|
|
275
448
|
}
|
|
@@ -492,7 +665,8 @@ export class AgentWidgetClient {
|
|
|
492
665
|
|
|
493
666
|
private async streamResponse(
|
|
494
667
|
body: ReadableStream<Uint8Array>,
|
|
495
|
-
onEvent: SSEHandler
|
|
668
|
+
onEvent: SSEHandler,
|
|
669
|
+
assistantMessageId?: string
|
|
496
670
|
) {
|
|
497
671
|
const reader = body.getReader();
|
|
498
672
|
const decoder = new TextDecoder();
|
|
@@ -585,7 +759,8 @@ export class AgentWidgetClient {
|
|
|
585
759
|
const ensureAssistantMessage = () => {
|
|
586
760
|
if (assistantMessage) return assistantMessage;
|
|
587
761
|
assistantMessage = {
|
|
588
|
-
|
|
762
|
+
// Use pre-generated ID if provided, otherwise generate one
|
|
763
|
+
id: assistantMessageId ?? `assistant-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
|
589
764
|
role: "assistant",
|
|
590
765
|
content: "",
|
|
591
766
|
createdAt: new Date().toISOString(),
|