@px-ui/ai 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,773 @@
1
+ import { t as __export } from "./chunk-BAz01cYq.js";
2
+ import { createContext, useContext, useEffect, useRef, useState } from "react";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ import { Avatar, Button, Dialog, FileIcon, SendIcon, StopIcon, Tooltip, toast } from "@px-ui/core";
5
+ import ReactMarkdown from "react-markdown";
6
+
7
+ //#region src/context/xandi-context.tsx
8
+ const XandiContext = createContext(null);
9
+ function XandiProvider({ api, userId, orgId, sessionId: initialSessionId, onFeedback, children }) {
10
+ const [sessionId, setSessionId] = useState(initialSessionId ?? null);
11
+ const [messages, setMessages] = useState([]);
12
+ const [isLoading, setIsLoading] = useState(false);
13
+ useEffect(() => {
14
+ if (!initialSessionId) setSessionId(crypto.randomUUID());
15
+ }, [initialSessionId]);
16
+ const sendMessage = async (text) => {
17
+ if (!text.trim() || isLoading) return;
18
+ const userMessage = {
19
+ id: crypto.randomUUID(),
20
+ role: "user",
21
+ content: text
22
+ };
23
+ setMessages((prev) => [...prev, userMessage]);
24
+ setIsLoading(true);
25
+ try {
26
+ const data = await (await fetch(api, {
27
+ method: "POST",
28
+ headers: {
29
+ "Content-Type": "application/json",
30
+ "x-org-id": orgId,
31
+ "x-user-id": userId
32
+ },
33
+ body: JSON.stringify({ message: text })
34
+ })).json();
35
+ if (data.success && data.response) {
36
+ const assistantMessage = {
37
+ id: crypto.randomUUID(),
38
+ role: "assistant",
39
+ content: data.response
40
+ };
41
+ setMessages((prev) => [...prev, assistantMessage]);
42
+ }
43
+ } catch (error) {
44
+ console.error("Failed to send message:", error);
45
+ } finally {
46
+ setIsLoading(false);
47
+ }
48
+ };
49
+ const value = {
50
+ messages,
51
+ isLoading,
52
+ sessionId,
53
+ sendMessage,
54
+ onFeedback
55
+ };
56
+ return /* @__PURE__ */ jsx(XandiContext.Provider, {
57
+ value,
58
+ children
59
+ });
60
+ }
61
+ function useXandi() {
62
+ const context = useContext(XandiContext);
63
+ if (!context) throw new Error("useXandi must be used within XandiProvider");
64
+ return context;
65
+ }
66
+
67
+ //#endregion
68
+ //#region src/components/x-main-intake.tsx
69
+ function XMainIntake({ placeholder = "Ask about jobs, candidates, timesheets, or anything workforce...", suggestions = [] }) {
70
+ const { isLoading, sendMessage } = useXandi();
71
+ const [input, setInput] = useState("");
72
+ const handleSubmit = (e) => {
73
+ e?.preventDefault();
74
+ if (input.trim() && !isLoading) {
75
+ sendMessage(input);
76
+ setInput("");
77
+ }
78
+ };
79
+ const handleSuggestionClick = (prompt) => {
80
+ setInput(prompt);
81
+ };
82
+ return /* @__PURE__ */ jsxs("div", {
83
+ className: "flex flex-col gap-3",
84
+ children: [/* @__PURE__ */ jsxs("form", {
85
+ onSubmit: handleSubmit,
86
+ className: "flex flex-col gap-2 rounded-2xl border border-ppx-neutral-5 bg-ppx-neutral-1 p-3",
87
+ children: [
88
+ /* @__PURE__ */ jsx("div", { className: "uploads-section" }),
89
+ /* @__PURE__ */ jsx(XIntakeTextarea, {
90
+ value: input,
91
+ onChange: setInput,
92
+ onSubmit: handleSubmit,
93
+ placeholder,
94
+ disabled: isLoading
95
+ }),
96
+ /* @__PURE__ */ jsxs("div", {
97
+ className: "actions-section flex flex-row items-center gap-2",
98
+ children: [
99
+ /* @__PURE__ */ jsx(Button, {
100
+ type: "button",
101
+ variant: "ghost",
102
+ size: "icon-sm",
103
+ disabled: isLoading,
104
+ children: /* @__PURE__ */ jsx(FileIcon, { width: 20 })
105
+ }),
106
+ /* @__PURE__ */ jsx("span", {
107
+ className: "ml-auto text-ppx-xs text-ppx-neutral-10",
108
+ children: "Enter to send · Shift+Enter for new line"
109
+ }),
110
+ /* @__PURE__ */ jsx(Button, {
111
+ type: "submit",
112
+ size: "icon-sm",
113
+ disabled: isLoading || !input.trim(),
114
+ className: "flex h-8 w-8 items-center justify-center rounded-full bg-ppx-green-5 text-white transition-all hover:bg-ppx-green-4 hover:shadow-[0_0_12px_rgba(40,182,116,0.6)] disabled:bg-ppx-neutral-5 disabled:text-ppx-neutral-10 disabled:shadow-none",
115
+ children: isLoading ? /* @__PURE__ */ jsx(StopIcon, { width: 14 }) : /* @__PURE__ */ jsx(SendIcon, { width: 16 })
116
+ })
117
+ ]
118
+ })
119
+ ]
120
+ }), suggestions.length > 0 && /* @__PURE__ */ jsx("div", {
121
+ className: "flex flex-wrap justify-center gap-2",
122
+ children: suggestions.map((suggestion, index) => /* @__PURE__ */ jsx(Button, {
123
+ type: "button",
124
+ variant: "outline",
125
+ size: "sm",
126
+ onClick: () => handleSuggestionClick(suggestion.prompt),
127
+ className: "animate-[popUp_0.3s_ease-out_forwards] rounded-full opacity-0",
128
+ style: { animationDelay: `${index * .1}s` },
129
+ children: suggestion.label
130
+ }, suggestion.id))
131
+ })]
132
+ });
133
+ }
134
+ function XIntakeTextarea({ value, onChange, onSubmit, placeholder, disabled }) {
135
+ const handleKeyDown = (e) => {
136
+ if (e.key === "Enter" && !e.shiftKey) {
137
+ e.preventDefault();
138
+ onSubmit();
139
+ }
140
+ };
141
+ return /* @__PURE__ */ jsx("textarea", {
142
+ className: "w-full resize-none border-none bg-transparent outline-none",
143
+ placeholder,
144
+ disabled,
145
+ value,
146
+ onChange: (e) => onChange(e.target.value),
147
+ onKeyDown: handleKeyDown
148
+ });
149
+ }
150
+
151
+ //#endregion
152
+ //#region src/assets/icons/chat-icon.tsx
153
+ function ChatIcon(props) {
154
+ return /* @__PURE__ */ jsx("svg", {
155
+ width: "18",
156
+ height: "18",
157
+ viewBox: "0 0 24 24",
158
+ fill: "none",
159
+ stroke: "currentColor",
160
+ strokeWidth: "2",
161
+ strokeLinecap: "round",
162
+ strokeLinejoin: "round",
163
+ ...props,
164
+ children: /* @__PURE__ */ jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })
165
+ });
166
+ }
167
+
168
+ //#endregion
169
+ //#region src/assets/icons/check-icon.tsx
170
+ function CheckIcon(props) {
171
+ return /* @__PURE__ */ jsx("svg", {
172
+ width: "16",
173
+ height: "16",
174
+ viewBox: "0 0 24 24",
175
+ fill: "none",
176
+ stroke: "currentColor",
177
+ strokeWidth: "2",
178
+ strokeLinecap: "round",
179
+ strokeLinejoin: "round",
180
+ ...props,
181
+ children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" })
182
+ });
183
+ }
184
+
185
+ //#endregion
186
+ //#region src/assets/icons/close-icon.tsx
187
+ function CloseIcon(props) {
188
+ return /* @__PURE__ */ jsxs("svg", {
189
+ width: "20",
190
+ height: "20",
191
+ viewBox: "0 0 24 24",
192
+ fill: "none",
193
+ stroke: "currentColor",
194
+ strokeWidth: "2",
195
+ strokeLinecap: "round",
196
+ strokeLinejoin: "round",
197
+ ...props,
198
+ children: [/* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }), /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })]
199
+ });
200
+ }
201
+
202
+ //#endregion
203
+ //#region src/assets/icons/copy-icon.tsx
204
+ function CopyIcon(props) {
205
+ return /* @__PURE__ */ jsxs("svg", {
206
+ width: "16",
207
+ height: "16",
208
+ viewBox: "0 0 24 24",
209
+ fill: "none",
210
+ stroke: "currentColor",
211
+ strokeWidth: "2",
212
+ strokeLinecap: "round",
213
+ strokeLinejoin: "round",
214
+ ...props,
215
+ children: [/* @__PURE__ */ jsx("rect", {
216
+ width: "14",
217
+ height: "14",
218
+ x: "8",
219
+ y: "8",
220
+ rx: "2",
221
+ ry: "2"
222
+ }), /* @__PURE__ */ jsx("path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" })]
223
+ });
224
+ }
225
+
226
+ //#endregion
227
+ //#region src/assets/icons/debug-icon.tsx
228
+ function DebugIcon(props) {
229
+ return /* @__PURE__ */ jsxs("svg", {
230
+ width: "16",
231
+ height: "16",
232
+ viewBox: "0 0 24 24",
233
+ fill: "none",
234
+ stroke: "currentColor",
235
+ strokeWidth: "2",
236
+ strokeLinecap: "round",
237
+ strokeLinejoin: "round",
238
+ ...props,
239
+ children: [/* @__PURE__ */ jsx("path", { d: "M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5c0 1.1.9 2 2 2h1" }), /* @__PURE__ */ jsx("path", { d: "M16 21h1a2 2 0 0 0 2-2v-5c0-1.1.9-2 2-2a2 2 0 0 1-2-2V5a2 2 0 0 0-2-2h-1" })]
240
+ });
241
+ }
242
+
243
+ //#endregion
244
+ //#region src/assets/icons/menu-icon.tsx
245
+ function MenuIcon(props) {
246
+ return /* @__PURE__ */ jsxs("svg", {
247
+ width: "20",
248
+ height: "20",
249
+ viewBox: "0 0 24 24",
250
+ fill: "none",
251
+ stroke: "currentColor",
252
+ strokeWidth: "2",
253
+ strokeLinecap: "round",
254
+ strokeLinejoin: "round",
255
+ ...props,
256
+ children: [
257
+ /* @__PURE__ */ jsx("line", {
258
+ x1: "4",
259
+ x2: "20",
260
+ y1: "12",
261
+ y2: "12"
262
+ }),
263
+ /* @__PURE__ */ jsx("line", {
264
+ x1: "4",
265
+ x2: "20",
266
+ y1: "6",
267
+ y2: "6"
268
+ }),
269
+ /* @__PURE__ */ jsx("line", {
270
+ x1: "4",
271
+ x2: "20",
272
+ y1: "18",
273
+ y2: "18"
274
+ })
275
+ ]
276
+ });
277
+ }
278
+
279
+ //#endregion
280
+ //#region src/assets/icons/new-chat-icon.tsx
281
+ function NewChatIcon(props) {
282
+ return /* @__PURE__ */ jsxs("svg", {
283
+ width: "20",
284
+ height: "20",
285
+ viewBox: "0 0 24 24",
286
+ fill: "none",
287
+ stroke: "currentColor",
288
+ strokeWidth: "2",
289
+ strokeLinecap: "round",
290
+ strokeLinejoin: "round",
291
+ ...props,
292
+ children: [/* @__PURE__ */ jsx("path", { d: "M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }), /* @__PURE__ */ jsx("path", { d: "M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z" })]
293
+ });
294
+ }
295
+
296
+ //#endregion
297
+ //#region src/assets/icons/sparkles-icon.tsx
298
+ function SparklesIcon(props) {
299
+ return /* @__PURE__ */ jsxs("svg", {
300
+ xmlns: "http://www.w3.org/2000/svg",
301
+ width: "20",
302
+ height: "20",
303
+ viewBox: "0 0 24 24",
304
+ fill: "none",
305
+ stroke: "currentColor",
306
+ strokeWidth: "2",
307
+ strokeLinecap: "round",
308
+ strokeLinejoin: "round",
309
+ ...props,
310
+ children: [
311
+ /* @__PURE__ */ jsx("path", { d: "M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z" }),
312
+ /* @__PURE__ */ jsx("path", { d: "M20 3v4" }),
313
+ /* @__PURE__ */ jsx("path", { d: "M22 5h-4" }),
314
+ /* @__PURE__ */ jsx("path", { d: "M4 17v2" }),
315
+ /* @__PURE__ */ jsx("path", { d: "M5 18H3" })
316
+ ]
317
+ });
318
+ }
319
+
320
+ //#endregion
321
+ //#region src/assets/icons/thumbs-down-icon.tsx
322
+ function ThumbsDownIcon(props) {
323
+ return /* @__PURE__ */ jsxs("svg", {
324
+ width: "16",
325
+ height: "16",
326
+ viewBox: "0 0 24 24",
327
+ fill: "none",
328
+ stroke: "currentColor",
329
+ strokeWidth: "2",
330
+ strokeLinecap: "round",
331
+ strokeLinejoin: "round",
332
+ ...props,
333
+ children: [/* @__PURE__ */ jsx("path", { d: "M17 14V2" }), /* @__PURE__ */ jsx("path", { d: "M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z" })]
334
+ });
335
+ }
336
+
337
+ //#endregion
338
+ //#region src/assets/icons/thumbs-up-icon.tsx
339
+ function ThumbsUpIcon(props) {
340
+ return /* @__PURE__ */ jsxs("svg", {
341
+ width: "16",
342
+ height: "16",
343
+ viewBox: "0 0 24 24",
344
+ fill: "none",
345
+ stroke: "currentColor",
346
+ strokeWidth: "2",
347
+ strokeLinecap: "round",
348
+ strokeLinejoin: "round",
349
+ ...props,
350
+ children: [/* @__PURE__ */ jsx("path", { d: "M7 10v12" }), /* @__PURE__ */ jsx("path", { d: "M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z" })]
351
+ });
352
+ }
353
+
354
+ //#endregion
355
+ //#region src/components/x-message-actions.tsx
356
+ var x_message_actions_exports = /* @__PURE__ */ __export({
357
+ Copy: () => Copy,
358
+ Debug: () => Debug,
359
+ Feedback: () => Feedback,
360
+ Root: () => Root
361
+ });
362
+ /**
363
+ * Container for message actions. Use with composable children:
364
+ * - XMessageActions.Feedback
365
+ * - XMessageActions.Copy
366
+ * - XMessageActions.Debug
367
+ */
368
+ function Root({ children }) {
369
+ return /* @__PURE__ */ jsx("div", {
370
+ className: "flex items-center gap-1",
371
+ children
372
+ });
373
+ }
374
+ function Feedback({ messageId }) {
375
+ const { onFeedback } = useXandi();
376
+ const [feedback, setFeedback] = useState(null);
377
+ const handleFeedback = (type) => {
378
+ const newFeedback = feedback === type ? null : type;
379
+ setFeedback(newFeedback);
380
+ onFeedback?.(messageId, newFeedback);
381
+ };
382
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Tooltip.Root, { children: [/* @__PURE__ */ jsx(Tooltip.Trigger, { render: /* @__PURE__ */ jsx(Button, {
383
+ variant: "ghost",
384
+ size: "icon-sm",
385
+ onClick: () => handleFeedback("up"),
386
+ className: `h-7 w-7 ${feedback === "up" ? "bg-ppx-green-2 text-ppx-green-5" : "text-ppx-neutral-10 hover:text-ppx-neutral-12"}`,
387
+ children: /* @__PURE__ */ jsx(ThumbsUpIcon, { className: feedback === "up" ? "fill-current" : "" })
388
+ }) }), /* @__PURE__ */ jsx(Tooltip.Content, { children: feedback === "up" ? "You found this helpful" : "Good response" })] }), /* @__PURE__ */ jsxs(Tooltip.Root, { children: [/* @__PURE__ */ jsx(Tooltip.Trigger, { render: /* @__PURE__ */ jsx(Button, {
389
+ variant: "ghost",
390
+ size: "icon-sm",
391
+ onClick: () => handleFeedback("down"),
392
+ className: `h-7 w-7 ${feedback === "down" ? "bg-ppx-red-2 text-ppx-red-5" : "text-ppx-neutral-10 hover:text-ppx-neutral-12"}`,
393
+ children: /* @__PURE__ */ jsx(ThumbsDownIcon, { className: feedback === "down" ? "fill-current" : "" })
394
+ }) }), /* @__PURE__ */ jsx(Tooltip.Content, { children: feedback === "down" ? "You found this unhelpful" : "Bad response" })] })] });
395
+ }
396
+ function Copy({ content }) {
397
+ const [copied, setCopied] = useState(false);
398
+ const handleCopy = async () => {
399
+ try {
400
+ await navigator.clipboard.writeText(content);
401
+ setCopied(true);
402
+ toast.add({
403
+ title: "Copied!",
404
+ description: "Message copied to clipboard",
405
+ type: "success"
406
+ });
407
+ setTimeout(() => setCopied(false), 2e3);
408
+ } catch {
409
+ toast.add({
410
+ title: "Failed to copy",
411
+ description: "Could not copy message to clipboard",
412
+ type: "error"
413
+ });
414
+ }
415
+ };
416
+ return /* @__PURE__ */ jsxs(Tooltip.Root, { children: [/* @__PURE__ */ jsx(Tooltip.Trigger, { render: /* @__PURE__ */ jsx(Button, {
417
+ variant: "ghost",
418
+ size: "icon-sm",
419
+ onClick: handleCopy,
420
+ className: "h-7 w-7 text-ppx-neutral-10 hover:text-ppx-neutral-12",
421
+ children: copied ? /* @__PURE__ */ jsx(CheckIcon, { className: "text-ppx-green-5" }) : /* @__PURE__ */ jsx(CopyIcon, {})
422
+ }) }), /* @__PURE__ */ jsx(Tooltip.Content, { children: copied ? "Copied!" : "Copy message" })] });
423
+ }
424
+ function Debug({ messageId, debugTrace }) {
425
+ const [debugOpen, setDebugOpen] = useState(false);
426
+ return /* @__PURE__ */ jsxs(Dialog.Root, {
427
+ open: debugOpen,
428
+ onOpenChange: setDebugOpen,
429
+ children: [/* @__PURE__ */ jsxs(Tooltip.Root, { children: [/* @__PURE__ */ jsx(Tooltip.Trigger, { render: /* @__PURE__ */ jsx(Dialog.Trigger, { render: /* @__PURE__ */ jsx(Button, {
430
+ variant: "ghost",
431
+ size: "icon-sm",
432
+ className: "h-7 w-7 text-ppx-neutral-10 hover:text-ppx-neutral-12",
433
+ children: /* @__PURE__ */ jsx(DebugIcon, {})
434
+ }) }) }), /* @__PURE__ */ jsx(Tooltip.Content, { children: "View debug trace" })] }), /* @__PURE__ */ jsxs(Dialog.Portal, { children: [/* @__PURE__ */ jsx(Dialog.Overlay, {}), /* @__PURE__ */ jsxs(Dialog.Content, {
435
+ className: "max-w-2xl",
436
+ children: [
437
+ /* @__PURE__ */ jsxs(Dialog.Header, { children: [/* @__PURE__ */ jsx(Dialog.Title, { children: "Debug Trace" }), /* @__PURE__ */ jsxs(Dialog.Description, { children: ["Response debug information for message ", messageId] })] }),
438
+ /* @__PURE__ */ jsx("div", {
439
+ className: "max-h-96 overflow-auto rounded bg-ppx-neutral-2 p-4",
440
+ children: /* @__PURE__ */ jsx("pre", {
441
+ className: "whitespace-pre-wrap font-mono text-ppx-xs text-ppx-neutral-12",
442
+ children: JSON.stringify(debugTrace, null, 2)
443
+ })
444
+ }),
445
+ /* @__PURE__ */ jsx(Dialog.Footer, { children: /* @__PURE__ */ jsx(Dialog.Close, { render: /* @__PURE__ */ jsx(Button, {
446
+ variant: "outline",
447
+ children: "Close"
448
+ }) }) })
449
+ ]
450
+ })] })]
451
+ });
452
+ }
453
+
454
+ //#endregion
455
+ //#region src/components/renderers/markdown-renderer.tsx
456
+ function MarkdownRenderer({ message }) {
457
+ const isUser = message.role === "user";
458
+ const baseClass = `text-ppx-sm leading-relaxed ${isUser ? "text-white" : "text-ppx-neutral-13"}`;
459
+ const showActions = !isUser;
460
+ return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
461
+ className: baseClass,
462
+ children: /* @__PURE__ */ jsx(ReactMarkdown, {
463
+ components: {
464
+ p: ({ children }) => /* @__PURE__ */ jsx("p", {
465
+ className: "mb-2 last:mb-0",
466
+ children
467
+ }),
468
+ ul: ({ children }) => /* @__PURE__ */ jsx("ul", {
469
+ className: "mb-2 list-disc pl-4",
470
+ children
471
+ }),
472
+ ol: ({ children }) => /* @__PURE__ */ jsx("ol", {
473
+ className: "mb-2 list-decimal pl-4",
474
+ children
475
+ }),
476
+ li: ({ children }) => /* @__PURE__ */ jsx("li", {
477
+ className: "mb-1",
478
+ children
479
+ }),
480
+ a: ({ href, children }) => /* @__PURE__ */ jsx("a", {
481
+ href,
482
+ className: "underline",
483
+ target: "_blank",
484
+ rel: "noopener noreferrer",
485
+ children
486
+ }),
487
+ strong: ({ children }) => /* @__PURE__ */ jsx("strong", {
488
+ className: "font-semibold",
489
+ children
490
+ })
491
+ },
492
+ children: message.content
493
+ })
494
+ }), showActions && /* @__PURE__ */ jsx("div", {
495
+ className: "mt-2",
496
+ children: /* @__PURE__ */ jsxs(Root, { children: [
497
+ /* @__PURE__ */ jsx(Feedback, { messageId: message.id }),
498
+ /* @__PURE__ */ jsx(Copy, { content: message.content }),
499
+ message.debugTrace != null && /* @__PURE__ */ jsx(Debug, {
500
+ messageId: message.id,
501
+ debugTrace: message.debugTrace
502
+ })
503
+ ] })
504
+ })] });
505
+ }
506
+
507
+ //#endregion
508
+ //#region src/components/renderers/text-renderer.tsx
509
+ function TextRenderer({ message }) {
510
+ const isUser = message.role === "user";
511
+ const showActions = !isUser;
512
+ return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
513
+ className: `text-ppx-sm leading-relaxed ${isUser ? "text-white" : "text-ppx-neutral-13"}`,
514
+ children: message.content
515
+ }), showActions && /* @__PURE__ */ jsx("div", {
516
+ className: "mt-2",
517
+ children: /* @__PURE__ */ jsxs(Root, { children: [
518
+ /* @__PURE__ */ jsx(Feedback, { messageId: message.id }),
519
+ /* @__PURE__ */ jsx(Copy, { content: message.content }),
520
+ message.debugTrace != null && /* @__PURE__ */ jsx(Debug, {
521
+ messageId: message.id,
522
+ debugTrace: message.debugTrace
523
+ })
524
+ ] })
525
+ })] });
526
+ }
527
+
528
+ //#endregion
529
+ //#region src/components/x-message-item.tsx
530
+ /**
531
+ * Router component that renders messages based on their type.
532
+ * Defaults to markdown rendering if no type is specified.
533
+ */
534
+ function XMessageItem({ message }) {
535
+ const isUser = message.role === "user";
536
+ const messageType = message.type ?? "markdown";
537
+ return /* @__PURE__ */ jsx("div", {
538
+ className: `flex ${isUser ? "justify-end" : "justify-start"}`,
539
+ children: isUser ? /* @__PURE__ */ jsx("div", {
540
+ className: "max-w-[90%] rounded-2xl rounded-br-sm bg-ppx-green-5 px-4 py-2.5",
541
+ children: /* @__PURE__ */ jsx(MessageRenderer, {
542
+ type: messageType,
543
+ message
544
+ })
545
+ }) : /* @__PURE__ */ jsx("div", {
546
+ className: "max-w-[90%]",
547
+ children: /* @__PURE__ */ jsx(MessageRenderer, {
548
+ type: messageType,
549
+ message
550
+ })
551
+ })
552
+ });
553
+ }
554
+ /**
555
+ * Switch component that selects the appropriate renderer based on message type.
556
+ */
557
+ function MessageRenderer({ type, message }) {
558
+ switch (type) {
559
+ case "text": return /* @__PURE__ */ jsx(TextRenderer, { message });
560
+ case "markdown":
561
+ default: return /* @__PURE__ */ jsx(MarkdownRenderer, { message });
562
+ }
563
+ }
564
+
565
+ //#endregion
566
+ //#region src/constants.ts
567
+ const XANDI_AVATAR_URL = new URL("./assets/images/xandi-avatar.png", import.meta.url).href;
568
+
569
+ //#endregion
570
+ //#region src/components/x-typing-indicator.tsx
571
+ function XTypingIndicator() {
572
+ return /* @__PURE__ */ jsxs("div", {
573
+ className: "flex items-center gap-4",
574
+ children: [/* @__PURE__ */ jsx("div", {
575
+ className: "animate-[popUp_0.3s_ease-out_forwards]",
576
+ children: /* @__PURE__ */ jsx(Avatar, {
577
+ imgSrc: XANDI_AVATAR_URL,
578
+ name: "Xandi",
579
+ variant: "rounded",
580
+ size: "48px",
581
+ hideTooltip: true,
582
+ className: "border-2 border-ppx-neutral-4"
583
+ })
584
+ }), /* @__PURE__ */ jsxs("div", {
585
+ className: "flex animate-[slideIn_0.3s_ease-out_0.2s_forwards] items-center gap-2 rounded-xl bg-ppx-neutral-2 px-[10px] pb-[5px] pt-[10px] opacity-0 shadow-sm",
586
+ children: [
587
+ /* @__PURE__ */ jsx("span", { className: "h-[12px] w-[12px] animate-bounce rounded-full bg-ppx-neutral-9 [animation-delay:-0.3s]" }),
588
+ /* @__PURE__ */ jsx("span", { className: "h-[12px] w-[12px] animate-bounce rounded-full bg-ppx-neutral-6 [animation-delay:-0.15s]" }),
589
+ /* @__PURE__ */ jsx("span", { className: "h-[12px] w-[12px] animate-bounce rounded-full bg-ppx-neutral-9" })
590
+ ]
591
+ })]
592
+ });
593
+ }
594
+
595
+ //#endregion
596
+ //#region src/components/x-message-container.tsx
597
+ function XMessageContainer({ height = 400 }) {
598
+ const { messages, isLoading } = useXandi();
599
+ const containerRef = useRef(null);
600
+ useEffect(() => {
601
+ if (containerRef.current) containerRef.current.scrollTop = containerRef.current.scrollHeight;
602
+ }, [messages, isLoading]);
603
+ return /* @__PURE__ */ jsx("div", {
604
+ ref: containerRef,
605
+ className: "overflow-y-auto py-[10px]",
606
+ style: { height: typeof height === "number" ? `${height}px` : height },
607
+ children: /* @__PURE__ */ jsxs("div", {
608
+ className: "flex flex-col gap-5 p-4",
609
+ children: [messages.map((message) => /* @__PURE__ */ jsx(XMessageItem, { message }, message.id)), isLoading && /* @__PURE__ */ jsx(XTypingIndicator, {})]
610
+ })
611
+ });
612
+ }
613
+
614
+ //#endregion
615
+ //#region src/components/x-welcome.tsx
616
+ function XWelcome({ message }) {
617
+ return /* @__PURE__ */ jsxs("div", {
618
+ className: "flex flex-col items-center justify-center gap-4 py-12",
619
+ children: [/* @__PURE__ */ jsxs("div", {
620
+ className: "relative",
621
+ children: [
622
+ /* @__PURE__ */ jsx("div", { className: "absolute -inset-1 rounded-full bg-gradient-to-b from-ppx-green-4 via-ppx-green-5/50 to-transparent" }),
623
+ /* @__PURE__ */ jsx("div", {
624
+ className: "relative rounded-full bg-ppx-neutral-18 p-1",
625
+ children: /* @__PURE__ */ jsx(Avatar, {
626
+ imgSrc: XANDI_AVATAR_URL,
627
+ name: "Xandi",
628
+ variant: "rounded",
629
+ size: "120px",
630
+ hideTooltip: true
631
+ })
632
+ }),
633
+ /* @__PURE__ */ jsx("div", {
634
+ className: "absolute -bottom-2 left-1/2",
635
+ children: /* @__PURE__ */ jsx("div", {
636
+ className: "flex h-6 w-6 animate-[pulse-zoom_2s_ease-in-out_infinite] items-center justify-center rounded-full bg-ppx-green-5",
637
+ children: /* @__PURE__ */ jsx(SparklesIcon, { className: "text-white" })
638
+ })
639
+ })
640
+ ]
641
+ }), /* @__PURE__ */ jsx("h2", {
642
+ className: "text-ppx-xl font-semibold text-ppx-foreground",
643
+ children: message
644
+ })]
645
+ });
646
+ }
647
+
648
+ //#endregion
649
+ //#region src/components/xandi.tsx
650
+ function Xandi({ welcomeMessage = "How can I help you today?", suggestions = [] }) {
651
+ const { messages, isLoading } = useXandi();
652
+ const isEmpty = messages.length === 0;
653
+ return /* @__PURE__ */ jsxs("div", {
654
+ className: "flex flex-col",
655
+ children: [isEmpty ? /* @__PURE__ */ jsx(XWelcome, { message: welcomeMessage }) : /* @__PURE__ */ jsx(XMessageContainer, {}), /* @__PURE__ */ jsx(XMainIntake, { suggestions: isEmpty ? suggestions : [] })]
656
+ });
657
+ }
658
+
659
+ //#endregion
660
+ //#region src/components/x-header.tsx
661
+ function XHeader({ title = "Xandi", onClose, onNewChat, onToggleHistory }) {
662
+ return /* @__PURE__ */ jsxs("header", {
663
+ className: "flex items-center justify-between border-b border-ppx-neutral-5 bg-ppx-neutral-2 px-3 py-2",
664
+ children: [/* @__PURE__ */ jsxs("div", {
665
+ className: "flex items-center gap-2",
666
+ children: [/* @__PURE__ */ jsx(Button, {
667
+ variant: "ghost",
668
+ size: "icon-sm",
669
+ onClick: onToggleHistory,
670
+ "aria-label": "Toggle chat history",
671
+ children: /* @__PURE__ */ jsx(MenuIcon, {})
672
+ }), /* @__PURE__ */ jsxs("div", {
673
+ className: "flex items-center gap-2",
674
+ children: [/* @__PURE__ */ jsx(Avatar, {
675
+ imgSrc: XANDI_AVATAR_URL,
676
+ name: "Xandi",
677
+ variant: "rounded",
678
+ size: "24px",
679
+ hideTooltip: true
680
+ }), /* @__PURE__ */ jsx("span", {
681
+ className: "font-medium text-ppx-foreground",
682
+ children: title
683
+ })]
684
+ })]
685
+ }), /* @__PURE__ */ jsxs("div", {
686
+ className: "flex items-center gap-1",
687
+ children: [/* @__PURE__ */ jsx(Button, {
688
+ variant: "ghost",
689
+ size: "icon-sm",
690
+ onClick: onNewChat,
691
+ "aria-label": "New chat",
692
+ children: /* @__PURE__ */ jsx(NewChatIcon, {})
693
+ }), /* @__PURE__ */ jsx(Button, {
694
+ variant: "ghost",
695
+ size: "icon-sm",
696
+ onClick: onClose,
697
+ "aria-label": "Close",
698
+ children: /* @__PURE__ */ jsx(CloseIcon, {})
699
+ })]
700
+ })]
701
+ });
702
+ }
703
+
704
+ //#endregion
705
+ //#region src/components/x-chat-history.tsx
706
+ function XChatHistory({ groups = [], activeChatId, onSelectChat }) {
707
+ return /* @__PURE__ */ jsxs("div", {
708
+ className: "flex-1 overflow-y-auto",
709
+ children: [/* @__PURE__ */ jsx("div", {
710
+ className: "px-3 py-2",
711
+ children: /* @__PURE__ */ jsxs("div", {
712
+ className: "flex items-center gap-2 text-ppx-sm font-medium text-ppx-foreground",
713
+ children: [/* @__PURE__ */ jsx(ChatIcon, {}), "Chats"]
714
+ })
715
+ }), groups.length === 0 ? /* @__PURE__ */ jsx("div", {
716
+ className: "px-6 py-4 text-ppx-sm text-ppx-neutral-10",
717
+ children: "No chat history yet"
718
+ }) : groups.map((group) => /* @__PURE__ */ jsxs("div", {
719
+ className: "mb-2",
720
+ children: [/* @__PURE__ */ jsx("div", {
721
+ className: "px-6 py-2 text-ppx-xs font-medium text-ppx-neutral-10",
722
+ children: group.label
723
+ }), /* @__PURE__ */ jsx("div", {
724
+ className: "space-y-0.5",
725
+ children: group.items.map((item) => /* @__PURE__ */ jsx(Button, {
726
+ variant: "ghost",
727
+ onClick: () => onSelectChat?.(item.id),
728
+ className: `w-full justify-start truncate rounded-none px-6 ${activeChatId === item.id ? "bg-ppx-neutral-4 text-ppx-foreground" : "text-ppx-neutral-12"}`,
729
+ children: item.title
730
+ }, item.id))
731
+ })]
732
+ }, group.label))]
733
+ });
734
+ }
735
+
736
+ //#endregion
737
+ //#region src/components/x-sidebar.tsx
738
+ function XSidebar({ isOpen = true, chatHistory = [], activeChatId, onClose, onNewChat, onSelectChat }) {
739
+ if (!isOpen) return null;
740
+ return /* @__PURE__ */ jsxs("aside", {
741
+ className: "flex h-full w-64 flex-col border-r border-ppx-neutral-5 bg-ppx-neutral-2",
742
+ children: [
743
+ /* @__PURE__ */ jsx("div", {
744
+ className: "flex items-center justify-between border-b border-ppx-neutral-5 p-3",
745
+ children: /* @__PURE__ */ jsx(Button, {
746
+ variant: "ghost",
747
+ size: "icon-sm",
748
+ onClick: onClose,
749
+ "aria-label": "Close sidebar",
750
+ children: /* @__PURE__ */ jsx(CloseIcon, {})
751
+ })
752
+ }),
753
+ /* @__PURE__ */ jsx("div", {
754
+ className: "p-3",
755
+ children: /* @__PURE__ */ jsxs(Button, {
756
+ variant: "ghost",
757
+ onClick: onNewChat,
758
+ className: "w-full justify-start gap-3",
759
+ children: [/* @__PURE__ */ jsx(NewChatIcon, { className: "h-5 w-5" }), "New chat"]
760
+ })
761
+ }),
762
+ /* @__PURE__ */ jsx(XChatHistory, {
763
+ groups: chatHistory,
764
+ activeChatId,
765
+ onSelectChat
766
+ })
767
+ ]
768
+ });
769
+ }
770
+
771
+ //#endregion
772
+ export { XChatHistory, XHeader, x_message_actions_exports as XMessageActions, XSidebar, Xandi, XandiProvider, useXandi };
773
+ //# sourceMappingURL=index.js.map