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