@wallavi/widget 1.0.1 → 1.1.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.d.mts +35 -2
- package/dist/index.d.ts +35 -2
- package/dist/index.js +124 -38
- package/dist/index.mjs +125 -39
- package/package.json +1 -1
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,21 @@ 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;
|
|
46
65
|
source?: string;
|
|
47
66
|
userContext?: UserContext;
|
|
67
|
+
/** Per-session tool overrides used by Playground (not persisted) */
|
|
68
|
+
playgroundOverrides?: {
|
|
69
|
+
ragIds?: string[];
|
|
70
|
+
mcpIds?: string[];
|
|
71
|
+
actionIds?: string[];
|
|
72
|
+
};
|
|
48
73
|
}
|
|
49
74
|
interface ChatWidgetProps extends ChatWidgetConfig {
|
|
50
75
|
className?: string;
|
|
@@ -54,13 +79,20 @@ interface ChatWidgetProps extends ChatWidgetConfig {
|
|
|
54
79
|
declare function getContrastColor(hex: string): string;
|
|
55
80
|
declare function formatToolName(name: string): string;
|
|
56
81
|
|
|
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;
|
|
82
|
+
declare function ChatWidget({ agentId, workspaceId, agentName, displayName, profilePicture, userMessageColor, initialMessages, suggestedMessages, messagePlaceholder, watermark, watermarkLogoUrl, footer, theme, showThinking, regenerateMessage, persist, onNavigate, source, userContext, playgroundOverrides, className, onClose, onReset, }: ChatWidgetProps): react_jsx_runtime.JSX.Element;
|
|
58
83
|
|
|
59
84
|
interface UseChatOptions {
|
|
60
85
|
agentId: string;
|
|
61
86
|
workspaceId?: string;
|
|
62
87
|
source?: string;
|
|
63
88
|
userContext?: UserContext;
|
|
89
|
+
persist?: boolean;
|
|
90
|
+
onNavigate?: (path: string) => void;
|
|
91
|
+
playgroundOverrides?: {
|
|
92
|
+
ragIds?: string[];
|
|
93
|
+
mcpIds?: string[];
|
|
94
|
+
actionIds?: string[];
|
|
95
|
+
};
|
|
64
96
|
}
|
|
65
97
|
interface UseChatReturn {
|
|
66
98
|
messages: Message[];
|
|
@@ -69,8 +101,9 @@ interface UseChatReturn {
|
|
|
69
101
|
streaming: boolean;
|
|
70
102
|
threadId: string;
|
|
71
103
|
send: (text?: string) => Promise<void>;
|
|
104
|
+
regenerate: () => Promise<void>;
|
|
72
105
|
reset: () => void;
|
|
73
106
|
}
|
|
74
|
-
declare function useChat({ agentId, workspaceId, source, userContext, }: UseChatOptions): UseChatReturn;
|
|
107
|
+
declare function useChat({ agentId, workspaceId, source, userContext, persist, onNavigate, playgroundOverrides, }: UseChatOptions): UseChatReturn;
|
|
75
108
|
|
|
76
109
|
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,21 @@ 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;
|
|
46
65
|
source?: string;
|
|
47
66
|
userContext?: UserContext;
|
|
67
|
+
/** Per-session tool overrides used by Playground (not persisted) */
|
|
68
|
+
playgroundOverrides?: {
|
|
69
|
+
ragIds?: string[];
|
|
70
|
+
mcpIds?: string[];
|
|
71
|
+
actionIds?: string[];
|
|
72
|
+
};
|
|
48
73
|
}
|
|
49
74
|
interface ChatWidgetProps extends ChatWidgetConfig {
|
|
50
75
|
className?: string;
|
|
@@ -54,13 +79,20 @@ interface ChatWidgetProps extends ChatWidgetConfig {
|
|
|
54
79
|
declare function getContrastColor(hex: string): string;
|
|
55
80
|
declare function formatToolName(name: string): string;
|
|
56
81
|
|
|
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;
|
|
82
|
+
declare function ChatWidget({ agentId, workspaceId, agentName, displayName, profilePicture, userMessageColor, initialMessages, suggestedMessages, messagePlaceholder, watermark, watermarkLogoUrl, footer, theme, showThinking, regenerateMessage, persist, onNavigate, source, userContext, playgroundOverrides, className, onClose, onReset, }: ChatWidgetProps): react_jsx_runtime.JSX.Element;
|
|
58
83
|
|
|
59
84
|
interface UseChatOptions {
|
|
60
85
|
agentId: string;
|
|
61
86
|
workspaceId?: string;
|
|
62
87
|
source?: string;
|
|
63
88
|
userContext?: UserContext;
|
|
89
|
+
persist?: boolean;
|
|
90
|
+
onNavigate?: (path: string) => void;
|
|
91
|
+
playgroundOverrides?: {
|
|
92
|
+
ragIds?: string[];
|
|
93
|
+
mcpIds?: string[];
|
|
94
|
+
actionIds?: string[];
|
|
95
|
+
};
|
|
64
96
|
}
|
|
65
97
|
interface UseChatReturn {
|
|
66
98
|
messages: Message[];
|
|
@@ -69,8 +101,9 @@ interface UseChatReturn {
|
|
|
69
101
|
streaming: boolean;
|
|
70
102
|
threadId: string;
|
|
71
103
|
send: (text?: string) => Promise<void>;
|
|
104
|
+
regenerate: () => Promise<void>;
|
|
72
105
|
reset: () => void;
|
|
73
106
|
}
|
|
74
|
-
declare function useChat({ agentId, workspaceId, source, userContext, }: UseChatOptions): UseChatReturn;
|
|
107
|
+
declare function useChat({ agentId, workspaceId, source, userContext, persist, onNavigate, playgroundOverrides, }: UseChatOptions): UseChatReturn;
|
|
75
108
|
|
|
76
109
|
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({
|
|
@@ -541,12 +619,18 @@ function ChatWidget({
|
|
|
541
619
|
footer,
|
|
542
620
|
theme,
|
|
543
621
|
showThinking = false,
|
|
622
|
+
regenerateMessage = false,
|
|
623
|
+
persist = false,
|
|
624
|
+
onNavigate,
|
|
544
625
|
source = "playground",
|
|
626
|
+
userContext,
|
|
627
|
+
playgroundOverrides,
|
|
545
628
|
className,
|
|
546
629
|
onClose,
|
|
547
630
|
onReset
|
|
548
631
|
}) {
|
|
549
|
-
const chat = useChat({ agentId, workspaceId, source });
|
|
632
|
+
const chat = useChat({ agentId, workspaceId, source, userContext, persist, onNavigate, playgroundOverrides });
|
|
633
|
+
const canRegenerate = regenerateMessage && chat.messages.length > 0 && chat.messages.at(-1)?.role === "assistant" && !chat.streaming;
|
|
550
634
|
const title = displayName || agentName;
|
|
551
635
|
const headerBg = userMessageColor;
|
|
552
636
|
const headerText = getContrastColor(userMessageColor);
|
|
@@ -596,7 +680,9 @@ function ChatWidget({
|
|
|
596
680
|
onSend: () => chat.send(),
|
|
597
681
|
streaming: chat.streaming,
|
|
598
682
|
placeholder: messagePlaceholder,
|
|
599
|
-
accentColor: userMessageColor
|
|
683
|
+
accentColor: userMessageColor,
|
|
684
|
+
canRegenerate: !!canRegenerate,
|
|
685
|
+
onRegenerate: () => void chat.regenerate()
|
|
600
686
|
}
|
|
601
687
|
),
|
|
602
688
|
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({
|
|
@@ -515,12 +593,18 @@ function ChatWidget({
|
|
|
515
593
|
footer,
|
|
516
594
|
theme,
|
|
517
595
|
showThinking = false,
|
|
596
|
+
regenerateMessage = false,
|
|
597
|
+
persist = false,
|
|
598
|
+
onNavigate,
|
|
518
599
|
source = "playground",
|
|
600
|
+
userContext,
|
|
601
|
+
playgroundOverrides,
|
|
519
602
|
className,
|
|
520
603
|
onClose,
|
|
521
604
|
onReset
|
|
522
605
|
}) {
|
|
523
|
-
const chat = useChat({ agentId, workspaceId, source });
|
|
606
|
+
const chat = useChat({ agentId, workspaceId, source, userContext, persist, onNavigate, playgroundOverrides });
|
|
607
|
+
const canRegenerate = regenerateMessage && chat.messages.length > 0 && chat.messages.at(-1)?.role === "assistant" && !chat.streaming;
|
|
524
608
|
const title = displayName || agentName;
|
|
525
609
|
const headerBg = userMessageColor;
|
|
526
610
|
const headerText = getContrastColor(userMessageColor);
|
|
@@ -570,7 +654,9 @@ function ChatWidget({
|
|
|
570
654
|
onSend: () => chat.send(),
|
|
571
655
|
streaming: chat.streaming,
|
|
572
656
|
placeholder: messagePlaceholder,
|
|
573
|
-
accentColor: userMessageColor
|
|
657
|
+
accentColor: userMessageColor,
|
|
658
|
+
canRegenerate: !!canRegenerate,
|
|
659
|
+
onRegenerate: () => void chat.regenerate()
|
|
574
660
|
}
|
|
575
661
|
),
|
|
576
662
|
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