@wallavi/widget 1.0.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 +76 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.js +630 -0
- package/dist/index.mjs +601 -0
- package/package.json +52 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
import { clsx } from 'clsx';
|
|
2
|
+
import { twMerge } from 'tailwind-merge';
|
|
3
|
+
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
4
|
+
import { RotateCcw, X, Loader2, ArrowUp, Zap, ChevronDown, CheckCircle2, AlertCircle } from 'lucide-react';
|
|
5
|
+
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
|
6
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
7
|
+
import ReactMarkdownLib from 'react-markdown';
|
|
8
|
+
import remarkGfm from 'remark-gfm';
|
|
9
|
+
|
|
10
|
+
// src/chat-widget.tsx
|
|
11
|
+
|
|
12
|
+
// src/types.ts
|
|
13
|
+
function getContrastColor(hex) {
|
|
14
|
+
const clean = hex.replace("#", "");
|
|
15
|
+
if (clean.length < 6) return "#ffffff";
|
|
16
|
+
const r = parseInt(clean.slice(0, 2), 16);
|
|
17
|
+
const g = parseInt(clean.slice(2, 4), 16);
|
|
18
|
+
const b = parseInt(clean.slice(4, 6), 16);
|
|
19
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5 ? "#000000" : "#ffffff";
|
|
20
|
+
}
|
|
21
|
+
function formatToolName(name) {
|
|
22
|
+
return name.replace(/([A-Z])/g, " $1").replace(/_/g, " ").trim();
|
|
23
|
+
}
|
|
24
|
+
var STREAM_DELIMITER = "\u03B6\u236E";
|
|
25
|
+
async function consumeStream(body, handler) {
|
|
26
|
+
const reader = body.getReader();
|
|
27
|
+
const decoder = new TextDecoder();
|
|
28
|
+
let buffer = "";
|
|
29
|
+
while (true) {
|
|
30
|
+
const { done, value } = await reader.read();
|
|
31
|
+
if (done) break;
|
|
32
|
+
buffer += decoder.decode(value, { stream: true });
|
|
33
|
+
const chunks = buffer.split(STREAM_DELIMITER);
|
|
34
|
+
buffer = chunks.pop() ?? "";
|
|
35
|
+
for (const chunk of chunks) {
|
|
36
|
+
const raw = chunk.trim();
|
|
37
|
+
if (!raw) continue;
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(raw);
|
|
40
|
+
if (parsed.data?.uiMessageProtocol) handler(parsed.data.uiMessageProtocol);
|
|
41
|
+
} catch {
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
var API_URL = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:4000";
|
|
47
|
+
function newId() {
|
|
48
|
+
return Math.random().toString(36).slice(2, 10);
|
|
49
|
+
}
|
|
50
|
+
function useChat({
|
|
51
|
+
agentId,
|
|
52
|
+
workspaceId = "",
|
|
53
|
+
source = "playground",
|
|
54
|
+
userContext
|
|
55
|
+
}) {
|
|
56
|
+
const [messages, setMessages] = useState([]);
|
|
57
|
+
const [input, setInput] = useState("");
|
|
58
|
+
const [streaming, setStreaming] = useState(false);
|
|
59
|
+
const [threadId, setThreadId] = useState(() => crypto.randomUUID());
|
|
60
|
+
const streamingMsgIdRef = useRef(null);
|
|
61
|
+
const reset = useCallback(() => {
|
|
62
|
+
setMessages([]);
|
|
63
|
+
setInput("");
|
|
64
|
+
setStreaming(false);
|
|
65
|
+
setThreadId(crypto.randomUUID());
|
|
66
|
+
streamingMsgIdRef.current = null;
|
|
67
|
+
}, []);
|
|
68
|
+
const send = useCallback(
|
|
69
|
+
async (text) => {
|
|
70
|
+
const userInput = (text ?? input).trim();
|
|
71
|
+
if (!userInput || streaming) return;
|
|
72
|
+
setInput("");
|
|
73
|
+
const userMsgId = newId();
|
|
74
|
+
setMessages((prev) => [
|
|
75
|
+
...prev,
|
|
76
|
+
{ id: userMsgId, role: "user", parts: [{ type: "text", text: userInput }] }
|
|
77
|
+
]);
|
|
78
|
+
setStreaming(true);
|
|
79
|
+
const assistantMsgId = newId();
|
|
80
|
+
streamingMsgIdRef.current = assistantMsgId;
|
|
81
|
+
setMessages((prev) => [
|
|
82
|
+
...prev,
|
|
83
|
+
{ id: assistantMsgId, role: "assistant", parts: [] }
|
|
84
|
+
]);
|
|
85
|
+
try {
|
|
86
|
+
const token = typeof window !== "undefined" ? await window.Clerk?.session?.getToken() : null;
|
|
87
|
+
const res = await fetch(`${API_URL}/api/threads/${threadId}/stream`, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: {
|
|
90
|
+
"Content-Type": "application/json",
|
|
91
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
92
|
+
},
|
|
93
|
+
body: JSON.stringify({
|
|
94
|
+
input: userInput,
|
|
95
|
+
agentId,
|
|
96
|
+
workspaceId,
|
|
97
|
+
source,
|
|
98
|
+
...userContext?.userName ? { userName: userContext.userName } : {},
|
|
99
|
+
...userContext?.userEmail ? { userEmail: userContext.userEmail } : {},
|
|
100
|
+
userMetadata: {
|
|
101
|
+
...userContext?.metadata ?? {},
|
|
102
|
+
...userContext?.headers ? { headers: userContext.headers } : {}
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
});
|
|
106
|
+
if (!res.ok) {
|
|
107
|
+
const errText = await res.text().catch(() => "");
|
|
108
|
+
throw new Error(errText || `API error ${res.status}`);
|
|
109
|
+
}
|
|
110
|
+
if (!res.body) throw new Error("No stream body");
|
|
111
|
+
const applyEvent = (proto) => {
|
|
112
|
+
setMessages((prev) => {
|
|
113
|
+
const idx = prev.findIndex((m) => m.id === assistantMsgId);
|
|
114
|
+
if (idx === -1) return prev;
|
|
115
|
+
const existing = prev[idx];
|
|
116
|
+
const msg = {
|
|
117
|
+
id: existing.id,
|
|
118
|
+
role: existing.role,
|
|
119
|
+
parts: [...existing.parts]
|
|
120
|
+
};
|
|
121
|
+
switch (proto.type) {
|
|
122
|
+
case "text-delta": {
|
|
123
|
+
const textIdx = msg.parts.findIndex((p) => p.type === "text");
|
|
124
|
+
if (textIdx === -1) {
|
|
125
|
+
msg.parts.push({ type: "text", text: proto.delta });
|
|
126
|
+
} else {
|
|
127
|
+
const p = msg.parts[textIdx];
|
|
128
|
+
msg.parts[textIdx] = { type: "text", text: p.text + proto.delta };
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case "reasoning-delta": {
|
|
133
|
+
const rIdx = msg.parts.findIndex((p) => p.type === "reasoning");
|
|
134
|
+
if (rIdx === -1) {
|
|
135
|
+
msg.parts.unshift({ type: "reasoning", text: proto.delta });
|
|
136
|
+
} else {
|
|
137
|
+
const p = msg.parts[rIdx];
|
|
138
|
+
msg.parts[rIdx] = { type: "reasoning", text: p.text + proto.delta };
|
|
139
|
+
}
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
case "tool-input-available": {
|
|
143
|
+
msg.parts.push({
|
|
144
|
+
type: "tool",
|
|
145
|
+
toolCallId: proto.toolCallId,
|
|
146
|
+
toolName: proto.toolName,
|
|
147
|
+
input: proto.input ?? {},
|
|
148
|
+
status: "running"
|
|
149
|
+
});
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case "tool-output-available": {
|
|
153
|
+
const tIdx = msg.parts.findIndex(
|
|
154
|
+
(p) => p.type === "tool" && p.toolCallId === proto.toolCallId
|
|
155
|
+
);
|
|
156
|
+
if (tIdx !== -1) {
|
|
157
|
+
msg.parts[tIdx] = {
|
|
158
|
+
...msg.parts[tIdx],
|
|
159
|
+
status: "done",
|
|
160
|
+
output: proto.output
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
case "tool-output-error": {
|
|
166
|
+
const tIdx = msg.parts.findIndex(
|
|
167
|
+
(p) => p.type === "tool" && p.toolCallId === proto.toolCallId
|
|
168
|
+
);
|
|
169
|
+
if (tIdx !== -1) {
|
|
170
|
+
msg.parts[tIdx] = {
|
|
171
|
+
...msg.parts[tIdx],
|
|
172
|
+
status: "error",
|
|
173
|
+
errorText: proto.errorText
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
default:
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
const copy = [...prev];
|
|
182
|
+
copy[idx] = msg;
|
|
183
|
+
return copy;
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
await consumeStream(res.body, applyEvent);
|
|
187
|
+
} catch {
|
|
188
|
+
setMessages((prev) => {
|
|
189
|
+
const idx = prev.findIndex((m) => m.id === assistantMsgId);
|
|
190
|
+
if (idx === -1) return prev;
|
|
191
|
+
const copy = [...prev];
|
|
192
|
+
const err = copy[idx];
|
|
193
|
+
copy[idx] = {
|
|
194
|
+
id: err.id,
|
|
195
|
+
role: err.role,
|
|
196
|
+
parts: [{ type: "text", text: "Sorry, something went wrong. Please try again." }]
|
|
197
|
+
};
|
|
198
|
+
return copy;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
setStreaming(false);
|
|
202
|
+
streamingMsgIdRef.current = null;
|
|
203
|
+
},
|
|
204
|
+
[input, streaming, agentId, workspaceId, source, threadId, userContext]
|
|
205
|
+
);
|
|
206
|
+
return { messages, input, setInput, streaming, threadId, send, reset };
|
|
207
|
+
}
|
|
208
|
+
var Avatar = ({ className, ...p }) => /* @__PURE__ */ jsx(AvatarPrimitive.Root, { className: ["relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className].filter(Boolean).join(" "), ...p });
|
|
209
|
+
var AvatarImage = AvatarPrimitive.Image;
|
|
210
|
+
var AvatarFallback = ({ className, ...p }) => /* @__PURE__ */ jsx(AvatarPrimitive.Fallback, { className: ["flex h-full w-full items-center justify-center rounded-full bg-muted", className].filter(Boolean).join(" "), ...p });
|
|
211
|
+
function ChatHeader({
|
|
212
|
+
title,
|
|
213
|
+
profilePicture,
|
|
214
|
+
headerBg,
|
|
215
|
+
headerText,
|
|
216
|
+
onReset,
|
|
217
|
+
onClose
|
|
218
|
+
}) {
|
|
219
|
+
return /* @__PURE__ */ jsxs(
|
|
220
|
+
"header",
|
|
221
|
+
{
|
|
222
|
+
className: "flex items-center justify-between px-4 py-3 shrink-0",
|
|
223
|
+
style: { backgroundColor: headerBg, color: headerText },
|
|
224
|
+
children: [
|
|
225
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 min-w-0", children: [
|
|
226
|
+
/* @__PURE__ */ jsx(Avatar, { className: "h-8 w-8 shrink-0 border-2", style: { borderColor: `${headerText}30` }, children: profilePicture ? /* @__PURE__ */ jsx(AvatarImage, { src: profilePicture, alt: title }) : /* @__PURE__ */ jsx(
|
|
227
|
+
AvatarFallback,
|
|
228
|
+
{
|
|
229
|
+
className: "text-xs font-bold",
|
|
230
|
+
style: { backgroundColor: `${headerText}20`, color: headerText },
|
|
231
|
+
children: title.slice(0, 2).toUpperCase()
|
|
232
|
+
}
|
|
233
|
+
) }),
|
|
234
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
235
|
+
/* @__PURE__ */ jsx("p", { className: "font-semibold text-sm truncate leading-tight", children: title }),
|
|
236
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] opacity-70 leading-tight", children: "AI Assistant" })
|
|
237
|
+
] })
|
|
238
|
+
] }),
|
|
239
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
240
|
+
/* @__PURE__ */ jsx(
|
|
241
|
+
"button",
|
|
242
|
+
{
|
|
243
|
+
onClick: onReset,
|
|
244
|
+
className: "rounded-full p-1.5 transition-colors hover:bg-white/10",
|
|
245
|
+
title: "New conversation",
|
|
246
|
+
children: /* @__PURE__ */ jsx(RotateCcw, { className: "h-3.5 w-3.5", style: { color: headerText } })
|
|
247
|
+
}
|
|
248
|
+
),
|
|
249
|
+
onClose && /* @__PURE__ */ jsx(
|
|
250
|
+
"button",
|
|
251
|
+
{
|
|
252
|
+
onClick: onClose,
|
|
253
|
+
className: "rounded-full p-1.5 transition-colors hover:bg-white/10",
|
|
254
|
+
title: "Close",
|
|
255
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5", style: { color: headerText } })
|
|
256
|
+
}
|
|
257
|
+
)
|
|
258
|
+
] })
|
|
259
|
+
]
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
var Avatar2 = ({ className, ...p }) => /* @__PURE__ */ jsx(AvatarPrimitive.Root, { className: ["relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className].filter(Boolean).join(" "), ...p });
|
|
264
|
+
var AvatarImage2 = AvatarPrimitive.Image;
|
|
265
|
+
var AvatarFallback2 = ({ className, ...p }) => /* @__PURE__ */ jsx(AvatarPrimitive.Fallback, { className: ["flex h-full w-full items-center justify-center rounded-full bg-muted", className].filter(Boolean).join(" "), ...p });
|
|
266
|
+
var cn = (...inputs) => twMerge(clsx(inputs));
|
|
267
|
+
var ReactMarkdown = ReactMarkdownLib;
|
|
268
|
+
function ThinkingDots() {
|
|
269
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
270
|
+
/* @__PURE__ */ jsx("style", { children: `
|
|
271
|
+
@keyframes wDotBounce {
|
|
272
|
+
0%, 80%, 100% { transform: translateY(0) scale(1); opacity: 0.35; }
|
|
273
|
+
40% { transform: translateY(-6px) scale(1.15); opacity: 1; }
|
|
274
|
+
}
|
|
275
|
+
` }),
|
|
276
|
+
/* @__PURE__ */ jsx(
|
|
277
|
+
"div",
|
|
278
|
+
{
|
|
279
|
+
className: "bg-muted",
|
|
280
|
+
style: {
|
|
281
|
+
display: "inline-flex",
|
|
282
|
+
alignItems: "center",
|
|
283
|
+
gap: 6,
|
|
284
|
+
borderRadius: "18px 18px 18px 4px",
|
|
285
|
+
padding: "13px 18px"
|
|
286
|
+
},
|
|
287
|
+
children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(
|
|
288
|
+
"span",
|
|
289
|
+
{
|
|
290
|
+
style: {
|
|
291
|
+
display: "block",
|
|
292
|
+
width: 7,
|
|
293
|
+
height: 7,
|
|
294
|
+
borderRadius: "50%",
|
|
295
|
+
backgroundColor: "currentColor",
|
|
296
|
+
animation: `wDotBounce 1.2s cubic-bezier(0.4,0,0.2,1) ${i * 0.16}s infinite`
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
i
|
|
300
|
+
))
|
|
301
|
+
}
|
|
302
|
+
)
|
|
303
|
+
] });
|
|
304
|
+
}
|
|
305
|
+
function ToolCallBadge({ part }) {
|
|
306
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground bg-muted/60 border rounded-full px-3 py-1 w-fit", children: [
|
|
307
|
+
part.status === "running" && /* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 animate-spin" }),
|
|
308
|
+
part.status === "done" && /* @__PURE__ */ jsx(CheckCircle2, { className: "h-3 w-3 text-emerald-500" }),
|
|
309
|
+
part.status === "error" && /* @__PURE__ */ jsx(AlertCircle, { className: "h-3 w-3 text-destructive" }),
|
|
310
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
311
|
+
part.status === "running" ? "Calling" : part.status === "done" ? "Called" : "Failed",
|
|
312
|
+
" ",
|
|
313
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: formatToolName(part.toolName) })
|
|
314
|
+
] })
|
|
315
|
+
] });
|
|
316
|
+
}
|
|
317
|
+
function ReasoningBlock({ text }) {
|
|
318
|
+
const [open, setOpen] = useState(false);
|
|
319
|
+
return /* @__PURE__ */ jsxs("div", { className: "mb-1", children: [
|
|
320
|
+
/* @__PURE__ */ jsxs(
|
|
321
|
+
"button",
|
|
322
|
+
{
|
|
323
|
+
onClick: () => setOpen((v) => !v),
|
|
324
|
+
className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors",
|
|
325
|
+
children: [
|
|
326
|
+
/* @__PURE__ */ jsx(Zap, { className: "h-3 w-3" }),
|
|
327
|
+
/* @__PURE__ */ jsx("span", { children: "Reasoning" }),
|
|
328
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: cn("h-3 w-3 transition-transform", open && "rotate-180") })
|
|
329
|
+
]
|
|
330
|
+
}
|
|
331
|
+
),
|
|
332
|
+
open && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground/80 whitespace-pre-wrap border-l-2 border-muted pl-2 leading-relaxed", children: text })
|
|
333
|
+
] });
|
|
334
|
+
}
|
|
335
|
+
function MessageBubble({
|
|
336
|
+
message,
|
|
337
|
+
userColor,
|
|
338
|
+
agentName,
|
|
339
|
+
profilePicture,
|
|
340
|
+
isStreaming,
|
|
341
|
+
showThinking = true
|
|
342
|
+
}) {
|
|
343
|
+
const isUser = message.role === "user";
|
|
344
|
+
const textPart = message.parts.find((p) => p.type === "text");
|
|
345
|
+
const reasoningPart = message.parts.find((p) => p.type === "reasoning");
|
|
346
|
+
const toolParts = message.parts.filter((p) => p.type === "tool");
|
|
347
|
+
const contrastColor = getContrastColor(userColor);
|
|
348
|
+
if (isUser) {
|
|
349
|
+
return /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
|
|
350
|
+
"div",
|
|
351
|
+
{
|
|
352
|
+
className: "max-w-[78%] rounded-2xl rounded-tr-sm px-4 py-2.5 text-sm leading-relaxed",
|
|
353
|
+
style: { backgroundColor: userColor, color: contrastColor },
|
|
354
|
+
children: textPart?.text
|
|
355
|
+
}
|
|
356
|
+
) });
|
|
357
|
+
}
|
|
358
|
+
const visibleToolParts = showThinking ? toolParts : [];
|
|
359
|
+
const isEmpty = !textPart?.text && visibleToolParts.length === 0;
|
|
360
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex gap-2.5 items-start", children: [
|
|
361
|
+
/* @__PURE__ */ jsx(Avatar2, { className: "h-7 w-7 shrink-0 mt-0.5 border", children: profilePicture ? /* @__PURE__ */ jsx(AvatarImage2, { src: profilePicture, alt: agentName }) : /* @__PURE__ */ jsx(AvatarFallback2, { className: "text-[10px] font-semibold bg-primary text-primary-foreground", children: agentName.slice(0, 2).toUpperCase() }) }),
|
|
362
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5 min-w-0 max-w-[82%]", children: [
|
|
363
|
+
showThinking && reasoningPart && /* @__PURE__ */ jsx(ReasoningBlock, { text: reasoningPart.text }),
|
|
364
|
+
visibleToolParts.map((t) => /* @__PURE__ */ jsx(ToolCallBadge, { part: t }, t.toolCallId)),
|
|
365
|
+
isEmpty && isStreaming ? /* @__PURE__ */ jsx(ThinkingDots, {}) : textPart?.text ? /* @__PURE__ */ jsxs("div", { className: "rounded-2xl rounded-tl-sm bg-muted px-4 py-2.5 text-sm leading-relaxed overflow-hidden prose prose-sm max-w-none prose-p:my-1 prose-headings:font-semibold prose-headings:my-1.5 prose-ul:my-1 prose-li:my-0.5 prose-strong:font-semibold", children: [
|
|
366
|
+
/* @__PURE__ */ jsx(
|
|
367
|
+
ReactMarkdown,
|
|
368
|
+
{
|
|
369
|
+
remarkPlugins: [remarkGfm],
|
|
370
|
+
components: {
|
|
371
|
+
p: ({ children }) => /* @__PURE__ */ jsx("p", { style: { margin: "4px 0" }, children }),
|
|
372
|
+
h1: ({ children }) => /* @__PURE__ */ jsx("p", { style: { margin: "6px 0", fontWeight: 700, fontSize: "1em" }, children }),
|
|
373
|
+
h2: ({ children }) => /* @__PURE__ */ jsx("p", { style: { margin: "6px 0", fontWeight: 700, fontSize: "1em" }, children }),
|
|
374
|
+
h3: ({ children }) => /* @__PURE__ */ jsx("p", { style: { margin: "6px 0", fontWeight: 600, fontSize: "0.95em" }, children }),
|
|
375
|
+
ul: ({ children }) => /* @__PURE__ */ jsx("ul", { style: { margin: "4px 0", paddingLeft: 16 }, children }),
|
|
376
|
+
ol: ({ children }) => /* @__PURE__ */ jsx("ol", { style: { margin: "4px 0", paddingLeft: 16 }, children }),
|
|
377
|
+
li: ({ children }) => /* @__PURE__ */ jsx("li", { style: { margin: "2px 0" }, children }),
|
|
378
|
+
strong: ({ children }) => /* @__PURE__ */ jsx("strong", { style: { fontWeight: 600 }, children }),
|
|
379
|
+
code: ({ children }) => /* @__PURE__ */ jsx("code", { style: { fontSize: "0.85em", backgroundColor: "rgba(0,0,0,0.07)", borderRadius: 4, padding: "1px 5px", fontFamily: "monospace" }, children }),
|
|
380
|
+
pre: ({ children }) => /* @__PURE__ */ jsx("pre", { style: { overflowX: "auto", margin: "6px 0", padding: "8px 10px", backgroundColor: "rgba(0,0,0,0.06)", borderRadius: 8, fontSize: "0.82em", fontFamily: "monospace" }, children }),
|
|
381
|
+
a: ({ href, children }) => /* @__PURE__ */ jsx("a", { href, target: "_blank", rel: "noopener noreferrer", style: { textDecoration: "underline", opacity: 0.75 }, children }),
|
|
382
|
+
table: ({ children }) => /* @__PURE__ */ jsx("div", { style: { overflowX: "auto", margin: "6px 0", borderRadius: 8, border: "1px solid rgba(0,0,0,0.1)" }, children: /* @__PURE__ */ jsx("table", { style: { borderCollapse: "collapse", minWidth: "100%", fontSize: "0.82em" }, children }) }),
|
|
383
|
+
thead: ({ children }) => /* @__PURE__ */ jsx("thead", { style: { backgroundColor: "rgba(0,0,0,0.04)" }, children }),
|
|
384
|
+
tbody: ({ children }) => /* @__PURE__ */ jsx("tbody", { children }),
|
|
385
|
+
tr: ({ children }) => /* @__PURE__ */ jsx("tr", { style: { borderBottom: "1px solid rgba(0,0,0,0.07)" }, children }),
|
|
386
|
+
th: ({ children }) => /* @__PURE__ */ jsx("th", { style: { padding: "6px 10px", textAlign: "left", fontWeight: 600, whiteSpace: "nowrap" }, children }),
|
|
387
|
+
td: ({ children }) => /* @__PURE__ */ jsx("td", { style: { padding: "5px 10px", verticalAlign: "top" }, children })
|
|
388
|
+
},
|
|
389
|
+
children: textPart.text
|
|
390
|
+
}
|
|
391
|
+
),
|
|
392
|
+
isStreaming && /* @__PURE__ */ jsx("span", { className: "inline-block w-0.5 h-3.5 bg-foreground/60 ml-0.5 animate-pulse align-middle" })
|
|
393
|
+
] }) : null
|
|
394
|
+
] })
|
|
395
|
+
] });
|
|
396
|
+
}
|
|
397
|
+
var Avatar3 = ({ className, ...p }) => /* @__PURE__ */ jsx(AvatarPrimitive.Root, { className: ["relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className].filter(Boolean).join(" "), ...p });
|
|
398
|
+
var AvatarImage3 = AvatarPrimitive.Image;
|
|
399
|
+
var AvatarFallback3 = ({ className, ...p }) => /* @__PURE__ */ jsx(AvatarPrimitive.Fallback, { className: ["flex h-full w-full items-center justify-center rounded-full bg-muted", className].filter(Boolean).join(" "), ...p });
|
|
400
|
+
function ChatMessages({
|
|
401
|
+
messages,
|
|
402
|
+
streaming,
|
|
403
|
+
agentName,
|
|
404
|
+
profilePicture,
|
|
405
|
+
userColor,
|
|
406
|
+
initialMessages = [],
|
|
407
|
+
suggestedMessages = [],
|
|
408
|
+
showThinking = true,
|
|
409
|
+
onSuggest
|
|
410
|
+
}) {
|
|
411
|
+
const bottomRef = useRef(null);
|
|
412
|
+
const showGreeting = messages.length === 0;
|
|
413
|
+
useEffect(() => {
|
|
414
|
+
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
415
|
+
}, [messages]);
|
|
416
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col overflow-y-auto overscroll-contain", children: [
|
|
417
|
+
showGreeting && /* @__PURE__ */ jsxs("div", { className: "flex gap-2.5 items-start px-4 pt-4", children: [
|
|
418
|
+
/* @__PURE__ */ jsx(Avatar3, { className: "h-7 w-7 shrink-0 mt-0.5 border", children: profilePicture ? /* @__PURE__ */ jsx(AvatarImage3, { src: profilePicture, alt: agentName }) : /* @__PURE__ */ jsx(AvatarFallback3, { className: "text-[10px] font-semibold bg-primary text-primary-foreground", children: agentName.slice(0, 2).toUpperCase() }) }),
|
|
419
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-2xl rounded-tl-sm bg-muted px-4 py-2.5 text-sm leading-relaxed max-w-[82%]", children: initialMessages[0] ?? "Hello! How can I help you today?" })
|
|
420
|
+
] }),
|
|
421
|
+
showGreeting && /* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
422
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-4 px-4 py-4", children: messages.map((msg, i) => /* @__PURE__ */ jsx(
|
|
423
|
+
MessageBubble,
|
|
424
|
+
{
|
|
425
|
+
message: msg,
|
|
426
|
+
userColor,
|
|
427
|
+
agentName,
|
|
428
|
+
profilePicture,
|
|
429
|
+
isStreaming: streaming && i === messages.length - 1 && msg.role === "assistant",
|
|
430
|
+
showThinking
|
|
431
|
+
},
|
|
432
|
+
msg.id
|
|
433
|
+
)) }),
|
|
434
|
+
showGreeting && suggestedMessages.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 px-4 pb-4", children: suggestedMessages.map((msg, i) => /* @__PURE__ */ jsx(
|
|
435
|
+
"button",
|
|
436
|
+
{
|
|
437
|
+
onClick: () => onSuggest(msg),
|
|
438
|
+
disabled: streaming,
|
|
439
|
+
className: "text-xs border rounded-full px-3 py-1.5 hover:bg-muted transition-colors text-left disabled:opacity-50",
|
|
440
|
+
children: msg
|
|
441
|
+
},
|
|
442
|
+
i
|
|
443
|
+
)) }),
|
|
444
|
+
/* @__PURE__ */ jsx("div", { ref: bottomRef })
|
|
445
|
+
] });
|
|
446
|
+
}
|
|
447
|
+
var cn2 = (...inputs) => twMerge(clsx(inputs));
|
|
448
|
+
function ChatInput({
|
|
449
|
+
input,
|
|
450
|
+
setInput,
|
|
451
|
+
onSend,
|
|
452
|
+
streaming,
|
|
453
|
+
placeholder,
|
|
454
|
+
accentColor
|
|
455
|
+
}) {
|
|
456
|
+
const textareaRef = useRef(null);
|
|
457
|
+
useEffect(() => {
|
|
458
|
+
if (!input && textareaRef.current) {
|
|
459
|
+
textareaRef.current.style.height = "auto";
|
|
460
|
+
}
|
|
461
|
+
}, [input]);
|
|
462
|
+
const handleKeyDown = (e) => {
|
|
463
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
464
|
+
e.preventDefault();
|
|
465
|
+
onSend();
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
const hasText = input.trim().length > 0;
|
|
469
|
+
return /* @__PURE__ */ jsx("div", { className: "shrink-0 border-t bg-background px-3 py-2.5", children: /* @__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: [
|
|
470
|
+
/* @__PURE__ */ jsx(
|
|
471
|
+
"textarea",
|
|
472
|
+
{
|
|
473
|
+
ref: textareaRef,
|
|
474
|
+
rows: 1,
|
|
475
|
+
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",
|
|
476
|
+
placeholder: placeholder ?? "Send a message\u2026",
|
|
477
|
+
value: input,
|
|
478
|
+
onChange: (e) => {
|
|
479
|
+
setInput(e.target.value);
|
|
480
|
+
e.target.style.height = "auto";
|
|
481
|
+
e.target.style.height = `${Math.min(e.target.scrollHeight, 128)}px`;
|
|
482
|
+
},
|
|
483
|
+
onKeyDown: handleKeyDown,
|
|
484
|
+
disabled: streaming,
|
|
485
|
+
autoFocus: true
|
|
486
|
+
}
|
|
487
|
+
),
|
|
488
|
+
/* @__PURE__ */ jsx(
|
|
489
|
+
"button",
|
|
490
|
+
{
|
|
491
|
+
onClick: onSend,
|
|
492
|
+
disabled: streaming || !hasText,
|
|
493
|
+
className: cn2(
|
|
494
|
+
"h-7 w-7 shrink-0 rounded-xl flex items-center justify-center transition-all duration-200",
|
|
495
|
+
hasText || streaming ? "opacity-100 shadow-sm" : "opacity-30"
|
|
496
|
+
),
|
|
497
|
+
style: hasText || streaming ? { backgroundColor: accentColor, color: getContrastColor(accentColor) } : { backgroundColor: "transparent", color: "currentColor" },
|
|
498
|
+
children: streaming ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(ArrowUp, { className: "h-3.5 w-3.5" })
|
|
499
|
+
}
|
|
500
|
+
)
|
|
501
|
+
] }) });
|
|
502
|
+
}
|
|
503
|
+
var cn3 = (...inputs) => twMerge(clsx(inputs));
|
|
504
|
+
function ChatWidget({
|
|
505
|
+
agentId,
|
|
506
|
+
workspaceId,
|
|
507
|
+
agentName,
|
|
508
|
+
displayName,
|
|
509
|
+
profilePicture,
|
|
510
|
+
userMessageColor = "#19191c",
|
|
511
|
+
initialMessages = ["Hello! How can I help you today?"],
|
|
512
|
+
suggestedMessages = [],
|
|
513
|
+
messagePlaceholder,
|
|
514
|
+
watermark = true,
|
|
515
|
+
watermarkLogoUrl = "https://wallavi.com/wallavi.svg",
|
|
516
|
+
footer,
|
|
517
|
+
theme,
|
|
518
|
+
showThinking = false,
|
|
519
|
+
source = "playground",
|
|
520
|
+
className,
|
|
521
|
+
onClose,
|
|
522
|
+
onReset
|
|
523
|
+
}) {
|
|
524
|
+
const chat = useChat({ agentId, workspaceId, source });
|
|
525
|
+
const title = displayName || agentName;
|
|
526
|
+
const headerBg = userMessageColor;
|
|
527
|
+
const headerText = getContrastColor(userMessageColor);
|
|
528
|
+
const handleReset = () => {
|
|
529
|
+
chat.reset();
|
|
530
|
+
onReset?.();
|
|
531
|
+
};
|
|
532
|
+
return /* @__PURE__ */ jsxs(
|
|
533
|
+
"div",
|
|
534
|
+
{
|
|
535
|
+
className: cn3(
|
|
536
|
+
"flex flex-col overflow-hidden rounded-2xl border shadow-xl bg-background",
|
|
537
|
+
className
|
|
538
|
+
),
|
|
539
|
+
style: { colorScheme: theme },
|
|
540
|
+
children: [
|
|
541
|
+
/* @__PURE__ */ jsx(
|
|
542
|
+
ChatHeader,
|
|
543
|
+
{
|
|
544
|
+
title,
|
|
545
|
+
profilePicture,
|
|
546
|
+
headerBg,
|
|
547
|
+
headerText,
|
|
548
|
+
onReset: handleReset,
|
|
549
|
+
onClose
|
|
550
|
+
}
|
|
551
|
+
),
|
|
552
|
+
/* @__PURE__ */ jsx(
|
|
553
|
+
ChatMessages,
|
|
554
|
+
{
|
|
555
|
+
messages: chat.messages,
|
|
556
|
+
streaming: chat.streaming,
|
|
557
|
+
agentName,
|
|
558
|
+
profilePicture,
|
|
559
|
+
userColor: userMessageColor,
|
|
560
|
+
initialMessages,
|
|
561
|
+
suggestedMessages,
|
|
562
|
+
showThinking,
|
|
563
|
+
onSuggest: (msg) => chat.send(msg)
|
|
564
|
+
}
|
|
565
|
+
),
|
|
566
|
+
/* @__PURE__ */ jsx(
|
|
567
|
+
ChatInput,
|
|
568
|
+
{
|
|
569
|
+
input: chat.input,
|
|
570
|
+
setInput: chat.setInput,
|
|
571
|
+
onSend: () => chat.send(),
|
|
572
|
+
streaming: chat.streaming,
|
|
573
|
+
placeholder: messagePlaceholder,
|
|
574
|
+
accentColor: userMessageColor
|
|
575
|
+
}
|
|
576
|
+
),
|
|
577
|
+
watermark && /* @__PURE__ */ jsxs("footer", { className: "shrink-0 flex items-center justify-center gap-1.5 bg-muted/50 py-1.5 border-t", children: [
|
|
578
|
+
/* @__PURE__ */ jsxs(
|
|
579
|
+
"a",
|
|
580
|
+
{
|
|
581
|
+
href: "https://wallavi.com",
|
|
582
|
+
target: "_blank",
|
|
583
|
+
rel: "noopener noreferrer",
|
|
584
|
+
className: "flex items-center gap-1 text-[10px] text-muted-foreground hover:text-foreground transition-colors",
|
|
585
|
+
children: [
|
|
586
|
+
/* @__PURE__ */ jsx("img", { src: watermarkLogoUrl, alt: "Wallavi", width: 12, height: 12 }),
|
|
587
|
+
/* @__PURE__ */ jsx("span", { children: "Powered by Wallavi" })
|
|
588
|
+
]
|
|
589
|
+
}
|
|
590
|
+
),
|
|
591
|
+
footer && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
592
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40 text-[10px]", children: "\xB7" }),
|
|
593
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: footer })
|
|
594
|
+
] })
|
|
595
|
+
] })
|
|
596
|
+
]
|
|
597
|
+
}
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
export { ChatWidget, formatToolName, getContrastColor, useChat };
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dependencies": {
|
|
3
|
+
"@radix-ui/react-avatar": "^1.1.10",
|
|
4
|
+
"clsx": "^2.1.1",
|
|
5
|
+
"lucide-react": "^0.475.0",
|
|
6
|
+
"react-markdown": "^10.1.0",
|
|
7
|
+
"remark-gfm": "^4.0.1",
|
|
8
|
+
"tailwind-merge": "^3.0.2"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"@types/node": "^22.10.7",
|
|
12
|
+
"@types/react": "^19.1.0",
|
|
13
|
+
"@types/react-dom": "^19.1.0",
|
|
14
|
+
"@wallavi/typescript-config": "workspace:*",
|
|
15
|
+
"tsup": "^8.3.5",
|
|
16
|
+
"typescript": "^5.7.3"
|
|
17
|
+
},
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"default": "./src/index.ts",
|
|
21
|
+
"types": "./src/index.ts"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"main": "./src/index.ts",
|
|
28
|
+
"name": "@wallavi/widget",
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
31
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
32
|
+
},
|
|
33
|
+
"private": false,
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"exports": {
|
|
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
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsup",
|
|
48
|
+
"typecheck": "tsc --noEmit"
|
|
49
|
+
},
|
|
50
|
+
"types": "./src/index.ts",
|
|
51
|
+
"version": "1.0.0"
|
|
52
|
+
}
|