@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.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
+ }