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/src/styles/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/src/types.ts
CHANGED
|
@@ -22,6 +22,7 @@ export type AgentWidgetRequestPayload = {
|
|
|
22
22
|
messages: AgentWidgetRequestPayloadMessage[];
|
|
23
23
|
flowId?: string;
|
|
24
24
|
context?: Record<string, unknown>;
|
|
25
|
+
metadata?: Record<string, unknown>;
|
|
25
26
|
};
|
|
26
27
|
|
|
27
28
|
export type AgentWidgetRequestMiddlewareContext = {
|
|
@@ -567,9 +568,33 @@ export type ClientInitResponse = {
|
|
|
567
568
|
export type ClientChatRequest = {
|
|
568
569
|
session_id: string;
|
|
569
570
|
messages: Array<{
|
|
571
|
+
id?: string;
|
|
570
572
|
role: 'user' | 'assistant' | 'system';
|
|
571
573
|
content: string;
|
|
572
574
|
}>;
|
|
575
|
+
/** ID for the expected assistant response message */
|
|
576
|
+
assistant_message_id?: string;
|
|
577
|
+
metadata?: Record<string, unknown>;
|
|
578
|
+
context?: Record<string, unknown>;
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Feedback types supported by the API
|
|
583
|
+
*/
|
|
584
|
+
export type ClientFeedbackType = 'upvote' | 'downvote' | 'copy' | 'csat' | 'nps';
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Request payload for /v1/client/feedback endpoint
|
|
588
|
+
*/
|
|
589
|
+
export type ClientFeedbackRequest = {
|
|
590
|
+
session_id: string;
|
|
591
|
+
/** Required for upvote, downvote, copy feedback types */
|
|
592
|
+
message_id?: string;
|
|
593
|
+
type: ClientFeedbackType;
|
|
594
|
+
/** Required for csat (1-5) and nps (0-10) feedback types */
|
|
595
|
+
rating?: number;
|
|
596
|
+
/** Optional comment for any feedback type */
|
|
597
|
+
comment?: string;
|
|
573
598
|
};
|
|
574
599
|
|
|
575
600
|
// ============================================================================
|
package/src/ui.ts
CHANGED
|
@@ -43,6 +43,12 @@ import {
|
|
|
43
43
|
extractComponentDirectiveFromMessage,
|
|
44
44
|
hasComponentDirective
|
|
45
45
|
} from "./utils/component-middleware";
|
|
46
|
+
import {
|
|
47
|
+
createCSATFeedback,
|
|
48
|
+
createNPSFeedback,
|
|
49
|
+
type CSATFeedbackOptions,
|
|
50
|
+
type NPSFeedbackOptions
|
|
51
|
+
} from "./components/feedback";
|
|
46
52
|
|
|
47
53
|
// Default localStorage key for chat history (automatically cleared on clear chat)
|
|
48
54
|
const DEFAULT_CHAT_HISTORY_STORAGE_KEY = "vanilla-agent-chat-history";
|
|
@@ -91,6 +97,11 @@ type Controller = {
|
|
|
91
97
|
isOpen: () => boolean;
|
|
92
98
|
isVoiceActive: () => boolean;
|
|
93
99
|
getState: () => AgentWidgetStateSnapshot;
|
|
100
|
+
// Feedback methods (CSAT/NPS)
|
|
101
|
+
showCSATFeedback: (options?: Partial<CSATFeedbackOptions>) => void;
|
|
102
|
+
showNPSFeedback: (options?: Partial<NPSFeedbackOptions>) => void;
|
|
103
|
+
submitCSATFeedback: (rating: number, comment?: string) => Promise<void>;
|
|
104
|
+
submitNPSFeedback: (rating: number, comment?: string) => Promise<void>;
|
|
94
105
|
};
|
|
95
106
|
|
|
96
107
|
const buildPostprocessor = (
|
|
@@ -229,13 +240,35 @@ export const createAgentExperience = (
|
|
|
229
240
|
let showReasoning = config.features?.showReasoning ?? true;
|
|
230
241
|
let showToolCalls = config.features?.showToolCalls ?? true;
|
|
231
242
|
|
|
232
|
-
// Create message action callbacks that emit events
|
|
243
|
+
// Create message action callbacks that emit events and optionally send to API
|
|
233
244
|
const messageActionCallbacks: MessageActionCallbacks = {
|
|
234
245
|
onCopy: (message: AgentWidgetMessage) => {
|
|
235
246
|
eventBus.emit("message:copy", message);
|
|
247
|
+
// Send copy feedback to API if in client token mode
|
|
248
|
+
if (session?.isClientTokenMode()) {
|
|
249
|
+
session.submitMessageFeedback(message.id, 'copy').catch((error) => {
|
|
250
|
+
if (config.debug) {
|
|
251
|
+
// eslint-disable-next-line no-console
|
|
252
|
+
console.error("[AgentWidget] Failed to submit copy feedback:", error);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
// Call user-provided callback
|
|
257
|
+
config.messageActions?.onCopy?.(message);
|
|
236
258
|
},
|
|
237
259
|
onFeedback: (feedback: AgentWidgetMessageFeedback) => {
|
|
238
260
|
eventBus.emit("message:feedback", feedback);
|
|
261
|
+
// Send feedback to API if in client token mode
|
|
262
|
+
if (session?.isClientTokenMode()) {
|
|
263
|
+
session.submitMessageFeedback(feedback.messageId, feedback.type).catch((error) => {
|
|
264
|
+
if (config.debug) {
|
|
265
|
+
// eslint-disable-next-line no-console
|
|
266
|
+
console.error("[AgentWidget] Failed to submit feedback:", error);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
// Call user-provided callback
|
|
271
|
+
config.messageActions?.onFeedback?.(feedback);
|
|
239
272
|
}
|
|
240
273
|
};
|
|
241
274
|
|
|
@@ -2797,6 +2830,67 @@ export const createAgentExperience = (
|
|
|
2797
2830
|
streaming: session.isStreaming()
|
|
2798
2831
|
};
|
|
2799
2832
|
},
|
|
2833
|
+
// Feedback methods (CSAT/NPS)
|
|
2834
|
+
showCSATFeedback(options?: Partial<CSATFeedbackOptions>) {
|
|
2835
|
+
// Auto-open widget if closed and launcher is enabled
|
|
2836
|
+
if (!open && launcherEnabled) {
|
|
2837
|
+
setOpenState(true, "system");
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
// Remove any existing feedback forms
|
|
2841
|
+
const existingFeedback = messagesWrapper.querySelector('.tvw-feedback-container');
|
|
2842
|
+
if (existingFeedback) {
|
|
2843
|
+
existingFeedback.remove();
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
const feedbackEl = createCSATFeedback({
|
|
2847
|
+
onSubmit: async (rating, comment) => {
|
|
2848
|
+
if (session.isClientTokenMode()) {
|
|
2849
|
+
await session.submitCSATFeedback(rating, comment);
|
|
2850
|
+
}
|
|
2851
|
+
options?.onSubmit?.(rating, comment);
|
|
2852
|
+
},
|
|
2853
|
+
onDismiss: options?.onDismiss,
|
|
2854
|
+
...options,
|
|
2855
|
+
});
|
|
2856
|
+
|
|
2857
|
+
// Append to messages area at the bottom
|
|
2858
|
+
messagesWrapper.appendChild(feedbackEl);
|
|
2859
|
+
feedbackEl.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
|
2860
|
+
},
|
|
2861
|
+
showNPSFeedback(options?: Partial<NPSFeedbackOptions>) {
|
|
2862
|
+
// Auto-open widget if closed and launcher is enabled
|
|
2863
|
+
if (!open && launcherEnabled) {
|
|
2864
|
+
setOpenState(true, "system");
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2867
|
+
// Remove any existing feedback forms
|
|
2868
|
+
const existingFeedback = messagesWrapper.querySelector('.tvw-feedback-container');
|
|
2869
|
+
if (existingFeedback) {
|
|
2870
|
+
existingFeedback.remove();
|
|
2871
|
+
}
|
|
2872
|
+
|
|
2873
|
+
const feedbackEl = createNPSFeedback({
|
|
2874
|
+
onSubmit: async (rating, comment) => {
|
|
2875
|
+
if (session.isClientTokenMode()) {
|
|
2876
|
+
await session.submitNPSFeedback(rating, comment);
|
|
2877
|
+
}
|
|
2878
|
+
options?.onSubmit?.(rating, comment);
|
|
2879
|
+
},
|
|
2880
|
+
onDismiss: options?.onDismiss,
|
|
2881
|
+
...options,
|
|
2882
|
+
});
|
|
2883
|
+
|
|
2884
|
+
// Append to messages area at the bottom
|
|
2885
|
+
messagesWrapper.appendChild(feedbackEl);
|
|
2886
|
+
feedbackEl.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
|
2887
|
+
},
|
|
2888
|
+
async submitCSATFeedback(rating: number, comment?: string): Promise<void> {
|
|
2889
|
+
return session.submitCSATFeedback(rating, comment);
|
|
2890
|
+
},
|
|
2891
|
+
async submitNPSFeedback(rating: number, comment?: string): Promise<void> {
|
|
2892
|
+
return session.submitNPSFeedback(rating, comment);
|
|
2893
|
+
},
|
|
2800
2894
|
destroy() {
|
|
2801
2895
|
destroyCallbacks.forEach((cb) => cb());
|
|
2802
2896
|
wrapper.remove();
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message ID utilities for client-side message tracking
|
|
3
|
+
* Used for feedback integration with the Travrse API
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generate a unique message ID for tracking
|
|
8
|
+
* Format: msg_{timestamp_base36}_{random_8chars}
|
|
9
|
+
*/
|
|
10
|
+
export function generateMessageId(): string {
|
|
11
|
+
const timestamp = Date.now().toString(36);
|
|
12
|
+
const random = Math.random().toString(36).substring(2, 10);
|
|
13
|
+
return `msg_${timestamp}_${random}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generate a unique user message ID
|
|
18
|
+
* Format: usr_{timestamp_base36}_{random_8chars}
|
|
19
|
+
*/
|
|
20
|
+
export function generateUserMessageId(): string {
|
|
21
|
+
const timestamp = Date.now().toString(36);
|
|
22
|
+
const random = Math.random().toString(36).substring(2, 10);
|
|
23
|
+
return `usr_${timestamp}_${random}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate a unique assistant message ID
|
|
28
|
+
* Format: ast_{timestamp_base36}_{random_8chars}
|
|
29
|
+
*/
|
|
30
|
+
export function generateAssistantMessageId(): string {
|
|
31
|
+
const timestamp = Date.now().toString(36);
|
|
32
|
+
const random = Math.random().toString(36).substring(2, 10);
|
|
33
|
+
return `ast_${timestamp}_${random}`;
|
|
34
|
+
}
|
|
35
|
+
|