@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 +114 -39
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +249 -74
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
87
|
+
/** Send a message to the assistant */
|
|
22
88
|
sendMessage: (text: string) => void;
|
|
23
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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-
|
|
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({
|
|
10
|
-
const [
|
|
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 (
|
|
15
|
-
}, [
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
121
|
+
conversation,
|
|
43
122
|
isLoading,
|
|
44
|
-
sessionId,
|
|
45
123
|
sendMessage,
|
|
46
|
-
|
|
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:
|
|
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:
|
|
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 {
|
|
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
|
-
|
|
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:
|
|
577
|
-
name:
|
|
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 {
|
|
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 (
|
|
601
|
-
|
|
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: [
|
|
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:
|
|
626
|
-
name:
|
|
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 {
|
|
651
|
-
|
|
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({
|
|
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-
|
|
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:
|
|
675
|
-
name:
|
|
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:
|
|
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:
|
|
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({
|
|
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
|
-
}),
|
|
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
|
-
}) :
|
|
718
|
-
className: "
|
|
719
|
-
children:
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
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,
|
|
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:
|
|
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
|
-
|
|
763
|
-
|
|
764
|
-
|
|
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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"}
|