@px-ui/ai 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -10,28 +10,110 @@ interface Message {
10
10
  type?: MessageType;
11
11
  debugTrace?: unknown;
12
12
  }
13
+ /** API response structure from the backend */
14
+ interface XandiApiResponse {
15
+ success: boolean;
16
+ message: string;
17
+ data: {
18
+ intent: string;
19
+ data: unknown;
20
+ conversation_id: string;
21
+ };
22
+ trace?: {
23
+ trace_id: string;
24
+ execution_mode: string;
25
+ intent: string;
26
+ tool_id: string;
27
+ debug_trace: unknown;
28
+ };
29
+ }
30
+ /** Transformed response for internal use */
13
31
  interface XandiResponse {
14
32
  content: string;
15
33
  type?: MessageType;
16
34
  debugTrace?: unknown;
35
+ conversationId?: string;
17
36
  }
18
- interface XandiContextValue {
37
+ /** Conversation summary for history list */
38
+ interface ConversationSummary {
39
+ id: string;
40
+ title: string;
41
+ timestamp: Date;
42
+ }
43
+ /** Full conversation with messages */
44
+ interface Conversation {
45
+ id: string | null;
46
+ title: string;
19
47
  messages: Message[];
48
+ createdAt: Date;
49
+ updatedAt: Date;
50
+ }
51
+ type XandiUIMode = "full" | "sidebar" | "floating";
52
+ interface XandiConfig {
53
+ /** URL for the assistant's avatar image */
54
+ avatarUrl?: string;
55
+ /** Name of the assistant (default: "Xandi") */
56
+ assistantName?: string;
57
+ /** UI mode: full (no close button), sidebar, or floating (with close button) */
58
+ uiMode?: XandiUIMode;
59
+ }
60
+ interface FetchRespOptions {
61
+ conversationId?: string;
62
+ signal?: AbortSignal;
63
+ }
64
+ interface GetConvOptions {
65
+ /** Page number (default: 1) */
66
+ page?: number;
67
+ /** Items per page (default: 20) */
68
+ perPage?: number;
69
+ }
70
+ interface XandiHandlers {
71
+ /** Fetch AI response for a message */
72
+ fetchResp: (message: string, options?: FetchRespOptions) => Promise<XandiResponse>;
73
+ /** Get a conversation by ID with pagination; consumer transforms API response to Conversation */
74
+ getConv?: (conversationId: string, options?: GetConvOptions) => Promise<Conversation>;
75
+ /** Get conversation history list */
76
+ getConvHistory?: () => Promise<ConversationSummary[]>;
77
+ /** Called when user provides feedback on a message */
78
+ onFeedback?: (messageId: string, conversationId: string, feedback: FeedbackType) => void;
79
+ /** Called when user stops the current request */
80
+ onStop?: (conversationId: string) => void;
81
+ }
82
+ interface XandiContextValue {
83
+ /** Current conversation with messages */
84
+ conversation: Conversation;
85
+ /** Whether a request is in progress */
20
86
  isLoading: boolean;
21
- sessionId: string | null;
87
+ /** Send a message to the assistant */
22
88
  sendMessage: (text: string) => void;
23
- onFeedback?: (messageId: string, feedback: FeedbackType) => void;
89
+ /** Stop the current request */
90
+ stopRequest: () => void;
91
+ /** Load an existing conversation by ID (with optional page/perPage) */
92
+ loadConversation: (conversationId: string, options?: GetConvOptions) => Promise<void>;
93
+ /** Start a new empty conversation */
94
+ startNewConversation: () => void;
95
+ /** Submit feedback for a message */
96
+ submitFeedback: (messageId: string, feedback: FeedbackType) => void;
97
+ /** Get conversation history list (from handlers) */
98
+ getConvHistory?: () => Promise<ConversationSummary[]>;
99
+ /** Configuration for the assistant */
100
+ config: Required<XandiConfig>;
101
+ /** Set UI mode override from Xandi component (internal use) */
102
+ setUiModeOverride: (mode: XandiUIMode | null) => void;
24
103
  }
25
104
  interface XandiProviderProps {
26
- fetchResponse: (message: string) => Promise<XandiResponse>;
27
- sessionId?: string;
28
- onFeedback?: (messageId: string, feedback: FeedbackType) => void;
105
+ /** All handler functions for API communication */
106
+ handlers: XandiHandlers;
107
+ /** Initial conversation ID to restore */
108
+ conversationId?: string;
109
+ /** Configuration for the assistant appearance */
110
+ config?: XandiConfig;
29
111
  children: React.ReactNode;
30
112
  }
31
113
  declare function XandiProvider({
32
- fetchResponse,
33
- sessionId: initialSessionId,
34
- onFeedback,
114
+ handlers,
115
+ conversationId: initialConversationId,
116
+ config: userConfig,
35
117
  children
36
118
  }: XandiProviderProps): react_jsx_runtime0.JSX.Element;
37
119
  declare function useXandi(): XandiContextValue;
@@ -47,64 +129,57 @@ interface Suggestion {
47
129
  interface XandiProps {
48
130
  welcomeMessage?: string;
49
131
  suggestions?: Suggestion[];
132
+ /**
133
+ * UI mode: "full" (default) = no close button; "sidebar" | "floating" = show close button in header.
134
+ * Overrides provider config when set.
135
+ */
136
+ uiMode?: XandiUIMode;
50
137
  }
51
138
  declare function Xandi({
52
139
  welcomeMessage,
53
- suggestions
140
+ suggestions,
141
+ uiMode
54
142
  }: XandiProps): react_jsx_runtime0.JSX.Element;
55
143
  //#endregion
56
144
  //#region src/components/x-header.d.ts
57
145
  interface XHeaderProps {
58
- title?: string;
59
146
  onClose?: () => void;
60
- onNewChat?: () => void;
61
147
  onToggleHistory?: () => void;
62
148
  }
63
149
  declare function XHeader({
64
- title,
65
150
  onClose,
66
- onNewChat,
67
151
  onToggleHistory
68
152
  }: XHeaderProps): react_jsx_runtime0.JSX.Element;
69
153
  //#endregion
154
+ //#region src/components/x-sidebar.d.ts
155
+ interface XSidebarProps {
156
+ isOpen?: boolean;
157
+ onClose?: () => void;
158
+ }
159
+ declare function XSidebar({
160
+ isOpen,
161
+ onClose
162
+ }: XSidebarProps): react_jsx_runtime0.JSX.Element | null;
163
+ //#endregion
70
164
  //#region src/components/x-chat-history.d.ts
71
165
  interface ChatHistoryItem {
72
166
  id: string;
73
167
  title: string;
74
168
  timestamp: Date;
75
169
  }
76
- interface ChatHistoryGroup {
77
- label: string;
78
- items: ChatHistoryItem[];
79
- }
80
170
  interface XChatHistoryProps {
81
- groups?: ChatHistoryGroup[];
171
+ items?: ChatHistoryItem[];
172
+ /** Whether conversation history is being fetched */
173
+ isLoading?: boolean;
82
174
  activeChatId?: string;
83
175
  onSelectChat?: (chatId: string) => void;
84
176
  }
85
177
  declare function XChatHistory({
86
- groups,
178
+ items,
179
+ isLoading,
87
180
  activeChatId,
88
181
  onSelectChat
89
182
  }: XChatHistoryProps): react_jsx_runtime0.JSX.Element;
90
- //#endregion
91
- //#region src/components/x-sidebar.d.ts
92
- interface XSidebarProps {
93
- isOpen?: boolean;
94
- chatHistory?: ChatHistoryGroup[];
95
- activeChatId?: string;
96
- onClose?: () => void;
97
- onNewChat?: () => void;
98
- onSelectChat?: (chatId: string) => void;
99
- }
100
- declare function XSidebar({
101
- isOpen,
102
- chatHistory,
103
- activeChatId,
104
- onClose,
105
- onNewChat,
106
- onSelectChat
107
- }: XSidebarProps): react_jsx_runtime0.JSX.Element | null;
108
183
  declare namespace x_message_actions_d_exports {
109
184
  export { Copy, CopyProps, Debug, DebugProps, Feedback, FeedbackProps, FeedbackType, Root };
110
185
  }
@@ -140,5 +215,5 @@ declare function Debug({
140
215
  debugTrace
141
216
  }: DebugProps): react_jsx_runtime0.JSX.Element;
142
217
  //#endregion
143
- export { type ChatHistoryGroup, type ChatHistoryItem, type FeedbackType, type Message, type MessageType, type Suggestion, XChatHistory, type XChatHistoryProps, XHeader, type XHeaderProps, x_message_actions_d_exports as XMessageActions, XSidebar, type XSidebarProps, Xandi, type XandiProps, XandiProvider, type XandiProviderProps, type XandiResponse, useXandi };
218
+ export { type ChatHistoryItem, type Conversation, type ConversationSummary, type FeedbackType, type FetchRespOptions, type GetConvOptions, type Message, type MessageType, type Suggestion, XChatHistory, type XChatHistoryProps, XHeader, type XHeaderProps, x_message_actions_d_exports as XMessageActions, XSidebar, type XSidebarProps, Xandi, type XandiApiResponse, type XandiConfig, type XandiContextValue, type XandiHandlers, type XandiProps, XandiProvider, type XandiProviderProps, type XandiResponse, type XandiUIMode, useXandi };
144
219
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/context/xandi-context.tsx","../src/components/x-main-intake.tsx","../src/components/xandi.tsx","../src/components/x-header.tsx","../src/components/x-chat-history.tsx","../src/components/x-sidebar.tsx","../src/components/x-message-actions.tsx"],"sourcesContent":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/context/xandi-context.tsx","../src/components/x-main-intake.tsx","../src/components/xandi.tsx","../src/components/x-header.tsx","../src/components/x-sidebar.tsx","../src/components/x-chat-history.tsx","../src/components/x-message-actions.tsx"],"sourcesContent":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,18 +1,72 @@
1
1
  import { t as __export } from "./chunk-BAz01cYq.js";
2
- import { createContext, useContext, useEffect, useRef, useState } from "react";
2
+ import { createContext, useContext, useEffect, useLayoutEffect, useRef, useState } from "react";
3
3
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
- import { Avatar, Button, Dialog, FileIcon, SendIcon, StopIcon, Tooltip, toast } from "@px-ui/core";
4
+ import { Avatar, Button, Dialog, FileIcon, SendIcon, Spinner, StopIcon, Tooltip, toast } from "@px-ui/core";
5
5
  import ReactMarkdown from "react-markdown";
6
6
 
7
+ //#region src/constants.ts
8
+ function getXandiAvatarUrl() {
9
+ try {
10
+ return new URL("./assets/images/xandi-avatar.png", import.meta.url).href;
11
+ } catch {
12
+ return "";
13
+ }
14
+ }
15
+ const XANDI_AVATAR_URL = getXandiAvatarUrl();
16
+
17
+ //#endregion
7
18
  //#region src/context/xandi-context.tsx
19
+ /** Creates a new empty conversation */
20
+ function createEmptyConversation() {
21
+ return {
22
+ id: null,
23
+ title: "New Chat",
24
+ messages: [],
25
+ createdAt: /* @__PURE__ */ new Date(),
26
+ updatedAt: /* @__PURE__ */ new Date()
27
+ };
28
+ }
29
+ const defaultConfig = {
30
+ avatarUrl: XANDI_AVATAR_URL,
31
+ assistantName: "Xandi",
32
+ uiMode: "full"
33
+ };
34
+ const defaultGetConvOptions = {
35
+ page: 1,
36
+ perPage: 20
37
+ };
8
38
  const XandiContext = createContext(null);
9
- function XandiProvider({ fetchResponse, sessionId: initialSessionId, onFeedback, children }) {
10
- const [sessionId, setSessionId] = useState(initialSessionId ?? null);
11
- const [messages, setMessages] = useState([]);
39
+ function XandiProvider({ handlers, conversationId: initialConversationId, config: userConfig, children }) {
40
+ const [conversation, setConversation] = useState(createEmptyConversation);
12
41
  const [isLoading, setIsLoading] = useState(false);
42
+ const [uiModeOverride, setUiModeOverride] = useState(null);
43
+ const abortControllerRef = useRef(null);
44
+ const config = {
45
+ ...defaultConfig,
46
+ ...userConfig,
47
+ uiMode: uiModeOverride ?? userConfig?.uiMode ?? defaultConfig.uiMode
48
+ };
13
49
  useEffect(() => {
14
- if (!initialSessionId) setSessionId(crypto.randomUUID());
15
- }, [initialSessionId]);
50
+ if (initialConversationId && handlers.getConv) loadConversation(initialConversationId);
51
+ }, [initialConversationId]);
52
+ const loadConversation = async (convId, options) => {
53
+ if (!handlers.getConv) return;
54
+ const opts = {
55
+ ...defaultGetConvOptions,
56
+ ...options
57
+ };
58
+ try {
59
+ setIsLoading(true);
60
+ setConversation(await handlers.getConv(convId, opts));
61
+ } catch (error) {
62
+ console.error("Failed to load conversation:", error);
63
+ } finally {
64
+ setIsLoading(false);
65
+ }
66
+ };
67
+ const startNewConversation = () => {
68
+ setConversation(createEmptyConversation());
69
+ };
16
70
  const sendMessage = async (text) => {
17
71
  if (!text.trim() || isLoading) return;
18
72
  const userMessage = {
@@ -20,10 +74,18 @@ function XandiProvider({ fetchResponse, sessionId: initialSessionId, onFeedback,
20
74
  role: "user",
21
75
  content: text
22
76
  };
23
- setMessages((prev) => [...prev, userMessage]);
77
+ setConversation((prev) => ({
78
+ ...prev,
79
+ messages: [...prev.messages, userMessage],
80
+ updatedAt: /* @__PURE__ */ new Date()
81
+ }));
24
82
  setIsLoading(true);
83
+ abortControllerRef.current = new AbortController();
25
84
  try {
26
- const response = await fetchResponse(text);
85
+ const response = await handlers.fetchResp(text, {
86
+ conversationId: conversation.id ?? void 0,
87
+ signal: abortControllerRef.current.signal
88
+ });
27
89
  const assistantMessage = {
28
90
  id: crypto.randomUUID(),
29
91
  role: "assistant",
@@ -31,19 +93,41 @@ function XandiProvider({ fetchResponse, sessionId: initialSessionId, onFeedback,
31
93
  type: response.type,
32
94
  debugTrace: response.debugTrace
33
95
  };
34
- setMessages((prev) => [...prev, assistantMessage]);
96
+ setConversation((prev) => ({
97
+ ...prev,
98
+ id: response.conversationId ?? prev.id,
99
+ messages: [...prev.messages, assistantMessage],
100
+ updatedAt: /* @__PURE__ */ new Date()
101
+ }));
35
102
  } catch (error) {
36
- console.error("Failed to send message:", error);
103
+ if (error.name !== "AbortError") console.error("Failed to send message:", error);
37
104
  } finally {
38
105
  setIsLoading(false);
106
+ abortControllerRef.current = null;
107
+ }
108
+ };
109
+ const stopRequest = () => {
110
+ if (abortControllerRef.current) {
111
+ abortControllerRef.current.abort();
112
+ abortControllerRef.current = null;
39
113
  }
114
+ if (conversation.id && handlers.onStop) handlers.onStop(conversation.id);
115
+ setIsLoading(false);
116
+ };
117
+ const submitFeedback = (messageId, feedback) => {
118
+ if (handlers.onFeedback && conversation.id) handlers.onFeedback(messageId, conversation.id, feedback);
40
119
  };
41
120
  const value = {
42
- messages,
121
+ conversation,
43
122
  isLoading,
44
- sessionId,
45
123
  sendMessage,
46
- onFeedback
124
+ stopRequest,
125
+ loadConversation,
126
+ startNewConversation,
127
+ submitFeedback,
128
+ getConvHistory: handlers.getConvHistory,
129
+ config,
130
+ setUiModeOverride
47
131
  };
48
132
  return /* @__PURE__ */ jsx(XandiContext.Provider, {
49
133
  value,
@@ -59,7 +143,7 @@ function useXandi() {
59
143
  //#endregion
60
144
  //#region src/components/x-main-intake.tsx
61
145
  function XMainIntake({ placeholder = "Ask about jobs, candidates, timesheets, or anything workforce...", suggestions = [] }) {
62
- const { isLoading, sendMessage } = useXandi();
146
+ const { isLoading, sendMessage, stopRequest } = useXandi();
63
147
  const [input, setInput] = useState("");
64
148
  const handleSubmit = (e) => {
65
149
  e?.preventDefault();
@@ -71,6 +155,9 @@ function XMainIntake({ placeholder = "Ask about jobs, candidates, timesheets, or
71
155
  const handleSuggestionClick = (prompt) => {
72
156
  setInput(prompt);
73
157
  };
158
+ const handleStopOrSend = () => {
159
+ if (isLoading) stopRequest();
160
+ };
74
161
  return /* @__PURE__ */ jsxs("div", {
75
162
  className: "flex flex-col gap-3",
76
163
  children: [/* @__PURE__ */ jsxs("form", {
@@ -99,12 +186,18 @@ function XMainIntake({ placeholder = "Ask about jobs, candidates, timesheets, or
99
186
  className: "ml-auto text-ppx-xs text-ppx-neutral-10",
100
187
  children: "Enter to send · Shift+Enter for new line"
101
188
  }),
102
- /* @__PURE__ */ jsx(Button, {
189
+ isLoading ? /* @__PURE__ */ jsx(Button, {
190
+ type: "button",
191
+ size: "icon-sm",
192
+ onClick: handleStopOrSend,
193
+ className: "flex h-8 w-8 items-center justify-center rounded-full bg-ppx-red-5 text-white transition-all hover:bg-ppx-red-4 hover:shadow-[0_0_12px_rgba(220,38,38,0.6)]",
194
+ children: /* @__PURE__ */ jsx(StopIcon, { width: 14 })
195
+ }) : /* @__PURE__ */ jsx(Button, {
103
196
  type: "submit",
104
197
  size: "icon-sm",
105
- disabled: isLoading || !input.trim(),
198
+ disabled: !input.trim(),
106
199
  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",
107
- children: isLoading ? /* @__PURE__ */ jsx(StopIcon, { width: 14 }) : /* @__PURE__ */ jsx(SendIcon, { width: 16 })
200
+ children: /* @__PURE__ */ jsx(SendIcon, { width: 16 })
108
201
  })
109
202
  ]
110
203
  })
@@ -364,12 +457,12 @@ function Root({ children }) {
364
457
  });
365
458
  }
366
459
  function Feedback({ messageId }) {
367
- const { onFeedback } = useXandi();
460
+ const { submitFeedback } = useXandi();
368
461
  const [feedback, setFeedback] = useState(null);
369
462
  const handleFeedback = (type) => {
370
463
  const newFeedback = feedback === type ? null : type;
371
464
  setFeedback(newFeedback);
372
- onFeedback?.(messageId, newFeedback);
465
+ submitFeedback(messageId, newFeedback);
373
466
  };
374
467
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Tooltip.Root, { children: [/* @__PURE__ */ jsx(Tooltip.Trigger, { render: /* @__PURE__ */ jsx(Button, {
375
468
  variant: "ghost",
@@ -554,27 +647,17 @@ function MessageRenderer({ type, message }) {
554
647
  }
555
648
  }
556
649
 
557
- //#endregion
558
- //#region src/constants.ts
559
- function getXandiAvatarUrl() {
560
- try {
561
- return new URL("./assets/images/xandi-avatar.png", import.meta.url).href;
562
- } catch {
563
- return "";
564
- }
565
- }
566
- const XANDI_AVATAR_URL = getXandiAvatarUrl();
567
-
568
650
  //#endregion
569
651
  //#region src/components/x-typing-indicator.tsx
570
652
  function XTypingIndicator() {
653
+ const { config } = useXandi();
571
654
  return /* @__PURE__ */ jsxs("div", {
572
655
  className: "flex items-center gap-4",
573
656
  children: [/* @__PURE__ */ jsx("div", {
574
657
  className: "animate-[popUp_0.3s_ease-out_forwards]",
575
658
  children: /* @__PURE__ */ jsx(Avatar, {
576
- imgSrc: XANDI_AVATAR_URL,
577
- name: "Xandi",
659
+ imgSrc: config.avatarUrl,
660
+ name: config.assistantName,
578
661
  variant: "rounded",
579
662
  size: "48px",
580
663
  hideTooltip: true,
@@ -593,19 +676,77 @@ function XTypingIndicator() {
593
676
 
594
677
  //#endregion
595
678
  //#region src/components/x-message-container.tsx
596
- function XMessageContainer({ height = 400 }) {
597
- const { messages, isLoading } = useXandi();
679
+ function XMessageContainer({ height = 400, onLoadMore }) {
680
+ const { conversation, isLoading } = useXandi();
598
681
  const containerRef = useRef(null);
682
+ const topSentinelRef = useRef(null);
683
+ const prevScrollHeightRef = useRef(0);
684
+ const prevScrollTopRef = useRef(0);
685
+ const prevLastMessageIdRef = useRef(null);
686
+ const prevMessageCountRef = useRef(0);
687
+ const skipScrollToBottomRef = useRef(false);
688
+ const messages = conversation.messages;
689
+ const messageCount = messages.length;
690
+ const lastMessageId = messageCount > 0 ? messages[messageCount - 1].id : null;
691
+ useLayoutEffect(() => {
692
+ const el = containerRef.current;
693
+ if (!el) return;
694
+ const prevCount = prevMessageCountRef.current;
695
+ const prevLastId = prevLastMessageIdRef.current;
696
+ if (prevCount > 0 && messageCount > prevCount && lastMessageId != null && lastMessageId !== prevLastId) {
697
+ const prevScrollHeight = prevScrollHeightRef.current;
698
+ el.scrollTop = prevScrollTopRef.current + (el.scrollHeight - prevScrollHeight);
699
+ skipScrollToBottomRef.current = true;
700
+ }
701
+ prevScrollHeightRef.current = el.scrollHeight;
702
+ prevScrollTopRef.current = el.scrollTop;
703
+ prevMessageCountRef.current = messageCount;
704
+ prevLastMessageIdRef.current = lastMessageId;
705
+ }, [
706
+ messageCount,
707
+ lastMessageId,
708
+ messages
709
+ ]);
710
+ useEffect(() => {
711
+ const el = containerRef.current;
712
+ if (!el) return;
713
+ if (skipScrollToBottomRef.current) {
714
+ skipScrollToBottomRef.current = false;
715
+ return;
716
+ }
717
+ el.scrollTop = 0;
718
+ }, [conversation.messages, isLoading]);
599
719
  useEffect(() => {
600
- if (containerRef.current) containerRef.current.scrollTop = containerRef.current.scrollHeight;
601
- }, [messages, isLoading]);
720
+ if (!onLoadMore) return;
721
+ const sentinel = topSentinelRef.current;
722
+ const container = containerRef.current;
723
+ if (!sentinel || !container) return;
724
+ const observer = new IntersectionObserver((entries) => {
725
+ const [entry] = entries;
726
+ if (entry?.isIntersecting) onLoadMore();
727
+ }, {
728
+ root: container,
729
+ rootMargin: "0px",
730
+ threshold: 0
731
+ });
732
+ observer.observe(sentinel);
733
+ return () => observer.disconnect();
734
+ }, [onLoadMore]);
602
735
  return /* @__PURE__ */ jsx("div", {
603
736
  ref: containerRef,
604
- className: "overflow-y-auto py-[10px]",
737
+ className: "flex flex-col overflow-y-auto py-[10px]",
605
738
  style: { height: typeof height === "number" ? `${height}px` : height },
606
739
  children: /* @__PURE__ */ jsxs("div", {
607
- className: "flex flex-col gap-5 p-4",
608
- children: [messages.map((message) => /* @__PURE__ */ jsx(XMessageItem, { message }, message.id)), isLoading && /* @__PURE__ */ jsx(XTypingIndicator, {})]
740
+ className: "flex flex-col-reverse gap-5 p-4",
741
+ children: [
742
+ /* @__PURE__ */ jsx("div", {
743
+ ref: topSentinelRef,
744
+ className: "h-1 shrink-0",
745
+ "aria-hidden": "true"
746
+ }),
747
+ messages.map((message) => /* @__PURE__ */ jsx(XMessageItem, { message }, message.id)),
748
+ isLoading && /* @__PURE__ */ jsx(XTypingIndicator, {})
749
+ ]
609
750
  })
610
751
  });
611
752
  }
@@ -613,6 +754,7 @@ function XMessageContainer({ height = 400 }) {
613
754
  //#endregion
614
755
  //#region src/components/x-welcome.tsx
615
756
  function XWelcome({ message }) {
757
+ const { config } = useXandi();
616
758
  return /* @__PURE__ */ jsxs("div", {
617
759
  className: "flex flex-col items-center justify-center gap-4 py-12",
618
760
  children: [/* @__PURE__ */ jsxs("div", {
@@ -622,8 +764,8 @@ function XWelcome({ message }) {
622
764
  /* @__PURE__ */ jsx("div", {
623
765
  className: "relative rounded-full bg-ppx-neutral-18 p-1",
624
766
  children: /* @__PURE__ */ jsx(Avatar, {
625
- imgSrc: XANDI_AVATAR_URL,
626
- name: "Xandi",
767
+ imgSrc: config.avatarUrl,
768
+ name: config.assistantName,
627
769
  variant: "rounded",
628
770
  size: "120px",
629
771
  hideTooltip: true
@@ -646,9 +788,13 @@ function XWelcome({ message }) {
646
788
 
647
789
  //#endregion
648
790
  //#region src/components/xandi.tsx
649
- function Xandi({ welcomeMessage = "How can I help you today?", suggestions = [] }) {
650
- const { messages, isLoading } = useXandi();
651
- const isEmpty = messages.length === 0;
791
+ function Xandi({ welcomeMessage = "How can I help you today?", suggestions = [], uiMode }) {
792
+ const { conversation, setUiModeOverride } = useXandi();
793
+ useEffect(() => {
794
+ setUiModeOverride(uiMode ?? null);
795
+ return () => setUiModeOverride(null);
796
+ }, [uiMode, setUiModeOverride]);
797
+ const isEmpty = conversation.messages.length === 0;
652
798
  return /* @__PURE__ */ jsxs("div", {
653
799
  className: "flex flex-col",
654
800
  children: [isEmpty ? /* @__PURE__ */ jsx(XWelcome, { message: welcomeMessage }) : /* @__PURE__ */ jsx(XMessageContainer, {}), /* @__PURE__ */ jsx(XMainIntake, { suggestions: isEmpty ? suggestions : [] })]
@@ -657,9 +803,10 @@ function Xandi({ welcomeMessage = "How can I help you today?", suggestions = []
657
803
 
658
804
  //#endregion
659
805
  //#region src/components/x-header.tsx
660
- function XHeader({ title = "Xandi", onClose, onNewChat, onToggleHistory }) {
806
+ function XHeader({ onClose, onToggleHistory }) {
807
+ const { startNewConversation, config } = useXandi();
661
808
  return /* @__PURE__ */ jsxs("header", {
662
- className: "flex items-center justify-between border-b border-ppx-neutral-5 bg-ppx-neutral-2 px-3 py-2",
809
+ className: "flex items-center justify-between border-b border-ppx-neutral-5 bg-transparent px-3 py-2",
663
810
  children: [/* @__PURE__ */ jsxs("div", {
664
811
  className: "flex items-center gap-2",
665
812
  children: [/* @__PURE__ */ jsx(Button, {
@@ -671,14 +818,14 @@ function XHeader({ title = "Xandi", onClose, onNewChat, onToggleHistory }) {
671
818
  }), /* @__PURE__ */ jsxs("div", {
672
819
  className: "flex items-center gap-2",
673
820
  children: [/* @__PURE__ */ jsx(Avatar, {
674
- imgSrc: XANDI_AVATAR_URL,
675
- name: "Xandi",
821
+ imgSrc: config.avatarUrl,
822
+ name: config.assistantName,
676
823
  variant: "rounded",
677
824
  size: "24px",
678
825
  hideTooltip: true
679
826
  }), /* @__PURE__ */ jsx("span", {
680
827
  className: "font-medium text-ppx-foreground",
681
- children: title
828
+ children: config.assistantName
682
829
  })]
683
830
  })]
684
831
  }), /* @__PURE__ */ jsxs("div", {
@@ -686,10 +833,10 @@ function XHeader({ title = "Xandi", onClose, onNewChat, onToggleHistory }) {
686
833
  children: [/* @__PURE__ */ jsx(Button, {
687
834
  variant: "ghost",
688
835
  size: "icon-sm",
689
- onClick: onNewChat,
836
+ onClick: startNewConversation,
690
837
  "aria-label": "New chat",
691
838
  children: /* @__PURE__ */ jsx(NewChatIcon, {})
692
- }), /* @__PURE__ */ jsx(Button, {
839
+ }), config.uiMode !== "full" && /* @__PURE__ */ jsx(Button, {
693
840
  variant: "ghost",
694
841
  size: "icon-sm",
695
842
  onClick: onClose,
@@ -702,7 +849,7 @@ function XHeader({ title = "Xandi", onClose, onNewChat, onToggleHistory }) {
702
849
 
703
850
  //#endregion
704
851
  //#region src/components/x-chat-history.tsx
705
- function XChatHistory({ groups = [], activeChatId, onSelectChat }) {
852
+ function XChatHistory({ items = [], isLoading = false, activeChatId, onSelectChat }) {
706
853
  return /* @__PURE__ */ jsxs("div", {
707
854
  className: "flex-1 overflow-y-auto",
708
855
  children: [/* @__PURE__ */ jsx("div", {
@@ -711,30 +858,57 @@ function XChatHistory({ groups = [], activeChatId, onSelectChat }) {
711
858
  className: "flex items-center gap-2 text-ppx-sm font-medium text-ppx-foreground",
712
859
  children: [/* @__PURE__ */ jsx(ChatIcon, {}), "Chats"]
713
860
  })
714
- }), groups.length === 0 ? /* @__PURE__ */ jsx("div", {
861
+ }), isLoading && items.length === 0 ? /* @__PURE__ */ jsxs("div", {
862
+ className: "flex flex-col items-center justify-center gap-2 px-6 py-8",
863
+ children: [/* @__PURE__ */ jsx(Spinner, {
864
+ size: "medium",
865
+ className: "text-ppx-neutral-10"
866
+ }), /* @__PURE__ */ jsx("span", {
867
+ className: "text-ppx-sm text-ppx-neutral-10",
868
+ children: "Loading conversations..."
869
+ })]
870
+ }) : items.length === 0 ? /* @__PURE__ */ jsx("div", {
715
871
  className: "px-6 py-4 text-ppx-sm text-ppx-neutral-10",
716
872
  children: "No chat history yet"
717
- }) : groups.map((group) => /* @__PURE__ */ jsxs("div", {
718
- className: "mb-2",
719
- children: [/* @__PURE__ */ jsx("div", {
720
- className: "px-6 py-2 text-ppx-xs font-medium text-ppx-neutral-10",
721
- children: group.label
722
- }), /* @__PURE__ */ jsx("div", {
723
- className: "space-y-0.5",
724
- children: group.items.map((item) => /* @__PURE__ */ jsx(Button, {
725
- variant: "ghost",
726
- onClick: () => onSelectChat?.(item.id),
727
- className: `w-full justify-start truncate rounded-none px-6 ${activeChatId === item.id ? "bg-ppx-neutral-4 text-ppx-foreground" : "text-ppx-neutral-12"}`,
728
- children: item.title
729
- }, item.id))
730
- })]
731
- }, group.label))]
873
+ }) : /* @__PURE__ */ jsx("div", {
874
+ className: "space-y-0.5",
875
+ children: items.map((item) => /* @__PURE__ */ jsx(Button, {
876
+ variant: "ghost",
877
+ onClick: () => onSelectChat?.(item.id),
878
+ className: `w-full justify-start truncate rounded-none px-6 ${activeChatId === item.id ? "bg-ppx-neutral-4 text-ppx-foreground" : "text-ppx-neutral-12"}`,
879
+ children: item.title
880
+ }, item.id))
881
+ })]
732
882
  });
733
883
  }
734
884
 
735
885
  //#endregion
736
886
  //#region src/components/x-sidebar.tsx
737
- function XSidebar({ isOpen = true, chatHistory = [], activeChatId, onClose, onNewChat, onSelectChat }) {
887
+ function XSidebar({ isOpen = true, onClose }) {
888
+ const { startNewConversation, getConvHistory, loadConversation, conversation } = useXandi();
889
+ const [chatHistoryItems, setChatHistoryItems] = useState([]);
890
+ const [isLoadingHistory, setIsLoadingHistory] = useState(false);
891
+ const fetchInProgressRef = useRef(false);
892
+ useEffect(() => {
893
+ if (!isOpen || !getConvHistory || fetchInProgressRef.current) return;
894
+ fetchInProgressRef.current = true;
895
+ setIsLoadingHistory(true);
896
+ getConvHistory().then((history) => {
897
+ setChatHistoryItems((history ?? []).map((item) => ({
898
+ id: item.id,
899
+ title: item.title,
900
+ timestamp: item.timestamp
901
+ })));
902
+ }).catch((error) => {
903
+ console.error("Failed to fetch conversation history:", error);
904
+ }).finally(() => {
905
+ fetchInProgressRef.current = false;
906
+ setIsLoadingHistory(false);
907
+ });
908
+ }, [isOpen, getConvHistory]);
909
+ const handleSelectChat = (chatId) => {
910
+ loadConversation(chatId);
911
+ };
738
912
  if (!isOpen) return null;
739
913
  return /* @__PURE__ */ jsxs("aside", {
740
914
  className: "flex h-full w-64 flex-col border-r border-ppx-neutral-5 bg-ppx-neutral-2",
@@ -753,15 +927,16 @@ function XSidebar({ isOpen = true, chatHistory = [], activeChatId, onClose, onNe
753
927
  className: "p-3",
754
928
  children: /* @__PURE__ */ jsxs(Button, {
755
929
  variant: "ghost",
756
- onClick: onNewChat,
930
+ onClick: startNewConversation,
757
931
  className: "w-full justify-start gap-3",
758
932
  children: [/* @__PURE__ */ jsx(NewChatIcon, { className: "h-5 w-5" }), "New chat"]
759
933
  })
760
934
  }),
761
935
  /* @__PURE__ */ jsx(XChatHistory, {
762
- groups: chatHistory,
763
- activeChatId,
764
- onSelectChat
936
+ items: chatHistoryItems,
937
+ isLoading: isLoadingHistory,
938
+ activeChatId: conversation.id ?? void 0,
939
+ onSelectChat: handleSelectChat
765
940
  })
766
941
  ]
767
942
  });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["userMessage: Message","assistantMessage: Message","value: XandiContextValue","XMessageActions.Root","XMessageActions.Feedback","XMessageActions.Copy","XMessageActions.Debug","XMessageActions.Root","XMessageActions.Feedback","XMessageActions.Copy","XMessageActions.Debug","messageType: MessageType"],"sources":["../src/context/xandi-context.tsx","../src/components/x-main-intake.tsx","../src/assets/icons/chat-icon.tsx","../src/assets/icons/check-icon.tsx","../src/assets/icons/close-icon.tsx","../src/assets/icons/copy-icon.tsx","../src/assets/icons/debug-icon.tsx","../src/assets/icons/menu-icon.tsx","../src/assets/icons/new-chat-icon.tsx","../src/assets/icons/sparkles-icon.tsx","../src/assets/icons/thumbs-down-icon.tsx","../src/assets/icons/thumbs-up-icon.tsx","../src/components/x-message-actions.tsx","../src/components/renderers/markdown-renderer.tsx","../src/components/renderers/text-renderer.tsx","../src/components/x-message-item.tsx","../src/constants.ts","../src/components/x-typing-indicator.tsx","../src/components/x-message-container.tsx","../src/components/x-welcome.tsx","../src/components/xandi.tsx","../src/components/x-header.tsx","../src/components/x-chat-history.tsx","../src/components/x-sidebar.tsx"],"sourcesContent":["import { createContext, useContext, useEffect, useState } from \"react\";\n\nexport type MessageType = \"text\" | \"markdown\";\nexport type FeedbackType = \"up\" | \"down\" | null;\n\nexport interface Message {\n id: string;\n role: \"user\" | \"assistant\";\n content: string;\n type?: MessageType;\n debugTrace?: unknown;\n}\n\nexport interface XandiResponse {\n content: string;\n type?: MessageType;\n debugTrace?: unknown;\n}\n\nexport interface XandiContextValue {\n messages: Message[];\n isLoading: boolean;\n sessionId: string | null;\n sendMessage: (text: string) => void;\n onFeedback?: (messageId: string, feedback: FeedbackType) => void;\n}\n\nconst XandiContext = createContext<XandiContextValue | null>(null);\n\nexport interface XandiProviderProps {\n fetchResponse: (message: string) => Promise<XandiResponse>;\n sessionId?: string;\n onFeedback?: (messageId: string, feedback: FeedbackType) => void;\n children: React.ReactNode;\n}\n\nexport function XandiProvider({\n fetchResponse,\n sessionId: initialSessionId,\n onFeedback,\n children,\n}: XandiProviderProps) {\n const [sessionId, setSessionId] = useState<string | null>(initialSessionId ?? null);\n const [messages, setMessages] = useState<Message[]>([]);\n const [isLoading, setIsLoading] = useState(false);\n\n // Initialize sessionId if not provided\n useEffect(() => {\n if (!initialSessionId) {\n setSessionId(crypto.randomUUID());\n }\n }, [initialSessionId]);\n\n const sendMessage = async (text: string) => {\n if (!text.trim() || isLoading) return;\n\n // Add user message\n const userMessage: Message = {\n id: crypto.randomUUID(),\n role: \"user\",\n content: text,\n };\n setMessages((prev) => [...prev, userMessage]);\n setIsLoading(true);\n\n try {\n const response = await fetchResponse(text);\n\n const assistantMessage: Message = {\n id: crypto.randomUUID(),\n role: \"assistant\",\n content: response.content,\n type: response.type,\n debugTrace: response.debugTrace,\n };\n setMessages((prev) => [...prev, assistantMessage]);\n } catch (error) {\n console.error(\"Failed to send message:\", error);\n } finally {\n setIsLoading(false);\n }\n };\n\n const value: XandiContextValue = {\n messages,\n isLoading,\n sessionId,\n sendMessage,\n onFeedback,\n };\n\n return <XandiContext.Provider value={value}>{children}</XandiContext.Provider>;\n}\n\nexport function useXandi(): XandiContextValue {\n const context = useContext(XandiContext);\n if (!context) {\n throw new Error(\"useXandi must be used within XandiProvider\");\n }\n return context;\n}\n","import { useState } from \"react\";\n\nimport { Button, FileIcon, SendIcon, StopIcon } from \"@px-ui/core\";\nimport { useXandi } from \"../context/xandi-context\";\n\nexport interface Suggestion {\n id: string;\n label: string;\n prompt: string;\n}\n\nexport interface XMainIntakeProps {\n placeholder?: string;\n suggestions?: Suggestion[];\n}\n\nexport function XMainIntake({ \n placeholder = \"Ask about jobs, candidates, timesheets, or anything workforce...\",\n suggestions = [],\n}: XMainIntakeProps) {\n const { isLoading, sendMessage } = useXandi();\n const [input, setInput] = useState(\"\");\n\n const handleSubmit = (e?: React.FormEvent) => {\n e?.preventDefault();\n if (input.trim() && !isLoading) {\n sendMessage(input);\n setInput(\"\");\n }\n };\n\n const handleSuggestionClick = (prompt: string) => {\n setInput(prompt);\n };\n\n return (\n <div className=\"flex flex-col gap-3\">\n <form onSubmit={handleSubmit} className=\"flex flex-col gap-2 rounded-2xl border border-ppx-neutral-5 bg-ppx-neutral-1 p-3\">\n <div className=\"uploads-section\"></div>\n\n <XIntakeTextarea\n value={input}\n onChange={setInput}\n onSubmit={handleSubmit}\n placeholder={placeholder}\n disabled={isLoading}\n />\n\n <div className=\"actions-section flex flex-row items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n disabled={isLoading}\n >\n <FileIcon width={20} />\n </Button>\n <span className=\"ml-auto text-ppx-xs text-ppx-neutral-10\">\n Enter to send · Shift+Enter for new line\n </span>\n <Button\n type=\"submit\"\n size=\"icon-sm\"\n disabled={isLoading || !input.trim()}\n 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\"\n >\n {isLoading ? (\n <StopIcon width={14} />\n ) : (\n <SendIcon width={16} />\n )}\n </Button>\n </div>\n </form>\n\n {suggestions.length > 0 && (\n <div className=\"flex flex-wrap justify-center gap-2\">\n {suggestions.map((suggestion, index) => (\n <Button\n key={suggestion.id}\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => handleSuggestionClick(suggestion.prompt)}\n className=\"animate-[popUp_0.3s_ease-out_forwards] rounded-full opacity-0\"\n style={{ animationDelay: `${index * 0.1}s` }}\n >\n {suggestion.label}\n </Button>\n ))}\n </div>\n )}\n </div>\n );\n}\n\n\n/////////////////////////////////////////////////////////////////////////////////////////////////\n//////////////////////////////////////Supporting Components//////////////////////////////////////\n/////////////////////////////////////////////////////////////////////////////////////////////////\n\ninterface XIntakeTextareaProps {\n value: string;\n onChange: (value: string) => void;\n onSubmit: () => void;\n placeholder?: string;\n disabled?: boolean;\n}\n\nfunction XIntakeTextarea({ value, onChange, onSubmit, placeholder, disabled }: XIntakeTextareaProps) {\n const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n onSubmit();\n }\n // Shift+Enter allows default behavior (newline)\n };\n\n return (\n <textarea\n className=\"w-full resize-none border-none bg-transparent outline-none\"\n placeholder={placeholder}\n disabled={disabled}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n onKeyDown={handleKeyDown}\n />\n );\n}\n","export function ChatIcon(props: React.ComponentProps<\"svg\">) {\n return (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\" />\n </svg>\n );\n}\n\n","export function CheckIcon(props: React.ComponentProps<\"svg\">) {\n return (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n );\n}\n\n","export function CloseIcon(props: React.ComponentProps<\"svg\">) {\n return (\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n );\n}\n\n","export function CopyIcon(props: React.ComponentProps<\"svg\">) {\n return (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\" />\n <path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n </svg>\n );\n}\n\n","export function DebugIcon(props: React.ComponentProps<\"svg\">) {\n return (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <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\" />\n <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\" />\n </svg>\n );\n}\n\n","export function MenuIcon(props: React.ComponentProps<\"svg\">) {\n return (\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <line x1=\"4\" x2=\"20\" y1=\"12\" y2=\"12\" />\n <line x1=\"4\" x2=\"20\" y1=\"6\" y2=\"6\" />\n <line x1=\"4\" x2=\"20\" y1=\"18\" y2=\"18\" />\n </svg>\n );\n}\n\n","export function NewChatIcon(props: React.ComponentProps<\"svg\">) {\n return (\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <path d=\"M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\" />\n <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\" />\n </svg>\n );\n}\n\n","export function SparklesIcon(props: React.ComponentProps<\"svg\">) {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <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\" />\n <path d=\"M20 3v4\" />\n <path d=\"M22 5h-4\" />\n <path d=\"M4 17v2\" />\n <path d=\"M5 18H3\" />\n </svg>\n );\n}\n\n","export function ThumbsDownIcon(props: React.ComponentProps<\"svg\">) {\n return (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <path d=\"M17 14V2\" />\n <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\" />\n </svg>\n );\n}\n\n","export function ThumbsUpIcon(props: React.ComponentProps<\"svg\">) {\n return (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <path d=\"M7 10v12\" />\n <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\" />\n </svg>\n );\n}\n\n","import { useState } from \"react\";\nimport { Button, Dialog, toast, Tooltip } from \"@px-ui/core\";\n\nimport {\n CheckIcon,\n CopyIcon,\n DebugIcon,\n ThumbsDownIcon,\n ThumbsUpIcon,\n} from \"../assets/icons\";\nimport { useXandi, type FeedbackType } from \"../context/xandi-context\";\n\nexport type { FeedbackType } from \"../context/xandi-context\";\n\n// ============================================================================\n// Root\n// ============================================================================\n\n/**\n * Container for message actions. Use with composable children:\n * - XMessageActions.Feedback\n * - XMessageActions.Copy\n * - XMessageActions.Debug\n */\nexport function Root({ children }: { children: React.ReactNode }) {\n return <div className=\"flex items-center gap-1\">{children}</div>;\n}\n\n// ============================================================================\n// Feedback\n// ============================================================================\n\nexport interface FeedbackProps {\n messageId: string;\n}\n\nexport function Feedback({ messageId }: FeedbackProps) {\n const { onFeedback } = useXandi();\n const [feedback, setFeedback] = useState<FeedbackType>(null);\n\n const handleFeedback = (type: FeedbackType) => {\n const newFeedback = feedback === type ? null : type;\n setFeedback(newFeedback);\n onFeedback?.(messageId, newFeedback);\n };\n\n return (\n <>\n {/* Thumbs Up */}\n <Tooltip.Root>\n <Tooltip.Trigger\n render={\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={() => handleFeedback(\"up\")}\n className={`h-7 w-7 ${\n feedback === \"up\"\n ? \"bg-ppx-green-2 text-ppx-green-5\"\n : \"text-ppx-neutral-10 hover:text-ppx-neutral-12\"\n }`}\n >\n <ThumbsUpIcon className={feedback === \"up\" ? \"fill-current\" : \"\"} />\n </Button>\n }\n />\n <Tooltip.Content>\n {feedback === \"up\" ? \"You found this helpful\" : \"Good response\"}\n </Tooltip.Content>\n </Tooltip.Root>\n\n {/* Thumbs Down */}\n <Tooltip.Root>\n <Tooltip.Trigger\n render={\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={() => handleFeedback(\"down\")}\n className={`h-7 w-7 ${\n feedback === \"down\"\n ? \"bg-ppx-red-2 text-ppx-red-5\"\n : \"text-ppx-neutral-10 hover:text-ppx-neutral-12\"\n }`}\n >\n <ThumbsDownIcon className={feedback === \"down\" ? \"fill-current\" : \"\"} />\n </Button>\n }\n />\n <Tooltip.Content>\n {feedback === \"down\" ? \"You found this unhelpful\" : \"Bad response\"}\n </Tooltip.Content>\n </Tooltip.Root>\n </>\n );\n}\n\n// ============================================================================\n// Copy\n// ============================================================================\n\nexport interface CopyProps {\n content: string;\n}\n\nexport function Copy({ content }: CopyProps) {\n const [copied, setCopied] = useState(false);\n\n const handleCopy = async () => {\n try {\n await navigator.clipboard.writeText(content);\n setCopied(true);\n toast.add({\n title: \"Copied!\",\n description: \"Message copied to clipboard\",\n type: \"success\",\n });\n setTimeout(() => setCopied(false), 2000);\n } catch {\n toast.add({\n title: \"Failed to copy\",\n description: \"Could not copy message to clipboard\",\n type: \"error\",\n });\n }\n };\n\n return (\n <Tooltip.Root>\n <Tooltip.Trigger\n render={\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={handleCopy}\n className=\"h-7 w-7 text-ppx-neutral-10 hover:text-ppx-neutral-12\"\n >\n {copied ? (\n <CheckIcon className=\"text-ppx-green-5\" />\n ) : (\n <CopyIcon />\n )}\n </Button>\n }\n />\n <Tooltip.Content>\n {copied ? \"Copied!\" : \"Copy message\"}\n </Tooltip.Content>\n </Tooltip.Root>\n );\n}\n\n// ============================================================================\n// Debug\n// ============================================================================\n\nexport interface DebugProps {\n messageId: string;\n debugTrace: unknown;\n}\n\nexport function Debug({ messageId, debugTrace }: DebugProps) {\n const [debugOpen, setDebugOpen] = useState(false);\n\n return (\n <Dialog.Root open={debugOpen} onOpenChange={setDebugOpen}>\n <Tooltip.Root>\n <Tooltip.Trigger\n render={\n <Dialog.Trigger\n render={\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"h-7 w-7 text-ppx-neutral-10 hover:text-ppx-neutral-12\"\n >\n <DebugIcon />\n </Button>\n }\n />\n }\n />\n <Tooltip.Content>\n View debug trace\n </Tooltip.Content>\n </Tooltip.Root>\n\n <Dialog.Portal>\n <Dialog.Overlay />\n <Dialog.Content className=\"max-w-2xl\">\n <Dialog.Header>\n <Dialog.Title>Debug Trace</Dialog.Title>\n <Dialog.Description>\n Response debug information for message {messageId}\n </Dialog.Description>\n </Dialog.Header>\n <div className=\"max-h-96 overflow-auto rounded bg-ppx-neutral-2 p-4\">\n <pre className=\"whitespace-pre-wrap font-mono text-ppx-xs text-ppx-neutral-12\">\n {JSON.stringify(debugTrace, null, 2)}\n </pre>\n </div>\n <Dialog.Footer>\n <Dialog.Close render={<Button variant=\"outline\">Close</Button>} />\n </Dialog.Footer>\n </Dialog.Content>\n </Dialog.Portal>\n </Dialog.Root>\n );\n}\n","import ReactMarkdown from \"react-markdown\";\n\nimport type { Message } from \"../../context/xandi-context\";\nimport * as XMessageActions from \"../x-message-actions\";\n\nexport interface MarkdownRendererProps {\n message: Message;\n}\n\nexport function MarkdownRenderer({ message }: MarkdownRendererProps) {\n const isUser = message.role === \"user\";\n const baseClass = `text-ppx-sm leading-relaxed ${isUser ? \"text-white\" : \"text-ppx-neutral-13\"}`;\n const showActions = !isUser;\n\n return (\n <div>\n <div className={baseClass}>\n <ReactMarkdown\n components={{\n p: ({ children }) => <p className=\"mb-2 last:mb-0\">{children}</p>,\n ul: ({ children }) => <ul className=\"mb-2 list-disc pl-4\">{children}</ul>,\n ol: ({ children }) => <ol className=\"mb-2 list-decimal pl-4\">{children}</ol>,\n li: ({ children }) => <li className=\"mb-1\">{children}</li>,\n a: ({ href, children }) => (\n <a href={href} className=\"underline\" target=\"_blank\" rel=\"noopener noreferrer\">\n {children}\n </a>\n ),\n strong: ({ children }) => <strong className=\"font-semibold\">{children}</strong>,\n }}\n >\n {message.content}\n </ReactMarkdown>\n </div>\n {showActions && (\n <div className=\"mt-2\">\n <XMessageActions.Root>\n <XMessageActions.Feedback messageId={message.id} />\n <XMessageActions.Copy content={message.content} />\n {message.debugTrace != null && (\n <XMessageActions.Debug messageId={message.id} debugTrace={message.debugTrace} />\n )}\n </XMessageActions.Root>\n </div>\n )}\n </div>\n );\n}\n","import type { Message } from \"../../context/xandi-context\";\nimport * as XMessageActions from \"../x-message-actions\";\n\nexport interface TextRendererProps {\n message: Message;\n}\n\nexport function TextRenderer({ message }: TextRendererProps) {\n const isUser = message.role === \"user\";\n const showActions = !isUser;\n\n return (\n <div>\n <p className={`text-ppx-sm leading-relaxed ${isUser ? \"text-white\" : \"text-ppx-neutral-13\"}`}>\n {message.content}\n </p>\n {showActions && (\n <div className=\"mt-2\">\n <XMessageActions.Root>\n <XMessageActions.Feedback messageId={message.id} />\n <XMessageActions.Copy content={message.content} />\n {message.debugTrace != null && (\n <XMessageActions.Debug messageId={message.id} debugTrace={message.debugTrace} />\n )}\n </XMessageActions.Root>\n </div>\n )}\n </div>\n );\n}\n","import type { Message, MessageType } from \"../context/xandi-context\";\nimport { MarkdownRenderer } from \"./renderers/markdown-renderer\";\nimport { TextRenderer } from \"./renderers/text-renderer\";\n\nexport interface XMessageItemProps {\n message: Message;\n}\n\n/**\n * Router component that renders messages based on their type.\n * Defaults to markdown rendering if no type is specified.\n */\nexport function XMessageItem({ message }: XMessageItemProps) {\n const isUser = message.role === \"user\";\n const messageType: MessageType = message.type ?? \"markdown\";\n\n return (\n <div className={`flex ${isUser ? \"justify-end\" : \"justify-start\"}`}>\n {isUser ? (\n <div className=\"max-w-[90%] rounded-2xl rounded-br-sm bg-ppx-green-5 px-4 py-2.5\">\n <MessageRenderer type={messageType} message={message} />\n </div>\n ) : (\n <div className=\"max-w-[90%]\">\n <MessageRenderer type={messageType} message={message} />\n </div>\n )}\n </div>\n );\n}\n\ninterface MessageRendererProps {\n type: MessageType;\n message: Message;\n}\n\n/**\n * Switch component that selects the appropriate renderer based on message type.\n */\nfunction MessageRenderer({ type, message }: MessageRendererProps) {\n switch (type) {\n case \"text\":\n return <TextRenderer message={message} />;\n case \"markdown\":\n default:\n return <MarkdownRenderer message={message} />;\n }\n}\n","// Xandi avatar asset path\n// Wrapped in try-catch for SSR compatibility (import.meta.url may not work in all environments)\nfunction getXandiAvatarUrl(): string {\n try {\n return new URL(\"./assets/images/xandi-avatar.png\", import.meta.url).href;\n } catch {\n // Fallback for SSR environments where import.meta.url is not a valid URL\n return \"\";\n }\n}\n\nexport const XANDI_AVATAR_URL = getXandiAvatarUrl();\n","import { Avatar } from \"@px-ui/core\";\n\nimport { XANDI_AVATAR_URL } from \"../constants\";\n\nexport function XTypingIndicator() {\n return (\n <div className=\"flex items-center gap-4\">\n <div className=\"animate-[popUp_0.3s_ease-out_forwards]\">\n <Avatar\n imgSrc={XANDI_AVATAR_URL}\n name=\"Xandi\"\n variant=\"rounded\"\n size=\"48px\"\n hideTooltip\n className=\"border-2 border-ppx-neutral-4\"\n />\n </div>\n <div 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\">\n <span className=\"h-[12px] w-[12px] animate-bounce rounded-full bg-ppx-neutral-9 [animation-delay:-0.3s]\" />\n <span className=\"h-[12px] w-[12px] animate-bounce rounded-full bg-ppx-neutral-6 [animation-delay:-0.15s]\" />\n <span className=\"h-[12px] w-[12px] animate-bounce rounded-full bg-ppx-neutral-9\" />\n </div>\n </div>\n );\n}\n","import { useEffect, useRef } from \"react\";\n\nimport { XMessageItem } from \"./x-message-item\";\nimport { XTypingIndicator } from \"./x-typing-indicator\";\nimport { useXandi } from \"../context/xandi-context\";\n\nexport interface XMessageContainerProps {\n height?: string | number;\n}\n\nexport function XMessageContainer({ height = 400 }: XMessageContainerProps) {\n const { messages, isLoading } = useXandi();\n const containerRef = useRef<HTMLDivElement>(null);\n\n // Auto-scroll to bottom when new messages arrive or loading state changes\n useEffect(() => {\n if (containerRef.current) {\n containerRef.current.scrollTop = containerRef.current.scrollHeight;\n }\n }, [messages, isLoading]);\n\n return (\n <div\n ref={containerRef}\n className=\"overflow-y-auto py-[10px]\"\n style={{ height: typeof height === \"number\" ? `${height}px` : height }}\n >\n <div className=\"flex flex-col gap-5 p-4\">\n {messages.map((message) => (\n <XMessageItem key={message.id} message={message} />\n ))}\n {isLoading && <XTypingIndicator />}\n </div>\n </div>\n );\n}\n","import { Avatar } from \"@px-ui/core\";\n\nimport { SparklesIcon } from \"../assets/icons\";\nimport { XANDI_AVATAR_URL } from \"../constants\";\n\nexport interface XWelcomeProps {\n message: string;\n}\n\nexport function XWelcome({ message }: XWelcomeProps) {\n return (\n <div className=\"flex flex-col items-center justify-center gap-4 py-12\">\n <div className=\"relative\">\n {/* Gradient border ring */}\n <div className=\"absolute -inset-1 rounded-full bg-gradient-to-b from-ppx-green-4 via-ppx-green-5/50 to-transparent\" />\n \n {/* Avatar container */}\n <div className=\"relative rounded-full bg-ppx-neutral-18 p-1\">\n <Avatar\n imgSrc={XANDI_AVATAR_URL}\n name=\"Xandi\"\n variant=\"rounded\"\n size=\"120px\"\n hideTooltip\n />\n </div>\n\n {/* Sparkles icon with pulse-zoom animation */}\n <div className=\"absolute -bottom-2 left-1/2\">\n <div className=\"flex h-6 w-6 animate-[pulse-zoom_2s_ease-in-out_infinite] items-center justify-center rounded-full bg-ppx-green-5\">\n <SparklesIcon className=\"text-white\" />\n </div>\n </div>\n </div>\n\n <h2 className=\"text-ppx-xl font-semibold text-ppx-foreground\">\n {message}\n </h2>\n </div>\n );\n}\n","import { XMainIntake, type Suggestion } from \"./x-main-intake\";\nimport { XMessageContainer } from \"./x-message-container\";\nimport { XWelcome } from \"./x-welcome\";\nimport { useXandi } from \"../context/xandi-context\";\n\nexport interface XandiProps {\n welcomeMessage?: string;\n suggestions?: Suggestion[];\n}\n\nexport function Xandi({\n welcomeMessage = \"How can I help you today?\",\n suggestions = [],\n}: XandiProps) {\n const { messages, isLoading } = useXandi();\n const isEmpty = messages.length === 0;\n\n return (\n <div className=\"flex flex-col\">\n {isEmpty ? (\n <XWelcome message={welcomeMessage} />\n ) : (\n <XMessageContainer />\n )}\n <XMainIntake suggestions={isEmpty ? suggestions : []} />\n </div>\n );\n}\n","import { Avatar, Button } from \"@px-ui/core\";\n\nimport { CloseIcon, MenuIcon, NewChatIcon } from \"../assets/icons\";\nimport { XANDI_AVATAR_URL } from \"../constants\";\n\nexport interface XHeaderProps {\n title?: string;\n onClose?: () => void;\n onNewChat?: () => void;\n onToggleHistory?: () => void;\n}\n\nexport function XHeader({\n title = \"Xandi\",\n onClose,\n onNewChat,\n onToggleHistory,\n}: XHeaderProps) {\n return (\n <header className=\"flex items-center justify-between border-b border-ppx-neutral-5 bg-ppx-neutral-2 px-3 py-2\">\n {/* Left section - Menu & Title */}\n <div className=\"flex items-center gap-2\">\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={onToggleHistory}\n aria-label=\"Toggle chat history\"\n >\n <MenuIcon />\n </Button>\n\n <div className=\"flex items-center gap-2\">\n <Avatar\n imgSrc={XANDI_AVATAR_URL}\n name=\"Xandi\"\n variant=\"rounded\"\n size=\"24px\"\n hideTooltip\n />\n <span className=\"font-medium text-ppx-foreground\">{title}</span>\n </div>\n </div>\n\n {/* Right section - Actions */}\n <div className=\"flex items-center gap-1\">\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={onNewChat}\n aria-label=\"New chat\"\n >\n <NewChatIcon />\n </Button>\n\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={onClose}\n aria-label=\"Close\"\n >\n <CloseIcon />\n </Button>\n </div>\n </header>\n );\n}\n","import { Button } from \"@px-ui/core\";\n\nimport { ChatIcon } from \"../assets/icons\";\n\nexport interface ChatHistoryItem {\n id: string;\n title: string;\n timestamp: Date;\n}\n\nexport interface ChatHistoryGroup {\n label: string;\n items: ChatHistoryItem[];\n}\n\nexport interface XChatHistoryProps {\n groups?: ChatHistoryGroup[];\n activeChatId?: string;\n onSelectChat?: (chatId: string) => void;\n}\n\nexport function XChatHistory({\n groups = [],\n activeChatId,\n onSelectChat,\n}: XChatHistoryProps) {\n return (\n <div className=\"flex-1 overflow-y-auto\">\n {/* Header */}\n <div className=\"px-3 py-2\">\n <div className=\"flex items-center gap-2 text-ppx-sm font-medium text-ppx-foreground\">\n <ChatIcon />\n Chats\n </div>\n </div>\n\n {/* Chat List */}\n {groups.length === 0 ? (\n <div className=\"px-6 py-4 text-ppx-sm text-ppx-neutral-10\">\n No chat history yet\n </div>\n ) : (\n groups.map((group) => (\n <div key={group.label} className=\"mb-2\">\n <div className=\"px-6 py-2 text-ppx-xs font-medium text-ppx-neutral-10\">\n {group.label}\n </div>\n <div className=\"space-y-0.5\">\n {group.items.map((item) => (\n <Button\n key={item.id}\n variant=\"ghost\"\n onClick={() => onSelectChat?.(item.id)}\n className={`w-full justify-start truncate rounded-none px-6 ${\n activeChatId === item.id\n ? \"bg-ppx-neutral-4 text-ppx-foreground\"\n : \"text-ppx-neutral-12\"\n }`}\n >\n {item.title}\n </Button>\n ))}\n </div>\n </div>\n ))\n )}\n </div>\n );\n}\n\n","import { Button } from \"@px-ui/core\";\n\nimport { CloseIcon, NewChatIcon } from \"../assets/icons\";\nimport { XChatHistory, type ChatHistoryGroup } from \"./x-chat-history\";\n\nexport interface XSidebarProps {\n isOpen?: boolean;\n chatHistory?: ChatHistoryGroup[];\n activeChatId?: string;\n onClose?: () => void;\n onNewChat?: () => void;\n onSelectChat?: (chatId: string) => void;\n}\n\nexport function XSidebar({\n isOpen = true,\n chatHistory = [],\n activeChatId,\n onClose,\n onNewChat,\n onSelectChat,\n}: XSidebarProps) {\n if (!isOpen) return null;\n\n return (\n <aside className=\"flex h-full w-64 flex-col border-r border-ppx-neutral-5 bg-ppx-neutral-2\">\n {/* Header */}\n <div className=\"flex items-center justify-between border-b border-ppx-neutral-5 p-3\">\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={onClose}\n aria-label=\"Close sidebar\"\n >\n <CloseIcon />\n </Button>\n </div>\n\n {/* New Chat Button */}\n <div className=\"p-3\">\n <Button\n variant=\"ghost\"\n onClick={onNewChat}\n className=\"w-full justify-start gap-3\"\n >\n <NewChatIcon className=\"h-5 w-5\" />\n New chat\n </Button>\n </div>\n\n {/* Chat History */}\n <XChatHistory\n groups={chatHistory}\n activeChatId={activeChatId}\n onSelectChat={onSelectChat}\n />\n </aside>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["defaultConfig: Required<XandiConfig>","defaultGetConvOptions: Required<GetConvOptions>","config: Required<XandiConfig>","userMessage: Message","assistantMessage: Message","value: XandiContextValue","XMessageActions.Root","XMessageActions.Feedback","XMessageActions.Copy","XMessageActions.Debug","XMessageActions.Root","XMessageActions.Feedback","XMessageActions.Copy","XMessageActions.Debug","messageType: MessageType"],"sources":["../src/constants.ts","../src/context/xandi-context.tsx","../src/components/x-main-intake.tsx","../src/assets/icons/chat-icon.tsx","../src/assets/icons/check-icon.tsx","../src/assets/icons/close-icon.tsx","../src/assets/icons/copy-icon.tsx","../src/assets/icons/debug-icon.tsx","../src/assets/icons/menu-icon.tsx","../src/assets/icons/new-chat-icon.tsx","../src/assets/icons/sparkles-icon.tsx","../src/assets/icons/thumbs-down-icon.tsx","../src/assets/icons/thumbs-up-icon.tsx","../src/components/x-message-actions.tsx","../src/components/renderers/markdown-renderer.tsx","../src/components/renderers/text-renderer.tsx","../src/components/x-message-item.tsx","../src/components/x-typing-indicator.tsx","../src/components/x-message-container.tsx","../src/components/x-welcome.tsx","../src/components/xandi.tsx","../src/components/x-header.tsx","../src/components/x-chat-history.tsx","../src/components/x-sidebar.tsx"],"sourcesContent":["// Xandi avatar asset path\n// Wrapped in try-catch for SSR compatibility (import.meta.url may not work in all environments)\nfunction getXandiAvatarUrl(): string {\n try {\n return new URL(\"./assets/images/xandi-avatar.png\", import.meta.url).href;\n } catch {\n // Fallback for SSR environments where import.meta.url is not a valid URL\n return \"\";\n }\n}\n\nexport const XANDI_AVATAR_URL = getXandiAvatarUrl();\n","import { createContext, useContext, useEffect, useRef, useState } from \"react\";\n\nimport { XANDI_AVATAR_URL } from \"../constants\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type MessageType = \"text\" | \"markdown\";\nexport type FeedbackType = \"up\" | \"down\" | null;\n\nexport interface Message {\n id: string;\n role: \"user\" | \"assistant\";\n content: string;\n type?: MessageType;\n debugTrace?: unknown;\n}\n\n/** API response structure from the backend */\nexport interface XandiApiResponse {\n success: boolean;\n message: string;\n data: {\n intent: string;\n data: unknown;\n conversation_id: string;\n };\n trace?: {\n trace_id: string;\n execution_mode: string;\n intent: string;\n tool_id: string;\n debug_trace: unknown;\n };\n}\n\n/** Transformed response for internal use */\nexport interface XandiResponse {\n content: string;\n type?: MessageType;\n debugTrace?: unknown;\n conversationId?: string;\n}\n\n/** Conversation summary for history list */\nexport interface ConversationSummary {\n id: string;\n title: string;\n timestamp: Date;\n}\n\n/** Full conversation with messages */\nexport interface Conversation {\n id: string | null;\n title: string;\n messages: Message[];\n createdAt: Date;\n updatedAt: Date;\n}\n\n/** Creates a new empty conversation */\nfunction createEmptyConversation(): Conversation {\n return {\n id: null,\n title: \"New Chat\",\n messages: [],\n createdAt: new Date(),\n updatedAt: new Date(),\n };\n}\n\n// ============================================================================\n// Config\n// ============================================================================\n\nexport type XandiUIMode = \"full\" | \"sidebar\" | \"floating\";\n\nexport interface XandiConfig {\n /** URL for the assistant's avatar image */\n avatarUrl?: string;\n /** Name of the assistant (default: \"Xandi\") */\n assistantName?: string;\n /** UI mode: full (no close button), sidebar, or floating (with close button) */\n uiMode?: XandiUIMode;\n}\n\nconst defaultConfig: Required<XandiConfig> = {\n avatarUrl: XANDI_AVATAR_URL,\n assistantName: \"Xandi\",\n uiMode: \"full\",\n};\n\n// ============================================================================\n// Handlers\n// ============================================================================\n\nexport interface FetchRespOptions {\n conversationId?: string;\n signal?: AbortSignal;\n}\n\nexport interface GetConvOptions {\n /** Page number (default: 1) */\n page?: number;\n /** Items per page (default: 20) */\n perPage?: number;\n}\n\nconst defaultGetConvOptions: Required<GetConvOptions> = {\n page: 1,\n perPage: 20,\n};\n\nexport interface XandiHandlers {\n /** Fetch AI response for a message */\n fetchResp: (message: string, options?: FetchRespOptions) => Promise<XandiResponse>;\n /** Get a conversation by ID with pagination; consumer transforms API response to Conversation */\n getConv?: (conversationId: string, options?: GetConvOptions) => Promise<Conversation>;\n /** Get conversation history list */\n getConvHistory?: () => Promise<ConversationSummary[]>;\n /** Called when user provides feedback on a message */\n onFeedback?: (messageId: string, conversationId: string, feedback: FeedbackType) => void;\n /** Called when user stops the current request */\n onStop?: (conversationId: string) => void;\n}\n\n// ============================================================================\n// Context\n// ============================================================================\n\nexport interface XandiContextValue {\n /** Current conversation with messages */\n conversation: Conversation;\n /** Whether a request is in progress */\n isLoading: boolean;\n /** Send a message to the assistant */\n sendMessage: (text: string) => void;\n /** Stop the current request */\n stopRequest: () => void;\n /** Load an existing conversation by ID (with optional page/perPage) */\n loadConversation: (conversationId: string, options?: GetConvOptions) => Promise<void>;\n /** Start a new empty conversation */\n startNewConversation: () => void;\n /** Submit feedback for a message */\n submitFeedback: (messageId: string, feedback: FeedbackType) => void;\n /** Get conversation history list (from handlers) */\n getConvHistory?: () => Promise<ConversationSummary[]>;\n /** Configuration for the assistant */\n config: Required<XandiConfig>;\n /** Set UI mode override from Xandi component (internal use) */\n setUiModeOverride: (mode: XandiUIMode | null) => void;\n}\n\nconst XandiContext = createContext<XandiContextValue | null>(null);\n\n// ============================================================================\n// Provider\n// ============================================================================\n\nexport interface XandiProviderProps {\n /** All handler functions for API communication */\n handlers: XandiHandlers;\n /** Initial conversation ID to restore */\n conversationId?: string;\n /** Configuration for the assistant appearance */\n config?: XandiConfig;\n children: React.ReactNode;\n}\n\nexport function XandiProvider({\n handlers,\n conversationId: initialConversationId,\n config: userConfig,\n children,\n}: XandiProviderProps) {\n const [conversation, setConversation] = useState<Conversation>(createEmptyConversation);\n const [isLoading, setIsLoading] = useState(false);\n const [uiModeOverride, setUiModeOverride] = useState<XandiUIMode | null>(null);\n const abortControllerRef = useRef<AbortController | null>(null);\n\n // Merge user config with defaults; Xandi's uiMode prop overrides config.uiMode when set\n const config: Required<XandiConfig> = {\n ...defaultConfig,\n ...userConfig,\n uiMode: uiModeOverride ?? userConfig?.uiMode ?? defaultConfig.uiMode,\n };\n\n // Load initial conversation if ID is provided\n useEffect(() => {\n if (initialConversationId && handlers.getConv) {\n loadConversation(initialConversationId);\n }\n }, [initialConversationId]);\n\n const loadConversation = async (convId: string, options?: GetConvOptions) => {\n if (!handlers.getConv) return;\n\n const opts = { ...defaultGetConvOptions, ...options };\n\n try {\n setIsLoading(true);\n const loadedConversation = await handlers.getConv(convId, opts);\n setConversation(loadedConversation);\n } catch (error) {\n console.error(\"Failed to load conversation:\", error);\n } finally {\n setIsLoading(false);\n }\n };\n\n const startNewConversation = () => {\n setConversation(createEmptyConversation());\n };\n\n const sendMessage = async (text: string) => {\n if (!text.trim() || isLoading) return;\n\n // Add user message\n const userMessage: Message = {\n id: crypto.randomUUID(),\n role: \"user\",\n content: text,\n };\n setConversation((prev) => ({\n ...prev,\n messages: [...prev.messages, userMessage],\n updatedAt: new Date(),\n }));\n setIsLoading(true);\n\n // Create abort controller for this request\n abortControllerRef.current = new AbortController();\n\n try {\n const response = await handlers.fetchResp(text, {\n conversationId: conversation.id ?? undefined,\n signal: abortControllerRef.current.signal,\n });\n\n const assistantMessage: Message = {\n id: crypto.randomUUID(),\n role: \"assistant\",\n content: response.content,\n type: response.type,\n debugTrace: response.debugTrace,\n };\n\n setConversation((prev) => ({\n ...prev,\n id: response.conversationId ?? prev.id,\n messages: [...prev.messages, assistantMessage],\n updatedAt: new Date(),\n }));\n } catch (error) {\n if ((error as Error).name !== \"AbortError\") {\n console.error(\"Failed to send message:\", error);\n }\n } finally {\n setIsLoading(false);\n abortControllerRef.current = null;\n }\n };\n\n const stopRequest = () => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n abortControllerRef.current = null;\n }\n if (conversation.id && handlers.onStop) {\n handlers.onStop(conversation.id);\n }\n setIsLoading(false);\n };\n\n const submitFeedback = (messageId: string, feedback: FeedbackType) => {\n if (handlers.onFeedback && conversation.id) {\n handlers.onFeedback(messageId, conversation.id, feedback);\n }\n };\n\n const value: XandiContextValue = {\n conversation,\n isLoading,\n sendMessage,\n stopRequest,\n loadConversation,\n startNewConversation,\n submitFeedback,\n getConvHistory: handlers.getConvHistory,\n config,\n setUiModeOverride,\n };\n\n return <XandiContext.Provider value={value}>{children}</XandiContext.Provider>;\n}\n\nexport function useXandi(): XandiContextValue {\n const context = useContext(XandiContext);\n if (!context) {\n throw new Error(\"useXandi must be used within XandiProvider\");\n }\n return context;\n}\n","import { useState } from \"react\";\n\nimport { Button, FileIcon, SendIcon, StopIcon } from \"@px-ui/core\";\nimport { useXandi } from \"../context/xandi-context\";\n\nexport interface Suggestion {\n id: string;\n label: string;\n prompt: string;\n}\n\nexport interface XMainIntakeProps {\n placeholder?: string;\n suggestions?: Suggestion[];\n}\n\nexport function XMainIntake({ \n placeholder = \"Ask about jobs, candidates, timesheets, or anything workforce...\",\n suggestions = [],\n}: XMainIntakeProps) {\n const { isLoading, sendMessage, stopRequest } = useXandi();\n const [input, setInput] = useState(\"\");\n\n const handleSubmit = (e?: React.FormEvent) => {\n e?.preventDefault();\n if (input.trim() && !isLoading) {\n sendMessage(input);\n setInput(\"\");\n }\n };\n\n const handleSuggestionClick = (prompt: string) => {\n setInput(prompt);\n };\n\n const handleStopOrSend = () => {\n if (isLoading) {\n stopRequest();\n }\n };\n\n return (\n <div className=\"flex flex-col gap-3\">\n <form onSubmit={handleSubmit} className=\"flex flex-col gap-2 rounded-2xl border border-ppx-neutral-5 bg-ppx-neutral-1 p-3\">\n <div className=\"uploads-section\"></div>\n\n <XIntakeTextarea\n value={input}\n onChange={setInput}\n onSubmit={handleSubmit}\n placeholder={placeholder}\n disabled={isLoading}\n />\n\n <div className=\"actions-section flex flex-row items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n disabled={isLoading}\n >\n <FileIcon width={20} />\n </Button>\n <span className=\"ml-auto text-ppx-xs text-ppx-neutral-10\">\n Enter to send · Shift+Enter for new line\n </span>\n {isLoading ? (\n <Button\n type=\"button\"\n size=\"icon-sm\"\n onClick={handleStopOrSend}\n className=\"flex h-8 w-8 items-center justify-center rounded-full bg-ppx-red-5 text-white transition-all hover:bg-ppx-red-4 hover:shadow-[0_0_12px_rgba(220,38,38,0.6)]\"\n >\n <StopIcon width={14} />\n </Button>\n ) : (\n <Button\n type=\"submit\"\n size=\"icon-sm\"\n disabled={!input.trim()}\n 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\"\n >\n <SendIcon width={16} />\n </Button>\n )}\n </div>\n </form>\n\n {suggestions.length > 0 && (\n <div className=\"flex flex-wrap justify-center gap-2\">\n {suggestions.map((suggestion, index) => (\n <Button\n key={suggestion.id}\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => handleSuggestionClick(suggestion.prompt)}\n className=\"animate-[popUp_0.3s_ease-out_forwards] rounded-full opacity-0\"\n style={{ animationDelay: `${index * 0.1}s` }}\n >\n {suggestion.label}\n </Button>\n ))}\n </div>\n )}\n </div>\n );\n}\n\n\n/////////////////////////////////////////////////////////////////////////////////////////////////\n//////////////////////////////////////Supporting Components//////////////////////////////////////\n/////////////////////////////////////////////////////////////////////////////////////////////////\n\ninterface XIntakeTextareaProps {\n value: string;\n onChange: (value: string) => void;\n onSubmit: () => void;\n placeholder?: string;\n disabled?: boolean;\n}\n\nfunction XIntakeTextarea({ value, onChange, onSubmit, placeholder, disabled }: XIntakeTextareaProps) {\n const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n onSubmit();\n }\n // Shift+Enter allows default behavior (newline)\n };\n\n return (\n <textarea\n className=\"w-full resize-none border-none bg-transparent outline-none\"\n placeholder={placeholder}\n disabled={disabled}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n onKeyDown={handleKeyDown}\n />\n );\n}\n","export function ChatIcon(props: React.ComponentProps<\"svg\">) {\n return (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\" />\n </svg>\n );\n}\n\n","export function CheckIcon(props: React.ComponentProps<\"svg\">) {\n return (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n );\n}\n\n","export function CloseIcon(props: React.ComponentProps<\"svg\">) {\n return (\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n );\n}\n\n","export function CopyIcon(props: React.ComponentProps<\"svg\">) {\n return (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\" />\n <path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n </svg>\n );\n}\n\n","export function DebugIcon(props: React.ComponentProps<\"svg\">) {\n return (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <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\" />\n <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\" />\n </svg>\n );\n}\n\n","export function MenuIcon(props: React.ComponentProps<\"svg\">) {\n return (\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <line x1=\"4\" x2=\"20\" y1=\"12\" y2=\"12\" />\n <line x1=\"4\" x2=\"20\" y1=\"6\" y2=\"6\" />\n <line x1=\"4\" x2=\"20\" y1=\"18\" y2=\"18\" />\n </svg>\n );\n}\n\n","export function NewChatIcon(props: React.ComponentProps<\"svg\">) {\n return (\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <path d=\"M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\" />\n <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\" />\n </svg>\n );\n}\n\n","export function SparklesIcon(props: React.ComponentProps<\"svg\">) {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <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\" />\n <path d=\"M20 3v4\" />\n <path d=\"M22 5h-4\" />\n <path d=\"M4 17v2\" />\n <path d=\"M5 18H3\" />\n </svg>\n );\n}\n\n","export function ThumbsDownIcon(props: React.ComponentProps<\"svg\">) {\n return (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <path d=\"M17 14V2\" />\n <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\" />\n </svg>\n );\n}\n\n","export function ThumbsUpIcon(props: React.ComponentProps<\"svg\">) {\n return (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <path d=\"M7 10v12\" />\n <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\" />\n </svg>\n );\n}\n\n","import { useState } from \"react\";\nimport { Button, Dialog, toast, Tooltip } from \"@px-ui/core\";\n\nimport {\n CheckIcon,\n CopyIcon,\n DebugIcon,\n ThumbsDownIcon,\n ThumbsUpIcon,\n} from \"../assets/icons\";\nimport { useXandi, type FeedbackType } from \"../context/xandi-context\";\n\nexport type { FeedbackType } from \"../context/xandi-context\";\n\n// ============================================================================\n// Root\n// ============================================================================\n\n/**\n * Container for message actions. Use with composable children:\n * - XMessageActions.Feedback\n * - XMessageActions.Copy\n * - XMessageActions.Debug\n */\nexport function Root({ children }: { children: React.ReactNode }) {\n return <div className=\"flex items-center gap-1\">{children}</div>;\n}\n\n// ============================================================================\n// Feedback\n// ============================================================================\n\nexport interface FeedbackProps {\n messageId: string;\n}\n\nexport function Feedback({ messageId }: FeedbackProps) {\n const { submitFeedback } = useXandi();\n const [feedback, setFeedback] = useState<FeedbackType>(null);\n\n const handleFeedback = (type: FeedbackType) => {\n const newFeedback = feedback === type ? null : type;\n setFeedback(newFeedback);\n submitFeedback(messageId, newFeedback);\n };\n\n return (\n <>\n {/* Thumbs Up */}\n <Tooltip.Root>\n <Tooltip.Trigger\n render={\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={() => handleFeedback(\"up\")}\n className={`h-7 w-7 ${\n feedback === \"up\"\n ? \"bg-ppx-green-2 text-ppx-green-5\"\n : \"text-ppx-neutral-10 hover:text-ppx-neutral-12\"\n }`}\n >\n <ThumbsUpIcon className={feedback === \"up\" ? \"fill-current\" : \"\"} />\n </Button>\n }\n />\n <Tooltip.Content>\n {feedback === \"up\" ? \"You found this helpful\" : \"Good response\"}\n </Tooltip.Content>\n </Tooltip.Root>\n\n {/* Thumbs Down */}\n <Tooltip.Root>\n <Tooltip.Trigger\n render={\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={() => handleFeedback(\"down\")}\n className={`h-7 w-7 ${\n feedback === \"down\"\n ? \"bg-ppx-red-2 text-ppx-red-5\"\n : \"text-ppx-neutral-10 hover:text-ppx-neutral-12\"\n }`}\n >\n <ThumbsDownIcon className={feedback === \"down\" ? \"fill-current\" : \"\"} />\n </Button>\n }\n />\n <Tooltip.Content>\n {feedback === \"down\" ? \"You found this unhelpful\" : \"Bad response\"}\n </Tooltip.Content>\n </Tooltip.Root>\n </>\n );\n}\n\n// ============================================================================\n// Copy\n// ============================================================================\n\nexport interface CopyProps {\n content: string;\n}\n\nexport function Copy({ content }: CopyProps) {\n const [copied, setCopied] = useState(false);\n\n const handleCopy = async () => {\n try {\n await navigator.clipboard.writeText(content);\n setCopied(true);\n toast.add({\n title: \"Copied!\",\n description: \"Message copied to clipboard\",\n type: \"success\",\n });\n setTimeout(() => setCopied(false), 2000);\n } catch {\n toast.add({\n title: \"Failed to copy\",\n description: \"Could not copy message to clipboard\",\n type: \"error\",\n });\n }\n };\n\n return (\n <Tooltip.Root>\n <Tooltip.Trigger\n render={\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={handleCopy}\n className=\"h-7 w-7 text-ppx-neutral-10 hover:text-ppx-neutral-12\"\n >\n {copied ? (\n <CheckIcon className=\"text-ppx-green-5\" />\n ) : (\n <CopyIcon />\n )}\n </Button>\n }\n />\n <Tooltip.Content>\n {copied ? \"Copied!\" : \"Copy message\"}\n </Tooltip.Content>\n </Tooltip.Root>\n );\n}\n\n// ============================================================================\n// Debug\n// ============================================================================\n\nexport interface DebugProps {\n messageId: string;\n debugTrace: unknown;\n}\n\nexport function Debug({ messageId, debugTrace }: DebugProps) {\n const [debugOpen, setDebugOpen] = useState(false);\n\n return (\n <Dialog.Root open={debugOpen} onOpenChange={setDebugOpen}>\n <Tooltip.Root>\n <Tooltip.Trigger\n render={\n <Dialog.Trigger\n render={\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"h-7 w-7 text-ppx-neutral-10 hover:text-ppx-neutral-12\"\n >\n <DebugIcon />\n </Button>\n }\n />\n }\n />\n <Tooltip.Content>\n View debug trace\n </Tooltip.Content>\n </Tooltip.Root>\n\n <Dialog.Portal>\n <Dialog.Overlay />\n <Dialog.Content className=\"max-w-2xl\">\n <Dialog.Header>\n <Dialog.Title>Debug Trace</Dialog.Title>\n <Dialog.Description>\n Response debug information for message {messageId}\n </Dialog.Description>\n </Dialog.Header>\n <div className=\"max-h-96 overflow-auto rounded bg-ppx-neutral-2 p-4\">\n <pre className=\"whitespace-pre-wrap font-mono text-ppx-xs text-ppx-neutral-12\">\n {JSON.stringify(debugTrace, null, 2)}\n </pre>\n </div>\n <Dialog.Footer>\n <Dialog.Close render={<Button variant=\"outline\">Close</Button>} />\n </Dialog.Footer>\n </Dialog.Content>\n </Dialog.Portal>\n </Dialog.Root>\n );\n}\n","import ReactMarkdown from \"react-markdown\";\n\nimport type { Message } from \"../../context/xandi-context\";\nimport * as XMessageActions from \"../x-message-actions\";\n\nexport interface MarkdownRendererProps {\n message: Message;\n}\n\nexport function MarkdownRenderer({ message }: MarkdownRendererProps) {\n const isUser = message.role === \"user\";\n const baseClass = `text-ppx-sm leading-relaxed ${isUser ? \"text-white\" : \"text-ppx-neutral-13\"}`;\n const showActions = !isUser;\n\n return (\n <div>\n <div className={baseClass}>\n <ReactMarkdown\n components={{\n p: ({ children }) => <p className=\"mb-2 last:mb-0\">{children}</p>,\n ul: ({ children }) => <ul className=\"mb-2 list-disc pl-4\">{children}</ul>,\n ol: ({ children }) => <ol className=\"mb-2 list-decimal pl-4\">{children}</ol>,\n li: ({ children }) => <li className=\"mb-1\">{children}</li>,\n a: ({ href, children }) => (\n <a href={href} className=\"underline\" target=\"_blank\" rel=\"noopener noreferrer\">\n {children}\n </a>\n ),\n strong: ({ children }) => <strong className=\"font-semibold\">{children}</strong>,\n }}\n >\n {message.content}\n </ReactMarkdown>\n </div>\n {showActions && (\n <div className=\"mt-2\">\n <XMessageActions.Root>\n <XMessageActions.Feedback messageId={message.id} />\n <XMessageActions.Copy content={message.content} />\n {message.debugTrace != null && (\n <XMessageActions.Debug messageId={message.id} debugTrace={message.debugTrace} />\n )}\n </XMessageActions.Root>\n </div>\n )}\n </div>\n );\n}\n","import type { Message } from \"../../context/xandi-context\";\nimport * as XMessageActions from \"../x-message-actions\";\n\nexport interface TextRendererProps {\n message: Message;\n}\n\nexport function TextRenderer({ message }: TextRendererProps) {\n const isUser = message.role === \"user\";\n const showActions = !isUser;\n\n return (\n <div>\n <p className={`text-ppx-sm leading-relaxed ${isUser ? \"text-white\" : \"text-ppx-neutral-13\"}`}>\n {message.content}\n </p>\n {showActions && (\n <div className=\"mt-2\">\n <XMessageActions.Root>\n <XMessageActions.Feedback messageId={message.id} />\n <XMessageActions.Copy content={message.content} />\n {message.debugTrace != null && (\n <XMessageActions.Debug messageId={message.id} debugTrace={message.debugTrace} />\n )}\n </XMessageActions.Root>\n </div>\n )}\n </div>\n );\n}\n","import type { Message, MessageType } from \"../context/xandi-context\";\nimport { MarkdownRenderer } from \"./renderers/markdown-renderer\";\nimport { TextRenderer } from \"./renderers/text-renderer\";\n\nexport interface XMessageItemProps {\n message: Message;\n}\n\n/**\n * Router component that renders messages based on their type.\n * Defaults to markdown rendering if no type is specified.\n */\nexport function XMessageItem({ message }: XMessageItemProps) {\n const isUser = message.role === \"user\";\n const messageType: MessageType = message.type ?? \"markdown\";\n\n return (\n <div className={`flex ${isUser ? \"justify-end\" : \"justify-start\"}`}>\n {isUser ? (\n <div className=\"max-w-[90%] rounded-2xl rounded-br-sm bg-ppx-green-5 px-4 py-2.5\">\n <MessageRenderer type={messageType} message={message} />\n </div>\n ) : (\n <div className=\"max-w-[90%]\">\n <MessageRenderer type={messageType} message={message} />\n </div>\n )}\n </div>\n );\n}\n\ninterface MessageRendererProps {\n type: MessageType;\n message: Message;\n}\n\n/**\n * Switch component that selects the appropriate renderer based on message type.\n */\nfunction MessageRenderer({ type, message }: MessageRendererProps) {\n switch (type) {\n case \"text\":\n return <TextRenderer message={message} />;\n case \"markdown\":\n default:\n return <MarkdownRenderer message={message} />;\n }\n}\n","import { Avatar } from \"@px-ui/core\";\n\nimport { useXandi } from \"../context/xandi-context\";\n\nexport function XTypingIndicator() {\n const { config } = useXandi();\n\n return (\n <div className=\"flex items-center gap-4\">\n <div className=\"animate-[popUp_0.3s_ease-out_forwards]\">\n <Avatar\n imgSrc={config.avatarUrl}\n name={config.assistantName}\n variant=\"rounded\"\n size=\"48px\"\n hideTooltip\n className=\"border-2 border-ppx-neutral-4\"\n />\n </div>\n <div 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\">\n <span className=\"h-[12px] w-[12px] animate-bounce rounded-full bg-ppx-neutral-9 [animation-delay:-0.3s]\" />\n <span className=\"h-[12px] w-[12px] animate-bounce rounded-full bg-ppx-neutral-6 [animation-delay:-0.15s]\" />\n <span className=\"h-[12px] w-[12px] animate-bounce rounded-full bg-ppx-neutral-9\" />\n </div>\n </div>\n );\n}\n","import { useEffect, useLayoutEffect, useRef } from \"react\";\n\nimport { XMessageItem } from \"./x-message-item\";\nimport { XTypingIndicator } from \"./x-typing-indicator\";\nimport { useXandi } from \"../context/xandi-context\";\n\nexport interface XMessageContainerProps {\n height?: string | number;\n /** Called when user scrolls to top (load more / older messages) */\n onLoadMore?: () => void;\n}\n\nexport function XMessageContainer({ height = 400, onLoadMore }: XMessageContainerProps) {\n const { conversation, isLoading } = useXandi();\n const containerRef = useRef<HTMLDivElement>(null);\n const topSentinelRef = useRef<HTMLDivElement>(null);\n const prevScrollHeightRef = useRef(0);\n const prevScrollTopRef = useRef(0);\n const prevLastMessageIdRef = useRef<string | null>(null);\n const prevMessageCountRef = useRef(0);\n const skipScrollToBottomRef = useRef(false);\n\n const messages = conversation.messages;\n const messageCount = messages.length;\n const lastMessageId = messageCount > 0 ? messages[messageCount - 1].id : null;\n\n // Restore scroll when older messages are appended (content added at top in column-reverse)\n useLayoutEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n\n const prevCount = prevMessageCountRef.current;\n const prevLastId = prevLastMessageIdRef.current;\n const isAppendOlder =\n prevCount > 0 &&\n messageCount > prevCount &&\n lastMessageId != null &&\n lastMessageId !== prevLastId;\n\n if (isAppendOlder) {\n const prevScrollHeight = prevScrollHeightRef.current;\n const prevScrollTop = prevScrollTopRef.current;\n const newScrollHeight = el.scrollHeight;\n el.scrollTop = prevScrollTop + (newScrollHeight - prevScrollHeight);\n skipScrollToBottomRef.current = true;\n }\n\n prevScrollHeightRef.current = el.scrollHeight;\n prevScrollTopRef.current = el.scrollTop;\n prevMessageCountRef.current = messageCount;\n prevLastMessageIdRef.current = lastMessageId;\n }, [messageCount, lastMessageId, messages]);\n\n // Scroll to bottom (newest) when new messages arrive or typing starts (column-reverse: bottom = 0)\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n if (skipScrollToBottomRef.current) {\n skipScrollToBottomRef.current = false;\n return;\n }\n el.scrollTop = 0;\n }, [conversation.messages, isLoading]);\n\n // IntersectionObserver: when top sentinel is visible, trigger load more\n useEffect(() => {\n if (!onLoadMore) return;\n const sentinel = topSentinelRef.current;\n const container = containerRef.current;\n if (!sentinel || !container) return;\n\n const observer = new IntersectionObserver(\n (entries) => {\n const [entry] = entries;\n if (entry?.isIntersecting) {\n onLoadMore();\n }\n },\n {\n root: container,\n rootMargin: \"0px\",\n threshold: 0,\n }\n );\n\n observer.observe(sentinel);\n return () => observer.disconnect();\n }, [onLoadMore]);\n\n return (\n <div\n ref={containerRef}\n className=\"flex flex-col overflow-y-auto py-[10px]\"\n style={{ height: typeof height === \"number\" ? `${height}px` : height }}\n >\n {/* column-reverse: messages [newest…oldest] show as oldest at top, newest at bottom; sentinel at top for load more */}\n <div className=\"flex flex-col-reverse gap-5 p-4\">\n <div ref={topSentinelRef} className=\"h-1 shrink-0\" aria-hidden=\"true\" />\n {messages.map((message) => (\n <XMessageItem key={message.id} message={message} />\n ))}\n {isLoading && <XTypingIndicator />}\n </div>\n </div>\n );\n}\n","import { Avatar } from \"@px-ui/core\";\n\nimport { SparklesIcon } from \"../assets/icons\";\nimport { useXandi } from \"../context/xandi-context\";\n\nexport interface XWelcomeProps {\n message: string;\n}\n\nexport function XWelcome({ message }: XWelcomeProps) {\n const { config } = useXandi();\n\n return (\n <div className=\"flex flex-col items-center justify-center gap-4 py-12\">\n <div className=\"relative\">\n {/* Gradient border ring */}\n <div className=\"absolute -inset-1 rounded-full bg-gradient-to-b from-ppx-green-4 via-ppx-green-5/50 to-transparent\" />\n \n {/* Avatar container */}\n <div className=\"relative rounded-full bg-ppx-neutral-18 p-1\">\n <Avatar\n imgSrc={config.avatarUrl}\n name={config.assistantName}\n variant=\"rounded\"\n size=\"120px\"\n hideTooltip\n />\n </div>\n\n {/* Sparkles icon with pulse-zoom animation */}\n <div className=\"absolute -bottom-2 left-1/2\">\n <div className=\"flex h-6 w-6 animate-[pulse-zoom_2s_ease-in-out_infinite] items-center justify-center rounded-full bg-ppx-green-5\">\n <SparklesIcon className=\"text-white\" />\n </div>\n </div>\n </div>\n\n <h2 className=\"text-ppx-xl font-semibold text-ppx-foreground\">\n {message}\n </h2>\n </div>\n );\n}\n","import { useEffect } from \"react\";\nimport { XMainIntake, type Suggestion } from \"./x-main-intake\";\nimport { XMessageContainer } from \"./x-message-container\";\nimport { XWelcome } from \"./x-welcome\";\nimport { useXandi } from \"../context/xandi-context\";\nimport type { XandiUIMode } from \"../context/xandi-context\";\n\nexport interface XandiProps {\n welcomeMessage?: string;\n suggestions?: Suggestion[];\n /**\n * UI mode: \"full\" (default) = no close button; \"sidebar\" | \"floating\" = show close button in header.\n * Overrides provider config when set.\n */\n uiMode?: XandiUIMode;\n}\n\nexport function Xandi({\n welcomeMessage = \"How can I help you today?\",\n suggestions = [],\n uiMode,\n}: XandiProps) {\n const { conversation, setUiModeOverride } = useXandi();\n\n // Push uiMode into provider so XHeader and others see it\n useEffect(() => {\n setUiModeOverride(uiMode ?? null);\n return () => setUiModeOverride(null);\n }, [uiMode, setUiModeOverride]);\n const isEmpty = conversation.messages.length === 0;\n\n return (\n <div className=\"flex flex-col\">\n {isEmpty ? (\n <XWelcome message={welcomeMessage} />\n ) : (\n <XMessageContainer />\n )}\n <XMainIntake suggestions={isEmpty ? suggestions : []} />\n </div>\n );\n}\n","import { Avatar, Button } from \"@px-ui/core\";\n\nimport { CloseIcon, MenuIcon, NewChatIcon } from \"../assets/icons\";\nimport { useXandi } from \"../context/xandi-context\";\n\nexport interface XHeaderProps {\n onClose?: () => void;\n onToggleHistory?: () => void;\n}\n\nexport function XHeader({\n onClose,\n onToggleHistory,\n}: XHeaderProps) {\n const { startNewConversation, config } = useXandi();\n\n return (\n <header className=\"flex items-center justify-between border-b border-ppx-neutral-5 bg-transparent px-3 py-2\">\n {/* Left section - Menu & Title */}\n <div className=\"flex items-center gap-2\">\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={onToggleHistory}\n aria-label=\"Toggle chat history\"\n >\n <MenuIcon />\n </Button>\n\n <div className=\"flex items-center gap-2\">\n <Avatar\n imgSrc={config.avatarUrl}\n name={config.assistantName}\n variant=\"rounded\"\n size=\"24px\"\n hideTooltip\n />\n <span className=\"font-medium text-ppx-foreground\">{config.assistantName}</span>\n </div>\n </div>\n\n {/* Right section - Actions */}\n <div className=\"flex items-center gap-1\">\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={startNewConversation}\n aria-label=\"New chat\"\n >\n <NewChatIcon />\n </Button>\n\n {config.uiMode !== \"full\" && (\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={onClose}\n aria-label=\"Close\"\n >\n <CloseIcon />\n </Button>\n )}\n </div>\n </header>\n );\n}\n","import { Button, Spinner } from \"@px-ui/core\";\n\nimport { ChatIcon } from \"../assets/icons\";\n\nexport interface ChatHistoryItem {\n id: string;\n title: string;\n timestamp: Date;\n}\n\nexport interface XChatHistoryProps {\n items?: ChatHistoryItem[];\n /** Whether conversation history is being fetched */\n isLoading?: boolean;\n activeChatId?: string;\n onSelectChat?: (chatId: string) => void;\n}\n\nexport function XChatHistory({\n items = [],\n isLoading = false,\n activeChatId,\n onSelectChat,\n}: XChatHistoryProps) {\n return (\n <div className=\"flex-1 overflow-y-auto\">\n {/* Header */}\n <div className=\"px-3 py-2\">\n <div className=\"flex items-center gap-2 text-ppx-sm font-medium text-ppx-foreground\">\n <ChatIcon />\n Chats\n </div>\n </div>\n\n {/* Chat List - show loader only when loading and list is empty (first time) */}\n {isLoading && items.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center gap-2 px-6 py-8\">\n <Spinner size=\"medium\" className=\"text-ppx-neutral-10\" />\n <span className=\"text-ppx-sm text-ppx-neutral-10\">Loading conversations...</span>\n </div>\n ) : items.length === 0 ? (\n <div className=\"px-6 py-4 text-ppx-sm text-ppx-neutral-10\">\n No chat history yet\n </div>\n ) : (\n <div className=\"space-y-0.5\">\n {items.map((item) => (\n <Button\n key={item.id}\n variant=\"ghost\"\n onClick={() => onSelectChat?.(item.id)}\n className={`w-full justify-start truncate rounded-none px-6 ${\n activeChatId === item.id\n ? \"bg-ppx-neutral-4 text-ppx-foreground\"\n : \"text-ppx-neutral-12\"\n }`}\n >\n {item.title}\n </Button>\n ))}\n </div>\n )}\n </div>\n );\n}\n\n","import { useEffect, useRef, useState } from \"react\";\nimport { Button } from \"@px-ui/core\";\n\nimport { CloseIcon, NewChatIcon } from \"../assets/icons\";\nimport { useXandi } from \"../context/xandi-context\";\nimport { XChatHistory, type ChatHistoryItem } from \"./x-chat-history\";\n\nexport interface XSidebarProps {\n isOpen?: boolean;\n onClose?: () => void;\n}\n\nexport function XSidebar({\n isOpen = true,\n onClose,\n}: XSidebarProps) {\n const { startNewConversation, getConvHistory, loadConversation, conversation } = useXandi();\n const [chatHistoryItems, setChatHistoryItems] = useState<ChatHistoryItem[]>([]);\n const [isLoadingHistory, setIsLoadingHistory] = useState(false);\n const fetchInProgressRef = useRef(false);\n\n // Fetch conversation history when sidebar opens (skip if a request is already pending)\n useEffect(() => {\n if (!isOpen || !getConvHistory || fetchInProgressRef.current) return;\n\n fetchInProgressRef.current = true;\n setIsLoadingHistory(true);\n\n getConvHistory()\n .then((history) => {\n setChatHistoryItems(\n (history ?? []).map((item) => ({\n id: item.id,\n title: item.title,\n timestamp: item.timestamp,\n }))\n );\n })\n .catch((error) => {\n console.error(\"Failed to fetch conversation history:\", error);\n })\n .finally(() => {\n fetchInProgressRef.current = false;\n setIsLoadingHistory(false);\n });\n }, [isOpen, getConvHistory]);\n\n const handleSelectChat = (chatId: string) => {\n loadConversation(chatId);\n };\n\n if (!isOpen) return null;\n\n return (\n <aside className=\"flex h-full w-64 flex-col border-r border-ppx-neutral-5 bg-ppx-neutral-2\">\n {/* Header */}\n <div className=\"flex items-center justify-between border-b border-ppx-neutral-5 p-3\">\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={onClose}\n aria-label=\"Close sidebar\"\n >\n <CloseIcon />\n </Button>\n </div>\n\n {/* New Chat Button */}\n <div className=\"p-3\">\n <Button\n variant=\"ghost\"\n onClick={startNewConversation}\n className=\"w-full justify-start gap-3\"\n >\n <NewChatIcon className=\"h-5 w-5\" />\n New chat\n </Button>\n </div>\n\n {/* Chat History */}\n <XChatHistory\n items={chatHistoryItems}\n isLoading={isLoadingHistory}\n activeChatId={conversation.id ?? undefined}\n onSelectChat={handleSelectChat}\n />\n </aside>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@px-ui/ai",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "description": "AI components built on top of px-ui-core",
5
5
  "type": "module",
6
6
  "module": "./dist/index.js",