@wallavi/widget 1.0.1 → 1.1.1
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.d.mts +41 -2
- package/dist/index.d.ts +41 -2
- package/dist/index.js +128 -41
- package/dist/index.mjs +129 -42
- package/package.json +10 -20
package/dist/index.d.mts
CHANGED
|
@@ -21,11 +21,23 @@ type Message = {
|
|
|
21
21
|
role: "user" | "assistant";
|
|
22
22
|
parts: MessagePart[];
|
|
23
23
|
};
|
|
24
|
+
interface PageContext {
|
|
25
|
+
/** Current URL or path (e.g. "/dashboard/agent/abc/agent-studio") */
|
|
26
|
+
url?: string;
|
|
27
|
+
/** Human-readable page name (e.g. "Agent Studio") */
|
|
28
|
+
title?: string;
|
|
29
|
+
/** Route parameters relevant to the page (e.g. { agentId: "abc123" }) */
|
|
30
|
+
params?: Record<string, string>;
|
|
31
|
+
/** Any custom key-value pairs the developer wants the agent to know about */
|
|
32
|
+
vars?: Record<string, unknown>;
|
|
33
|
+
}
|
|
24
34
|
interface UserContext {
|
|
25
35
|
headers?: Record<string, string>;
|
|
26
36
|
userId?: string;
|
|
27
37
|
userName?: string;
|
|
28
38
|
userEmail?: string;
|
|
39
|
+
/** Contextual info about where the user is and what they're doing */
|
|
40
|
+
pageContext?: PageContext;
|
|
29
41
|
metadata?: Record<string, unknown>;
|
|
30
42
|
}
|
|
31
43
|
interface ChatWidgetConfig {
|
|
@@ -43,8 +55,27 @@ interface ChatWidgetConfig {
|
|
|
43
55
|
footer?: string | null;
|
|
44
56
|
theme?: "light" | "dark";
|
|
45
57
|
showThinking?: boolean;
|
|
58
|
+
regenerateMessage?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Persist messages and threadId in sessionStorage so the conversation
|
|
61
|
+
* survives page reloads and navigations. Uses agentId as the storage key.
|
|
62
|
+
*/
|
|
63
|
+
persist?: boolean;
|
|
64
|
+
onNavigate?: (path: string) => void;
|
|
65
|
+
/**
|
|
66
|
+
* Hide the close (X) button in the widget header.
|
|
67
|
+
* Useful when the integrator controls open/close from an outer bubble wrapper
|
|
68
|
+
* and wants to avoid showing two close buttons.
|
|
69
|
+
*/
|
|
70
|
+
hideCloseButton?: boolean;
|
|
46
71
|
source?: string;
|
|
47
72
|
userContext?: UserContext;
|
|
73
|
+
/** Per-session tool overrides used by Playground (not persisted) */
|
|
74
|
+
playgroundOverrides?: {
|
|
75
|
+
ragIds?: string[];
|
|
76
|
+
mcpIds?: string[];
|
|
77
|
+
actionIds?: string[];
|
|
78
|
+
};
|
|
48
79
|
}
|
|
49
80
|
interface ChatWidgetProps extends ChatWidgetConfig {
|
|
50
81
|
className?: string;
|
|
@@ -54,13 +85,20 @@ interface ChatWidgetProps extends ChatWidgetConfig {
|
|
|
54
85
|
declare function getContrastColor(hex: string): string;
|
|
55
86
|
declare function formatToolName(name: string): string;
|
|
56
87
|
|
|
57
|
-
declare function ChatWidget({ agentId, workspaceId, agentName, displayName, profilePicture, userMessageColor, initialMessages, suggestedMessages, messagePlaceholder, watermark, watermarkLogoUrl, footer, theme, showThinking, source, className, onClose, onReset, }: ChatWidgetProps): react_jsx_runtime.JSX.Element;
|
|
88
|
+
declare function ChatWidget({ agentId, workspaceId, agentName, displayName, profilePicture, userMessageColor, initialMessages, suggestedMessages, messagePlaceholder, watermark, watermarkLogoUrl, footer, theme, showThinking, regenerateMessage, persist, onNavigate, hideCloseButton, source, userContext, playgroundOverrides, className, onClose, onReset, }: ChatWidgetProps): react_jsx_runtime.JSX.Element;
|
|
58
89
|
|
|
59
90
|
interface UseChatOptions {
|
|
60
91
|
agentId: string;
|
|
61
92
|
workspaceId?: string;
|
|
62
93
|
source?: string;
|
|
63
94
|
userContext?: UserContext;
|
|
95
|
+
persist?: boolean;
|
|
96
|
+
onNavigate?: (path: string) => void;
|
|
97
|
+
playgroundOverrides?: {
|
|
98
|
+
ragIds?: string[];
|
|
99
|
+
mcpIds?: string[];
|
|
100
|
+
actionIds?: string[];
|
|
101
|
+
};
|
|
64
102
|
}
|
|
65
103
|
interface UseChatReturn {
|
|
66
104
|
messages: Message[];
|
|
@@ -69,8 +107,9 @@ interface UseChatReturn {
|
|
|
69
107
|
streaming: boolean;
|
|
70
108
|
threadId: string;
|
|
71
109
|
send: (text?: string) => Promise<void>;
|
|
110
|
+
regenerate: () => Promise<void>;
|
|
72
111
|
reset: () => void;
|
|
73
112
|
}
|
|
74
|
-
declare function useChat({ agentId, workspaceId, source, userContext, }: UseChatOptions): UseChatReturn;
|
|
113
|
+
declare function useChat({ agentId, workspaceId, source, userContext, persist, onNavigate, playgroundOverrides, }: UseChatOptions): UseChatReturn;
|
|
75
114
|
|
|
76
115
|
export { ChatWidget, type ChatWidgetConfig, type ChatWidgetProps, type Message, type MessagePart, type ToolPart, type UserContext, formatToolName, getContrastColor, useChat };
|
package/dist/index.d.ts
CHANGED
|
@@ -21,11 +21,23 @@ type Message = {
|
|
|
21
21
|
role: "user" | "assistant";
|
|
22
22
|
parts: MessagePart[];
|
|
23
23
|
};
|
|
24
|
+
interface PageContext {
|
|
25
|
+
/** Current URL or path (e.g. "/dashboard/agent/abc/agent-studio") */
|
|
26
|
+
url?: string;
|
|
27
|
+
/** Human-readable page name (e.g. "Agent Studio") */
|
|
28
|
+
title?: string;
|
|
29
|
+
/** Route parameters relevant to the page (e.g. { agentId: "abc123" }) */
|
|
30
|
+
params?: Record<string, string>;
|
|
31
|
+
/** Any custom key-value pairs the developer wants the agent to know about */
|
|
32
|
+
vars?: Record<string, unknown>;
|
|
33
|
+
}
|
|
24
34
|
interface UserContext {
|
|
25
35
|
headers?: Record<string, string>;
|
|
26
36
|
userId?: string;
|
|
27
37
|
userName?: string;
|
|
28
38
|
userEmail?: string;
|
|
39
|
+
/** Contextual info about where the user is and what they're doing */
|
|
40
|
+
pageContext?: PageContext;
|
|
29
41
|
metadata?: Record<string, unknown>;
|
|
30
42
|
}
|
|
31
43
|
interface ChatWidgetConfig {
|
|
@@ -43,8 +55,27 @@ interface ChatWidgetConfig {
|
|
|
43
55
|
footer?: string | null;
|
|
44
56
|
theme?: "light" | "dark";
|
|
45
57
|
showThinking?: boolean;
|
|
58
|
+
regenerateMessage?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Persist messages and threadId in sessionStorage so the conversation
|
|
61
|
+
* survives page reloads and navigations. Uses agentId as the storage key.
|
|
62
|
+
*/
|
|
63
|
+
persist?: boolean;
|
|
64
|
+
onNavigate?: (path: string) => void;
|
|
65
|
+
/**
|
|
66
|
+
* Hide the close (X) button in the widget header.
|
|
67
|
+
* Useful when the integrator controls open/close from an outer bubble wrapper
|
|
68
|
+
* and wants to avoid showing two close buttons.
|
|
69
|
+
*/
|
|
70
|
+
hideCloseButton?: boolean;
|
|
46
71
|
source?: string;
|
|
47
72
|
userContext?: UserContext;
|
|
73
|
+
/** Per-session tool overrides used by Playground (not persisted) */
|
|
74
|
+
playgroundOverrides?: {
|
|
75
|
+
ragIds?: string[];
|
|
76
|
+
mcpIds?: string[];
|
|
77
|
+
actionIds?: string[];
|
|
78
|
+
};
|
|
48
79
|
}
|
|
49
80
|
interface ChatWidgetProps extends ChatWidgetConfig {
|
|
50
81
|
className?: string;
|
|
@@ -54,13 +85,20 @@ interface ChatWidgetProps extends ChatWidgetConfig {
|
|
|
54
85
|
declare function getContrastColor(hex: string): string;
|
|
55
86
|
declare function formatToolName(name: string): string;
|
|
56
87
|
|
|
57
|
-
declare function ChatWidget({ agentId, workspaceId, agentName, displayName, profilePicture, userMessageColor, initialMessages, suggestedMessages, messagePlaceholder, watermark, watermarkLogoUrl, footer, theme, showThinking, source, className, onClose, onReset, }: ChatWidgetProps): react_jsx_runtime.JSX.Element;
|
|
88
|
+
declare function ChatWidget({ agentId, workspaceId, agentName, displayName, profilePicture, userMessageColor, initialMessages, suggestedMessages, messagePlaceholder, watermark, watermarkLogoUrl, footer, theme, showThinking, regenerateMessage, persist, onNavigate, hideCloseButton, source, userContext, playgroundOverrides, className, onClose, onReset, }: ChatWidgetProps): react_jsx_runtime.JSX.Element;
|
|
58
89
|
|
|
59
90
|
interface UseChatOptions {
|
|
60
91
|
agentId: string;
|
|
61
92
|
workspaceId?: string;
|
|
62
93
|
source?: string;
|
|
63
94
|
userContext?: UserContext;
|
|
95
|
+
persist?: boolean;
|
|
96
|
+
onNavigate?: (path: string) => void;
|
|
97
|
+
playgroundOverrides?: {
|
|
98
|
+
ragIds?: string[];
|
|
99
|
+
mcpIds?: string[];
|
|
100
|
+
actionIds?: string[];
|
|
101
|
+
};
|
|
64
102
|
}
|
|
65
103
|
interface UseChatReturn {
|
|
66
104
|
messages: Message[];
|
|
@@ -69,8 +107,9 @@ interface UseChatReturn {
|
|
|
69
107
|
streaming: boolean;
|
|
70
108
|
threadId: string;
|
|
71
109
|
send: (text?: string) => Promise<void>;
|
|
110
|
+
regenerate: () => Promise<void>;
|
|
72
111
|
reset: () => void;
|
|
73
112
|
}
|
|
74
|
-
declare function useChat({ agentId, workspaceId, source, userContext, }: UseChatOptions): UseChatReturn;
|
|
113
|
+
declare function useChat({ agentId, workspaceId, source, userContext, persist, onNavigate, playgroundOverrides, }: UseChatOptions): UseChatReturn;
|
|
75
114
|
|
|
76
115
|
export { ChatWidget, type ChatWidgetConfig, type ChatWidgetProps, type Message, type MessagePart, type ToolPart, type UserContext, formatToolName, getContrastColor, useChat };
|
package/dist/index.js
CHANGED
|
@@ -77,20 +77,59 @@ function useChat({
|
|
|
77
77
|
agentId,
|
|
78
78
|
workspaceId = "",
|
|
79
79
|
source = "playground",
|
|
80
|
-
userContext
|
|
80
|
+
userContext,
|
|
81
|
+
persist = false,
|
|
82
|
+
onNavigate,
|
|
83
|
+
playgroundOverrides
|
|
81
84
|
}) {
|
|
82
|
-
const
|
|
85
|
+
const persistKey = persist ? `wallavi_${agentId}` : null;
|
|
86
|
+
const [messages, setMessages] = react.useState(() => {
|
|
87
|
+
if (!persistKey || typeof window === "undefined") return [];
|
|
88
|
+
try {
|
|
89
|
+
const saved = sessionStorage.getItem(`${persistKey}_msgs`);
|
|
90
|
+
return saved ? JSON.parse(saved) : [];
|
|
91
|
+
} catch {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
});
|
|
83
95
|
const [input, setInput] = react.useState("");
|
|
84
96
|
const [streaming, setStreaming] = react.useState(false);
|
|
85
|
-
const [threadId, setThreadId] = react.useState(() =>
|
|
97
|
+
const [threadId, setThreadId] = react.useState(() => {
|
|
98
|
+
if (persistKey && typeof window !== "undefined") {
|
|
99
|
+
const saved = sessionStorage.getItem(`${persistKey}_tid`);
|
|
100
|
+
if (saved) return saved;
|
|
101
|
+
}
|
|
102
|
+
return crypto.randomUUID();
|
|
103
|
+
});
|
|
86
104
|
const streamingMsgIdRef = react.useRef(null);
|
|
105
|
+
react.useEffect(() => {
|
|
106
|
+
if (!persistKey) return;
|
|
107
|
+
try {
|
|
108
|
+
sessionStorage.setItem(`${persistKey}_msgs`, JSON.stringify(messages));
|
|
109
|
+
} catch {
|
|
110
|
+
}
|
|
111
|
+
}, [persistKey, messages]);
|
|
112
|
+
react.useEffect(() => {
|
|
113
|
+
if (!persistKey) return;
|
|
114
|
+
try {
|
|
115
|
+
sessionStorage.setItem(`${persistKey}_tid`, threadId);
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
}, [persistKey, threadId]);
|
|
87
119
|
const reset = react.useCallback(() => {
|
|
88
120
|
setMessages([]);
|
|
89
121
|
setInput("");
|
|
90
122
|
setStreaming(false);
|
|
91
123
|
setThreadId(crypto.randomUUID());
|
|
92
124
|
streamingMsgIdRef.current = null;
|
|
93
|
-
|
|
125
|
+
if (persistKey) {
|
|
126
|
+
try {
|
|
127
|
+
sessionStorage.removeItem(`${persistKey}_msgs`);
|
|
128
|
+
sessionStorage.removeItem(`${persistKey}_tid`);
|
|
129
|
+
} catch {
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}, [persistKey]);
|
|
94
133
|
const send = react.useCallback(
|
|
95
134
|
async (text) => {
|
|
96
135
|
const userInput = (text ?? input).trim();
|
|
@@ -121,11 +160,16 @@ function useChat({
|
|
|
121
160
|
agentId,
|
|
122
161
|
workspaceId,
|
|
123
162
|
source,
|
|
163
|
+
...playgroundOverrides ? { playgroundOverrides } : {},
|
|
124
164
|
...userContext?.userName ? { userName: userContext.userName } : {},
|
|
125
165
|
...userContext?.userEmail ? { userEmail: userContext.userEmail } : {},
|
|
126
166
|
userMetadata: {
|
|
127
167
|
...userContext?.metadata ?? {},
|
|
128
|
-
...userContext?.
|
|
168
|
+
...userContext?.pageContext ? { pageContext: userContext.pageContext } : {},
|
|
169
|
+
headers: {
|
|
170
|
+
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
171
|
+
...userContext?.headers ?? {}
|
|
172
|
+
}
|
|
129
173
|
}
|
|
130
174
|
})
|
|
131
175
|
});
|
|
@@ -135,6 +179,10 @@ function useChat({
|
|
|
135
179
|
}
|
|
136
180
|
if (!res.body) throw new Error("No stream body");
|
|
137
181
|
const applyEvent = (proto) => {
|
|
182
|
+
if (proto.type === "navigate") {
|
|
183
|
+
if (proto.path.startsWith("/")) onNavigate?.(proto.path);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
138
186
|
setMessages((prev) => {
|
|
139
187
|
const idx = prev.findIndex((m) => m.id === assistantMsgId);
|
|
140
188
|
if (idx === -1) return prev;
|
|
@@ -229,7 +277,20 @@ function useChat({
|
|
|
229
277
|
},
|
|
230
278
|
[input, streaming, agentId, workspaceId, source, threadId, userContext]
|
|
231
279
|
);
|
|
232
|
-
|
|
280
|
+
const regenerate = react.useCallback(async () => {
|
|
281
|
+
if (streaming) return;
|
|
282
|
+
const lastUser = [...messages].reverse().find((m) => m.role === "user");
|
|
283
|
+
const lastText = lastUser?.parts.find((p) => p.type === "text")?.text;
|
|
284
|
+
if (!lastText) return;
|
|
285
|
+
setMessages((prev) => {
|
|
286
|
+
const copy = [...prev];
|
|
287
|
+
if (copy.at(-1)?.role === "assistant") copy.pop();
|
|
288
|
+
if (copy.at(-1)?.role === "user") copy.pop();
|
|
289
|
+
return copy;
|
|
290
|
+
});
|
|
291
|
+
await send(lastText);
|
|
292
|
+
}, [streaming, messages, send]);
|
|
293
|
+
return { messages, input, setInput, streaming, threadId, send, regenerate, reset };
|
|
233
294
|
}
|
|
234
295
|
var Avatar = ({ style, ...p }) => /* @__PURE__ */ jsxRuntime.jsx(AvatarPrimitive__namespace.Root, { style: { position: "relative", display: "flex", flexShrink: 0, overflow: "hidden", borderRadius: "9999px", ...style }, ...p });
|
|
235
296
|
var AvatarImage = ({ style, ...p }) => /* @__PURE__ */ jsxRuntime.jsx(AvatarPrimitive__namespace.Image, { style: { position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", ...style }, ...p });
|
|
@@ -476,7 +537,9 @@ function ChatInput({
|
|
|
476
537
|
onSend,
|
|
477
538
|
streaming,
|
|
478
539
|
placeholder,
|
|
479
|
-
accentColor
|
|
540
|
+
accentColor,
|
|
541
|
+
canRegenerate = false,
|
|
542
|
+
onRegenerate
|
|
480
543
|
}) {
|
|
481
544
|
const textareaRef = react.useRef(null);
|
|
482
545
|
react.useEffect(() => {
|
|
@@ -491,39 +554,54 @@ function ChatInput({
|
|
|
491
554
|
}
|
|
492
555
|
};
|
|
493
556
|
const hasText = input.trim().length > 0;
|
|
494
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
495
|
-
/* @__PURE__ */ jsxRuntime.
|
|
496
|
-
"
|
|
557
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "shrink-0 border-t bg-background px-3 py-2.5", children: [
|
|
558
|
+
canRegenerate && onRegenerate && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
559
|
+
"button",
|
|
497
560
|
{
|
|
498
|
-
|
|
499
|
-
rows: 1,
|
|
500
|
-
className: "flex-1 resize-none bg-transparent text-sm outline-none placeholder:text-muted-foreground/50 leading-relaxed max-h-32 overflow-y-auto py-0.5",
|
|
501
|
-
placeholder: placeholder ?? "Send a message\u2026",
|
|
502
|
-
value: input,
|
|
503
|
-
onChange: (e) => {
|
|
504
|
-
setInput(e.target.value);
|
|
505
|
-
e.target.style.height = "auto";
|
|
506
|
-
e.target.style.height = `${Math.min(e.target.scrollHeight, 128)}px`;
|
|
507
|
-
},
|
|
508
|
-
onKeyDown: handleKeyDown,
|
|
561
|
+
onClick: onRegenerate,
|
|
509
562
|
disabled: streaming,
|
|
510
|
-
|
|
563
|
+
className: "flex items-center gap-1 text-[11px] text-muted-foreground hover:text-foreground transition-colors mb-2 disabled:opacity-40",
|
|
564
|
+
children: [
|
|
565
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, { className: "h-3 w-3" }),
|
|
566
|
+
"Regenerate response"
|
|
567
|
+
]
|
|
511
568
|
}
|
|
512
569
|
),
|
|
513
|
-
/* @__PURE__ */ jsxRuntime.
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
570
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-end gap-2 rounded-2xl border bg-background px-3 py-2 shadow-sm focus-within:ring-1 focus-within:ring-ring/40 transition-shadow", children: [
|
|
571
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
572
|
+
"textarea",
|
|
573
|
+
{
|
|
574
|
+
id: "wallavi-chat-input",
|
|
575
|
+
ref: textareaRef,
|
|
576
|
+
rows: 1,
|
|
577
|
+
className: "flex-1 resize-none bg-transparent text-sm outline-none placeholder:text-muted-foreground/50 leading-relaxed max-h-32 overflow-y-auto py-0.5",
|
|
578
|
+
placeholder: placeholder ?? "Send a message\u2026",
|
|
579
|
+
value: input,
|
|
580
|
+
onChange: (e) => {
|
|
581
|
+
setInput(e.target.value);
|
|
582
|
+
e.target.style.height = "auto";
|
|
583
|
+
e.target.style.height = `${Math.min(e.target.scrollHeight, 128)}px`;
|
|
584
|
+
},
|
|
585
|
+
onKeyDown: handleKeyDown,
|
|
586
|
+
disabled: streaming,
|
|
587
|
+
autoFocus: true
|
|
588
|
+
}
|
|
589
|
+
),
|
|
590
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
591
|
+
"button",
|
|
592
|
+
{
|
|
593
|
+
onClick: onSend,
|
|
594
|
+
disabled: streaming || !hasText,
|
|
595
|
+
className: cn2(
|
|
596
|
+
"h-7 w-7 shrink-0 rounded-xl flex items-center justify-center transition-all duration-200",
|
|
597
|
+
hasText || streaming ? "opacity-100 shadow-sm" : "opacity-30"
|
|
598
|
+
),
|
|
599
|
+
style: hasText || streaming ? { backgroundColor: accentColor, color: getContrastColor(accentColor) } : { backgroundColor: "transparent", color: "currentColor" },
|
|
600
|
+
children: streaming ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUp, { className: "h-3.5 w-3.5" })
|
|
601
|
+
}
|
|
602
|
+
)
|
|
603
|
+
] })
|
|
604
|
+
] });
|
|
527
605
|
}
|
|
528
606
|
var cn3 = (...inputs) => tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
529
607
|
function ChatWidget({
|
|
@@ -537,16 +615,23 @@ function ChatWidget({
|
|
|
537
615
|
suggestedMessages = [],
|
|
538
616
|
messagePlaceholder,
|
|
539
617
|
watermark = true,
|
|
540
|
-
watermarkLogoUrl = "https://wallavi.com/wallavi.svg",
|
|
618
|
+
watermarkLogoUrl = "https://app.wallavi.com/wallavi.svg",
|
|
541
619
|
footer,
|
|
542
620
|
theme,
|
|
543
621
|
showThinking = false,
|
|
622
|
+
regenerateMessage = false,
|
|
623
|
+
persist = false,
|
|
624
|
+
onNavigate,
|
|
625
|
+
hideCloseButton = false,
|
|
544
626
|
source = "playground",
|
|
627
|
+
userContext,
|
|
628
|
+
playgroundOverrides,
|
|
545
629
|
className,
|
|
546
630
|
onClose,
|
|
547
631
|
onReset
|
|
548
632
|
}) {
|
|
549
|
-
const chat = useChat({ agentId, workspaceId, source });
|
|
633
|
+
const chat = useChat({ agentId, workspaceId, source, userContext, persist, onNavigate, playgroundOverrides });
|
|
634
|
+
const canRegenerate = regenerateMessage && chat.messages.length > 0 && chat.messages.at(-1)?.role === "assistant" && !chat.streaming;
|
|
550
635
|
const title = displayName || agentName;
|
|
551
636
|
const headerBg = userMessageColor;
|
|
552
637
|
const headerText = getContrastColor(userMessageColor);
|
|
@@ -558,7 +643,7 @@ function ChatWidget({
|
|
|
558
643
|
"div",
|
|
559
644
|
{
|
|
560
645
|
className: cn3(
|
|
561
|
-
"flex flex-col overflow-hidden rounded-2xl border shadow-xl bg-background",
|
|
646
|
+
"flex flex-col overflow-hidden rounded-2xl border shadow-xl bg-background h-full",
|
|
562
647
|
className
|
|
563
648
|
),
|
|
564
649
|
style: { colorScheme: theme },
|
|
@@ -571,7 +656,7 @@ function ChatWidget({
|
|
|
571
656
|
headerBg,
|
|
572
657
|
headerText,
|
|
573
658
|
onReset: handleReset,
|
|
574
|
-
onClose
|
|
659
|
+
onClose: hideCloseButton ? void 0 : onClose
|
|
575
660
|
}
|
|
576
661
|
),
|
|
577
662
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -596,7 +681,9 @@ function ChatWidget({
|
|
|
596
681
|
onSend: () => chat.send(),
|
|
597
682
|
streaming: chat.streaming,
|
|
598
683
|
placeholder: messagePlaceholder,
|
|
599
|
-
accentColor: userMessageColor
|
|
684
|
+
accentColor: userMessageColor,
|
|
685
|
+
canRegenerate: !!canRegenerate,
|
|
686
|
+
onRegenerate: () => void chat.regenerate()
|
|
600
687
|
}
|
|
601
688
|
),
|
|
602
689
|
watermark && /* @__PURE__ */ jsxRuntime.jsxs("footer", { className: "shrink-0 flex items-center justify-center gap-1.5 bg-muted/50 py-1.5 border-t", children: [
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { clsx } from 'clsx';
|
|
2
2
|
import { twMerge } from 'tailwind-merge';
|
|
3
|
-
import { useState, useRef,
|
|
3
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
4
4
|
import { RotateCcw, X, Loader2, ArrowUp, Zap, ChevronDown, CheckCircle2, AlertCircle } from 'lucide-react';
|
|
5
5
|
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
|
6
6
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
@@ -51,20 +51,59 @@ function useChat({
|
|
|
51
51
|
agentId,
|
|
52
52
|
workspaceId = "",
|
|
53
53
|
source = "playground",
|
|
54
|
-
userContext
|
|
54
|
+
userContext,
|
|
55
|
+
persist = false,
|
|
56
|
+
onNavigate,
|
|
57
|
+
playgroundOverrides
|
|
55
58
|
}) {
|
|
56
|
-
const
|
|
59
|
+
const persistKey = persist ? `wallavi_${agentId}` : null;
|
|
60
|
+
const [messages, setMessages] = useState(() => {
|
|
61
|
+
if (!persistKey || typeof window === "undefined") return [];
|
|
62
|
+
try {
|
|
63
|
+
const saved = sessionStorage.getItem(`${persistKey}_msgs`);
|
|
64
|
+
return saved ? JSON.parse(saved) : [];
|
|
65
|
+
} catch {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
});
|
|
57
69
|
const [input, setInput] = useState("");
|
|
58
70
|
const [streaming, setStreaming] = useState(false);
|
|
59
|
-
const [threadId, setThreadId] = useState(() =>
|
|
71
|
+
const [threadId, setThreadId] = useState(() => {
|
|
72
|
+
if (persistKey && typeof window !== "undefined") {
|
|
73
|
+
const saved = sessionStorage.getItem(`${persistKey}_tid`);
|
|
74
|
+
if (saved) return saved;
|
|
75
|
+
}
|
|
76
|
+
return crypto.randomUUID();
|
|
77
|
+
});
|
|
60
78
|
const streamingMsgIdRef = useRef(null);
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (!persistKey) return;
|
|
81
|
+
try {
|
|
82
|
+
sessionStorage.setItem(`${persistKey}_msgs`, JSON.stringify(messages));
|
|
83
|
+
} catch {
|
|
84
|
+
}
|
|
85
|
+
}, [persistKey, messages]);
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (!persistKey) return;
|
|
88
|
+
try {
|
|
89
|
+
sessionStorage.setItem(`${persistKey}_tid`, threadId);
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
}, [persistKey, threadId]);
|
|
61
93
|
const reset = useCallback(() => {
|
|
62
94
|
setMessages([]);
|
|
63
95
|
setInput("");
|
|
64
96
|
setStreaming(false);
|
|
65
97
|
setThreadId(crypto.randomUUID());
|
|
66
98
|
streamingMsgIdRef.current = null;
|
|
67
|
-
|
|
99
|
+
if (persistKey) {
|
|
100
|
+
try {
|
|
101
|
+
sessionStorage.removeItem(`${persistKey}_msgs`);
|
|
102
|
+
sessionStorage.removeItem(`${persistKey}_tid`);
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}, [persistKey]);
|
|
68
107
|
const send = useCallback(
|
|
69
108
|
async (text) => {
|
|
70
109
|
const userInput = (text ?? input).trim();
|
|
@@ -95,11 +134,16 @@ function useChat({
|
|
|
95
134
|
agentId,
|
|
96
135
|
workspaceId,
|
|
97
136
|
source,
|
|
137
|
+
...playgroundOverrides ? { playgroundOverrides } : {},
|
|
98
138
|
...userContext?.userName ? { userName: userContext.userName } : {},
|
|
99
139
|
...userContext?.userEmail ? { userEmail: userContext.userEmail } : {},
|
|
100
140
|
userMetadata: {
|
|
101
141
|
...userContext?.metadata ?? {},
|
|
102
|
-
...userContext?.
|
|
142
|
+
...userContext?.pageContext ? { pageContext: userContext.pageContext } : {},
|
|
143
|
+
headers: {
|
|
144
|
+
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
145
|
+
...userContext?.headers ?? {}
|
|
146
|
+
}
|
|
103
147
|
}
|
|
104
148
|
})
|
|
105
149
|
});
|
|
@@ -109,6 +153,10 @@ function useChat({
|
|
|
109
153
|
}
|
|
110
154
|
if (!res.body) throw new Error("No stream body");
|
|
111
155
|
const applyEvent = (proto) => {
|
|
156
|
+
if (proto.type === "navigate") {
|
|
157
|
+
if (proto.path.startsWith("/")) onNavigate?.(proto.path);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
112
160
|
setMessages((prev) => {
|
|
113
161
|
const idx = prev.findIndex((m) => m.id === assistantMsgId);
|
|
114
162
|
if (idx === -1) return prev;
|
|
@@ -203,7 +251,20 @@ function useChat({
|
|
|
203
251
|
},
|
|
204
252
|
[input, streaming, agentId, workspaceId, source, threadId, userContext]
|
|
205
253
|
);
|
|
206
|
-
|
|
254
|
+
const regenerate = useCallback(async () => {
|
|
255
|
+
if (streaming) return;
|
|
256
|
+
const lastUser = [...messages].reverse().find((m) => m.role === "user");
|
|
257
|
+
const lastText = lastUser?.parts.find((p) => p.type === "text")?.text;
|
|
258
|
+
if (!lastText) return;
|
|
259
|
+
setMessages((prev) => {
|
|
260
|
+
const copy = [...prev];
|
|
261
|
+
if (copy.at(-1)?.role === "assistant") copy.pop();
|
|
262
|
+
if (copy.at(-1)?.role === "user") copy.pop();
|
|
263
|
+
return copy;
|
|
264
|
+
});
|
|
265
|
+
await send(lastText);
|
|
266
|
+
}, [streaming, messages, send]);
|
|
267
|
+
return { messages, input, setInput, streaming, threadId, send, regenerate, reset };
|
|
207
268
|
}
|
|
208
269
|
var Avatar = ({ style, ...p }) => /* @__PURE__ */ jsx(AvatarPrimitive.Root, { style: { position: "relative", display: "flex", flexShrink: 0, overflow: "hidden", borderRadius: "9999px", ...style }, ...p });
|
|
209
270
|
var AvatarImage = ({ style, ...p }) => /* @__PURE__ */ jsx(AvatarPrimitive.Image, { style: { position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", ...style }, ...p });
|
|
@@ -450,7 +511,9 @@ function ChatInput({
|
|
|
450
511
|
onSend,
|
|
451
512
|
streaming,
|
|
452
513
|
placeholder,
|
|
453
|
-
accentColor
|
|
514
|
+
accentColor,
|
|
515
|
+
canRegenerate = false,
|
|
516
|
+
onRegenerate
|
|
454
517
|
}) {
|
|
455
518
|
const textareaRef = useRef(null);
|
|
456
519
|
useEffect(() => {
|
|
@@ -465,39 +528,54 @@ function ChatInput({
|
|
|
465
528
|
}
|
|
466
529
|
};
|
|
467
530
|
const hasText = input.trim().length > 0;
|
|
468
|
-
return /* @__PURE__ */
|
|
469
|
-
/* @__PURE__ */
|
|
470
|
-
"
|
|
531
|
+
return /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-t bg-background px-3 py-2.5", children: [
|
|
532
|
+
canRegenerate && onRegenerate && /* @__PURE__ */ jsxs(
|
|
533
|
+
"button",
|
|
471
534
|
{
|
|
472
|
-
|
|
473
|
-
rows: 1,
|
|
474
|
-
className: "flex-1 resize-none bg-transparent text-sm outline-none placeholder:text-muted-foreground/50 leading-relaxed max-h-32 overflow-y-auto py-0.5",
|
|
475
|
-
placeholder: placeholder ?? "Send a message\u2026",
|
|
476
|
-
value: input,
|
|
477
|
-
onChange: (e) => {
|
|
478
|
-
setInput(e.target.value);
|
|
479
|
-
e.target.style.height = "auto";
|
|
480
|
-
e.target.style.height = `${Math.min(e.target.scrollHeight, 128)}px`;
|
|
481
|
-
},
|
|
482
|
-
onKeyDown: handleKeyDown,
|
|
535
|
+
onClick: onRegenerate,
|
|
483
536
|
disabled: streaming,
|
|
484
|
-
|
|
537
|
+
className: "flex items-center gap-1 text-[11px] text-muted-foreground hover:text-foreground transition-colors mb-2 disabled:opacity-40",
|
|
538
|
+
children: [
|
|
539
|
+
/* @__PURE__ */ jsx(RotateCcw, { className: "h-3 w-3" }),
|
|
540
|
+
"Regenerate response"
|
|
541
|
+
]
|
|
485
542
|
}
|
|
486
543
|
),
|
|
487
|
-
/* @__PURE__ */
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
544
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-end gap-2 rounded-2xl border bg-background px-3 py-2 shadow-sm focus-within:ring-1 focus-within:ring-ring/40 transition-shadow", children: [
|
|
545
|
+
/* @__PURE__ */ jsx(
|
|
546
|
+
"textarea",
|
|
547
|
+
{
|
|
548
|
+
id: "wallavi-chat-input",
|
|
549
|
+
ref: textareaRef,
|
|
550
|
+
rows: 1,
|
|
551
|
+
className: "flex-1 resize-none bg-transparent text-sm outline-none placeholder:text-muted-foreground/50 leading-relaxed max-h-32 overflow-y-auto py-0.5",
|
|
552
|
+
placeholder: placeholder ?? "Send a message\u2026",
|
|
553
|
+
value: input,
|
|
554
|
+
onChange: (e) => {
|
|
555
|
+
setInput(e.target.value);
|
|
556
|
+
e.target.style.height = "auto";
|
|
557
|
+
e.target.style.height = `${Math.min(e.target.scrollHeight, 128)}px`;
|
|
558
|
+
},
|
|
559
|
+
onKeyDown: handleKeyDown,
|
|
560
|
+
disabled: streaming,
|
|
561
|
+
autoFocus: true
|
|
562
|
+
}
|
|
563
|
+
),
|
|
564
|
+
/* @__PURE__ */ jsx(
|
|
565
|
+
"button",
|
|
566
|
+
{
|
|
567
|
+
onClick: onSend,
|
|
568
|
+
disabled: streaming || !hasText,
|
|
569
|
+
className: cn2(
|
|
570
|
+
"h-7 w-7 shrink-0 rounded-xl flex items-center justify-center transition-all duration-200",
|
|
571
|
+
hasText || streaming ? "opacity-100 shadow-sm" : "opacity-30"
|
|
572
|
+
),
|
|
573
|
+
style: hasText || streaming ? { backgroundColor: accentColor, color: getContrastColor(accentColor) } : { backgroundColor: "transparent", color: "currentColor" },
|
|
574
|
+
children: streaming ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(ArrowUp, { className: "h-3.5 w-3.5" })
|
|
575
|
+
}
|
|
576
|
+
)
|
|
577
|
+
] })
|
|
578
|
+
] });
|
|
501
579
|
}
|
|
502
580
|
var cn3 = (...inputs) => twMerge(clsx(inputs));
|
|
503
581
|
function ChatWidget({
|
|
@@ -511,16 +589,23 @@ function ChatWidget({
|
|
|
511
589
|
suggestedMessages = [],
|
|
512
590
|
messagePlaceholder,
|
|
513
591
|
watermark = true,
|
|
514
|
-
watermarkLogoUrl = "https://wallavi.com/wallavi.svg",
|
|
592
|
+
watermarkLogoUrl = "https://app.wallavi.com/wallavi.svg",
|
|
515
593
|
footer,
|
|
516
594
|
theme,
|
|
517
595
|
showThinking = false,
|
|
596
|
+
regenerateMessage = false,
|
|
597
|
+
persist = false,
|
|
598
|
+
onNavigate,
|
|
599
|
+
hideCloseButton = false,
|
|
518
600
|
source = "playground",
|
|
601
|
+
userContext,
|
|
602
|
+
playgroundOverrides,
|
|
519
603
|
className,
|
|
520
604
|
onClose,
|
|
521
605
|
onReset
|
|
522
606
|
}) {
|
|
523
|
-
const chat = useChat({ agentId, workspaceId, source });
|
|
607
|
+
const chat = useChat({ agentId, workspaceId, source, userContext, persist, onNavigate, playgroundOverrides });
|
|
608
|
+
const canRegenerate = regenerateMessage && chat.messages.length > 0 && chat.messages.at(-1)?.role === "assistant" && !chat.streaming;
|
|
524
609
|
const title = displayName || agentName;
|
|
525
610
|
const headerBg = userMessageColor;
|
|
526
611
|
const headerText = getContrastColor(userMessageColor);
|
|
@@ -532,7 +617,7 @@ function ChatWidget({
|
|
|
532
617
|
"div",
|
|
533
618
|
{
|
|
534
619
|
className: cn3(
|
|
535
|
-
"flex flex-col overflow-hidden rounded-2xl border shadow-xl bg-background",
|
|
620
|
+
"flex flex-col overflow-hidden rounded-2xl border shadow-xl bg-background h-full",
|
|
536
621
|
className
|
|
537
622
|
),
|
|
538
623
|
style: { colorScheme: theme },
|
|
@@ -545,7 +630,7 @@ function ChatWidget({
|
|
|
545
630
|
headerBg,
|
|
546
631
|
headerText,
|
|
547
632
|
onReset: handleReset,
|
|
548
|
-
onClose
|
|
633
|
+
onClose: hideCloseButton ? void 0 : onClose
|
|
549
634
|
}
|
|
550
635
|
),
|
|
551
636
|
/* @__PURE__ */ jsx(
|
|
@@ -570,7 +655,9 @@ function ChatWidget({
|
|
|
570
655
|
onSend: () => chat.send(),
|
|
571
656
|
streaming: chat.streaming,
|
|
572
657
|
placeholder: messagePlaceholder,
|
|
573
|
-
accentColor: userMessageColor
|
|
658
|
+
accentColor: userMessageColor,
|
|
659
|
+
canRegenerate: !!canRegenerate,
|
|
660
|
+
onRegenerate: () => void chat.regenerate()
|
|
574
661
|
}
|
|
575
662
|
),
|
|
576
663
|
watermark && /* @__PURE__ */ jsxs("footer", { className: "shrink-0 flex items-center justify-center gap-1.5 bg-muted/50 py-1.5 border-t", children: [
|
package/package.json
CHANGED
|
@@ -11,42 +11,32 @@
|
|
|
11
11
|
"@types/node": "^22.10.7",
|
|
12
12
|
"@types/react": "^19.1.0",
|
|
13
13
|
"@types/react-dom": "^19.1.0",
|
|
14
|
-
"@wallavi/typescript-config": "workspace:*",
|
|
15
14
|
"tsup": "^8.3.5",
|
|
16
|
-
"typescript": "^5.7.3"
|
|
15
|
+
"typescript": "^5.7.3",
|
|
16
|
+
"@wallavi/typescript-config": "0.0.1"
|
|
17
17
|
},
|
|
18
18
|
"exports": {
|
|
19
19
|
".": {
|
|
20
|
-
"
|
|
21
|
-
"
|
|
20
|
+
"import": "./dist/index.mjs",
|
|
21
|
+
"require": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts"
|
|
22
23
|
}
|
|
23
24
|
},
|
|
24
25
|
"files": [
|
|
25
26
|
"dist"
|
|
26
27
|
],
|
|
27
|
-
"main": "./
|
|
28
|
+
"main": "./dist/index.js",
|
|
28
29
|
"name": "@wallavi/widget",
|
|
29
30
|
"peerDependencies": {
|
|
30
31
|
"react": "^18.0.0 || ^19.0.0",
|
|
31
32
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
32
33
|
},
|
|
33
34
|
"private": false,
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
".": {
|
|
37
|
-
"import": "./dist/index.mjs",
|
|
38
|
-
"require": "./dist/index.js",
|
|
39
|
-
"types": "./dist/index.d.ts"
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
"main": "./dist/index.js",
|
|
43
|
-
"module": "./dist/index.mjs",
|
|
44
|
-
"types": "./dist/index.d.ts"
|
|
45
|
-
},
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"version": "1.1.1",
|
|
46
37
|
"scripts": {
|
|
47
38
|
"build": "tsup",
|
|
48
39
|
"typecheck": "tsc --noEmit"
|
|
49
40
|
},
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
}
|
|
41
|
+
"module": "./dist/index.mjs"
|
|
42
|
+
}
|