vanilla-agent 1.21.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/src/ui.ts CHANGED
@@ -11,7 +11,8 @@ import {
11
11
  AgentWidgetStateEvent,
12
12
  AgentWidgetStateSnapshot,
13
13
  WidgetLayoutSlot,
14
- SlotRenderer
14
+ SlotRenderer,
15
+ AgentWidgetMessageFeedback
15
16
  } from "./types";
16
17
  import { applyThemeVariables } from "./utils/theme";
17
18
  import { renderLucideIcon } from "./utils/icons";
@@ -21,7 +22,7 @@ import { createLauncherButton } from "./components/launcher";
21
22
  import { createWrapper, buildPanel, buildHeader, buildComposer, attachHeaderToContainer } from "./components/panel";
22
23
  import { positionMap } from "./utils/positioning";
23
24
  import type { HeaderElements, ComposerElements } from "./components/panel";
24
- import { MessageTransform } from "./components/message-bubble";
25
+ import { MessageTransform, MessageActionCallbacks } from "./components/message-bubble";
25
26
  import { createStandardBubble, createTypingIndicator } from "./components/message-bubble";
26
27
  import { createReasoningBubble } from "./components/reasoning-bubble";
27
28
  import { createToolBubble } from "./components/tool-bubble";
@@ -42,6 +43,12 @@ import {
42
43
  extractComponentDirectiveFromMessage,
43
44
  hasComponentDirective
44
45
  } from "./utils/component-middleware";
46
+ import {
47
+ createCSATFeedback,
48
+ createNPSFeedback,
49
+ type CSATFeedbackOptions,
50
+ type NPSFeedbackOptions
51
+ } from "./components/feedback";
45
52
 
46
53
  // Default localStorage key for chat history (automatically cleared on clear chat)
47
54
  const DEFAULT_CHAT_HISTORY_STORAGE_KEY = "vanilla-agent-chat-history";
@@ -90,6 +97,11 @@ type Controller = {
90
97
  isOpen: () => boolean;
91
98
  isVoiceActive: () => boolean;
92
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>;
93
105
  };
94
106
 
95
107
  const buildPostprocessor = (
@@ -228,6 +240,38 @@ export const createAgentExperience = (
228
240
  let showReasoning = config.features?.showReasoning ?? true;
229
241
  let showToolCalls = config.features?.showToolCalls ?? true;
230
242
 
243
+ // Create message action callbacks that emit events and optionally send to API
244
+ const messageActionCallbacks: MessageActionCallbacks = {
245
+ onCopy: (message: AgentWidgetMessage) => {
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);
258
+ },
259
+ onFeedback: (feedback: AgentWidgetMessageFeedback) => {
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);
272
+ }
273
+ };
274
+
231
275
  // Get status indicator config
232
276
  const statusConfig = config.statusIndicator ?? {};
233
277
  const getStatusText = (status: AgentWidgetSessionStatus): string => {
@@ -877,7 +921,13 @@ export const createAgentExperience = (
877
921
  bubble = matchingPlugin.renderMessage({
878
922
  message,
879
923
  defaultRenderer: () => {
880
- const b = createStandardBubble(message, transform, messageLayoutConfig);
924
+ const b = createStandardBubble(
925
+ message,
926
+ transform,
927
+ messageLayoutConfig,
928
+ config.messageActions,
929
+ messageActionCallbacks
930
+ );
881
931
  if (message.role !== "user") {
882
932
  enhanceWithForms(b, message, config, session);
883
933
  }
@@ -957,7 +1007,13 @@ export const createAgentExperience = (
957
1007
  streaming: Boolean(message.streaming)
958
1008
  });
959
1009
  } else {
960
- bubble = createStandardBubble(message, transform, messageLayoutConfig);
1010
+ bubble = createStandardBubble(
1011
+ message,
1012
+ transform,
1013
+ messageLayoutConfig,
1014
+ config.messageActions,
1015
+ messageActionCallbacks
1016
+ );
961
1017
  }
962
1018
  if (message.role !== "user" && bubble) {
963
1019
  enhanceWithForms(bubble, message, config, session);
@@ -2774,6 +2830,67 @@ export const createAgentExperience = (
2774
2830
  streaming: session.isStreaming()
2775
2831
  };
2776
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
+ },
2777
2894
  destroy() {
2778
2895
  destroyCallbacks.forEach((cb) => cb());
2779
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
+