@px-ui/ai 4.0.0 → 4.2.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 +82 -45
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +250 -87
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -10,28 +10,83 @@ interface Message {
|
|
|
10
10
|
type?: MessageType;
|
|
11
11
|
debugTrace?: unknown;
|
|
12
12
|
}
|
|
13
|
+
interface XandiApiResponse {
|
|
14
|
+
success: boolean;
|
|
15
|
+
message: string;
|
|
16
|
+
data: {
|
|
17
|
+
intent: string;
|
|
18
|
+
data: unknown;
|
|
19
|
+
conversation_id: string;
|
|
20
|
+
};
|
|
21
|
+
trace?: {
|
|
22
|
+
trace_id: string;
|
|
23
|
+
execution_mode: string;
|
|
24
|
+
intent: string;
|
|
25
|
+
tool_id: string;
|
|
26
|
+
debug_trace: unknown;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
13
29
|
interface XandiResponse {
|
|
14
30
|
content: string;
|
|
15
31
|
type?: MessageType;
|
|
16
32
|
debugTrace?: unknown;
|
|
33
|
+
conversationId?: string;
|
|
17
34
|
}
|
|
18
|
-
interface
|
|
35
|
+
interface ConversationSummary {
|
|
36
|
+
id: string;
|
|
37
|
+
title: string;
|
|
38
|
+
timestamp: Date;
|
|
39
|
+
}
|
|
40
|
+
interface Conversation {
|
|
41
|
+
id: string | null;
|
|
42
|
+
title: string;
|
|
19
43
|
messages: Message[];
|
|
44
|
+
createdAt: Date;
|
|
45
|
+
updatedAt: Date;
|
|
46
|
+
}
|
|
47
|
+
type XandiUIMode = "full" | "sidebar" | "floating";
|
|
48
|
+
interface XandiConfig {
|
|
49
|
+
avatarUrl?: string;
|
|
50
|
+
assistantName?: string;
|
|
51
|
+
uiMode?: XandiUIMode;
|
|
52
|
+
}
|
|
53
|
+
interface FetchRespOptions {
|
|
54
|
+
conversationId?: string;
|
|
55
|
+
signal?: AbortSignal;
|
|
56
|
+
}
|
|
57
|
+
interface GetConvOptions {
|
|
58
|
+
page?: number;
|
|
59
|
+
perPage?: number;
|
|
60
|
+
}
|
|
61
|
+
interface XandiHandlers {
|
|
62
|
+
fetchResp: (message: string, options?: FetchRespOptions) => Promise<XandiResponse>;
|
|
63
|
+
getConv?: (conversationId: string, options?: GetConvOptions) => Promise<Conversation>;
|
|
64
|
+
getConvHistory?: () => Promise<ConversationSummary[]>;
|
|
65
|
+
onFeedback?: (messageId: string, conversationId: string, feedback: FeedbackType) => void;
|
|
66
|
+
onStop?: (conversationId: string) => void;
|
|
67
|
+
}
|
|
68
|
+
interface XandiContextValue {
|
|
69
|
+
conversation: Conversation;
|
|
20
70
|
isLoading: boolean;
|
|
21
|
-
sessionId: string | null;
|
|
22
71
|
sendMessage: (text: string) => void;
|
|
23
|
-
|
|
72
|
+
stopRequest: () => void;
|
|
73
|
+
loadConversation: (conversationId: string, options?: GetConvOptions) => Promise<void>;
|
|
74
|
+
startNewConversation: () => void;
|
|
75
|
+
submitFeedback: (messageId: string, feedback: FeedbackType) => void;
|
|
76
|
+
getConvHistory?: () => Promise<ConversationSummary[]>;
|
|
77
|
+
config: Required<XandiConfig>;
|
|
78
|
+
setUiModeOverride: (mode: XandiUIMode | null) => void;
|
|
24
79
|
}
|
|
25
80
|
interface XandiProviderProps {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
81
|
+
handlers: XandiHandlers;
|
|
82
|
+
conversationId?: string;
|
|
83
|
+
config?: XandiConfig;
|
|
29
84
|
children: React.ReactNode;
|
|
30
85
|
}
|
|
31
86
|
declare function XandiProvider({
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
87
|
+
handlers,
|
|
88
|
+
conversationId: initialConversationId,
|
|
89
|
+
config: userConfig,
|
|
35
90
|
children
|
|
36
91
|
}: XandiProviderProps): react_jsx_runtime0.JSX.Element;
|
|
37
92
|
declare function useXandi(): XandiContextValue;
|
|
@@ -47,73 +102,55 @@ interface Suggestion {
|
|
|
47
102
|
interface XandiProps {
|
|
48
103
|
welcomeMessage?: string;
|
|
49
104
|
suggestions?: Suggestion[];
|
|
105
|
+
uiMode?: XandiUIMode;
|
|
50
106
|
}
|
|
51
107
|
declare function Xandi({
|
|
52
108
|
welcomeMessage,
|
|
53
|
-
suggestions
|
|
109
|
+
suggestions,
|
|
110
|
+
uiMode
|
|
54
111
|
}: XandiProps): react_jsx_runtime0.JSX.Element;
|
|
55
112
|
//#endregion
|
|
56
113
|
//#region src/components/x-header.d.ts
|
|
57
114
|
interface XHeaderProps {
|
|
58
|
-
title?: string;
|
|
59
115
|
onClose?: () => void;
|
|
60
|
-
onNewChat?: () => void;
|
|
61
116
|
onToggleHistory?: () => void;
|
|
62
117
|
}
|
|
63
118
|
declare function XHeader({
|
|
64
|
-
title,
|
|
65
119
|
onClose,
|
|
66
|
-
onNewChat,
|
|
67
120
|
onToggleHistory
|
|
68
121
|
}: XHeaderProps): react_jsx_runtime0.JSX.Element;
|
|
69
122
|
//#endregion
|
|
123
|
+
//#region src/components/x-sidebar.d.ts
|
|
124
|
+
interface XSidebarProps {
|
|
125
|
+
isOpen?: boolean;
|
|
126
|
+
onClose?: () => void;
|
|
127
|
+
}
|
|
128
|
+
declare function XSidebar({
|
|
129
|
+
isOpen,
|
|
130
|
+
onClose
|
|
131
|
+
}: XSidebarProps): react_jsx_runtime0.JSX.Element | null;
|
|
132
|
+
//#endregion
|
|
70
133
|
//#region src/components/x-chat-history.d.ts
|
|
71
134
|
interface ChatHistoryItem {
|
|
72
135
|
id: string;
|
|
73
136
|
title: string;
|
|
74
137
|
timestamp: Date;
|
|
75
138
|
}
|
|
76
|
-
interface ChatHistoryGroup {
|
|
77
|
-
label: string;
|
|
78
|
-
items: ChatHistoryItem[];
|
|
79
|
-
}
|
|
80
139
|
interface XChatHistoryProps {
|
|
81
|
-
|
|
140
|
+
items?: ChatHistoryItem[];
|
|
141
|
+
isLoading?: boolean;
|
|
82
142
|
activeChatId?: string;
|
|
83
143
|
onSelectChat?: (chatId: string) => void;
|
|
84
144
|
}
|
|
85
145
|
declare function XChatHistory({
|
|
86
|
-
|
|
146
|
+
items,
|
|
147
|
+
isLoading,
|
|
87
148
|
activeChatId,
|
|
88
149
|
onSelectChat
|
|
89
150
|
}: 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
151
|
declare namespace x_message_actions_d_exports {
|
|
109
152
|
export { Copy, CopyProps, Debug, DebugProps, Feedback, FeedbackProps, FeedbackType, Root };
|
|
110
153
|
}
|
|
111
|
-
/**
|
|
112
|
-
* Container for message actions. Use with composable children:
|
|
113
|
-
* - XMessageActions.Feedback
|
|
114
|
-
* - XMessageActions.Copy
|
|
115
|
-
* - XMessageActions.Debug
|
|
116
|
-
*/
|
|
117
154
|
declare function Root({
|
|
118
155
|
children
|
|
119
156
|
}: {
|
|
@@ -140,5 +177,5 @@ declare function Debug({
|
|
|
140
177
|
debugTrace
|
|
141
178
|
}: DebugProps): react_jsx_runtime0.JSX.Element;
|
|
142
179
|
//#endregion
|
|
143
|
-
export { type
|
|
180
|
+
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
181
|
//# 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,71 @@
|
|
|
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
|
+
function createEmptyConversation() {
|
|
20
|
+
return {
|
|
21
|
+
id: null,
|
|
22
|
+
title: "New Chat",
|
|
23
|
+
messages: [],
|
|
24
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
25
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const defaultConfig = {
|
|
29
|
+
avatarUrl: XANDI_AVATAR_URL,
|
|
30
|
+
assistantName: "Xandi",
|
|
31
|
+
uiMode: "full"
|
|
32
|
+
};
|
|
33
|
+
const defaultGetConvOptions = {
|
|
34
|
+
page: 1,
|
|
35
|
+
perPage: 20
|
|
36
|
+
};
|
|
8
37
|
const XandiContext = createContext(null);
|
|
9
|
-
function XandiProvider({
|
|
10
|
-
const [
|
|
11
|
-
const [messages, setMessages] = useState([]);
|
|
38
|
+
function XandiProvider({ handlers, conversationId: initialConversationId, config: userConfig, children }) {
|
|
39
|
+
const [conversation, setConversation] = useState(createEmptyConversation);
|
|
12
40
|
const [isLoading, setIsLoading] = useState(false);
|
|
41
|
+
const [uiModeOverride, setUiModeOverride] = useState(null);
|
|
42
|
+
const abortControllerRef = useRef(null);
|
|
43
|
+
const config = {
|
|
44
|
+
...defaultConfig,
|
|
45
|
+
...userConfig,
|
|
46
|
+
uiMode: uiModeOverride ?? userConfig?.uiMode ?? defaultConfig.uiMode
|
|
47
|
+
};
|
|
13
48
|
useEffect(() => {
|
|
14
|
-
if (
|
|
15
|
-
}, [
|
|
49
|
+
if (initialConversationId && handlers.getConv) loadConversation(initialConversationId);
|
|
50
|
+
}, [initialConversationId]);
|
|
51
|
+
const loadConversation = async (convId, options) => {
|
|
52
|
+
if (!handlers.getConv) return;
|
|
53
|
+
const opts = {
|
|
54
|
+
...defaultGetConvOptions,
|
|
55
|
+
...options
|
|
56
|
+
};
|
|
57
|
+
try {
|
|
58
|
+
setIsLoading(true);
|
|
59
|
+
setConversation(await handlers.getConv(convId, opts));
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error("Failed to load conversation:", error);
|
|
62
|
+
} finally {
|
|
63
|
+
setIsLoading(false);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const startNewConversation = () => {
|
|
67
|
+
setConversation(createEmptyConversation());
|
|
68
|
+
};
|
|
16
69
|
const sendMessage = async (text) => {
|
|
17
70
|
if (!text.trim() || isLoading) return;
|
|
18
71
|
const userMessage = {
|
|
@@ -20,10 +73,18 @@ function XandiProvider({ fetchResponse, sessionId: initialSessionId, onFeedback,
|
|
|
20
73
|
role: "user",
|
|
21
74
|
content: text
|
|
22
75
|
};
|
|
23
|
-
|
|
76
|
+
setConversation((prev) => ({
|
|
77
|
+
...prev,
|
|
78
|
+
messages: [...prev.messages, userMessage],
|
|
79
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
80
|
+
}));
|
|
24
81
|
setIsLoading(true);
|
|
82
|
+
abortControllerRef.current = new AbortController();
|
|
25
83
|
try {
|
|
26
|
-
const response = await
|
|
84
|
+
const response = await handlers.fetchResp(text, {
|
|
85
|
+
conversationId: conversation.id ?? void 0,
|
|
86
|
+
signal: abortControllerRef.current.signal
|
|
87
|
+
});
|
|
27
88
|
const assistantMessage = {
|
|
28
89
|
id: crypto.randomUUID(),
|
|
29
90
|
role: "assistant",
|
|
@@ -31,19 +92,41 @@ function XandiProvider({ fetchResponse, sessionId: initialSessionId, onFeedback,
|
|
|
31
92
|
type: response.type,
|
|
32
93
|
debugTrace: response.debugTrace
|
|
33
94
|
};
|
|
34
|
-
|
|
95
|
+
setConversation((prev) => ({
|
|
96
|
+
...prev,
|
|
97
|
+
id: response.conversationId ?? prev.id,
|
|
98
|
+
messages: [...prev.messages, assistantMessage],
|
|
99
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
100
|
+
}));
|
|
35
101
|
} catch (error) {
|
|
36
|
-
console.error("Failed to send message:", error);
|
|
102
|
+
if (error.name !== "AbortError") console.error("Failed to send message:", error);
|
|
37
103
|
} finally {
|
|
38
104
|
setIsLoading(false);
|
|
105
|
+
abortControllerRef.current = null;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
const stopRequest = () => {
|
|
109
|
+
if (abortControllerRef.current) {
|
|
110
|
+
abortControllerRef.current.abort();
|
|
111
|
+
abortControllerRef.current = null;
|
|
39
112
|
}
|
|
113
|
+
if (conversation.id && handlers.onStop) handlers.onStop(conversation.id);
|
|
114
|
+
setIsLoading(false);
|
|
115
|
+
};
|
|
116
|
+
const submitFeedback = (messageId, feedback) => {
|
|
117
|
+
if (handlers.onFeedback && conversation.id) handlers.onFeedback(messageId, conversation.id, feedback);
|
|
40
118
|
};
|
|
41
119
|
const value = {
|
|
42
|
-
|
|
120
|
+
conversation,
|
|
43
121
|
isLoading,
|
|
44
|
-
sessionId,
|
|
45
122
|
sendMessage,
|
|
46
|
-
|
|
123
|
+
stopRequest,
|
|
124
|
+
loadConversation,
|
|
125
|
+
startNewConversation,
|
|
126
|
+
submitFeedback,
|
|
127
|
+
getConvHistory: handlers.getConvHistory,
|
|
128
|
+
config,
|
|
129
|
+
setUiModeOverride
|
|
47
130
|
};
|
|
48
131
|
return /* @__PURE__ */ jsx(XandiContext.Provider, {
|
|
49
132
|
value,
|
|
@@ -59,7 +142,7 @@ function useXandi() {
|
|
|
59
142
|
//#endregion
|
|
60
143
|
//#region src/components/x-main-intake.tsx
|
|
61
144
|
function XMainIntake({ placeholder = "Ask about jobs, candidates, timesheets, or anything workforce...", suggestions = [] }) {
|
|
62
|
-
const { isLoading, sendMessage } = useXandi();
|
|
145
|
+
const { isLoading, sendMessage, stopRequest } = useXandi();
|
|
63
146
|
const [input, setInput] = useState("");
|
|
64
147
|
const handleSubmit = (e) => {
|
|
65
148
|
e?.preventDefault();
|
|
@@ -71,6 +154,9 @@ function XMainIntake({ placeholder = "Ask about jobs, candidates, timesheets, or
|
|
|
71
154
|
const handleSuggestionClick = (prompt) => {
|
|
72
155
|
setInput(prompt);
|
|
73
156
|
};
|
|
157
|
+
const handleStopOrSend = () => {
|
|
158
|
+
if (isLoading) stopRequest();
|
|
159
|
+
};
|
|
74
160
|
return /* @__PURE__ */ jsxs("div", {
|
|
75
161
|
className: "flex flex-col gap-3",
|
|
76
162
|
children: [/* @__PURE__ */ jsxs("form", {
|
|
@@ -99,12 +185,18 @@ function XMainIntake({ placeholder = "Ask about jobs, candidates, timesheets, or
|
|
|
99
185
|
className: "ml-auto text-ppx-xs text-ppx-neutral-10",
|
|
100
186
|
children: "Enter to send · Shift+Enter for new line"
|
|
101
187
|
}),
|
|
102
|
-
/* @__PURE__ */ jsx(Button, {
|
|
188
|
+
isLoading ? /* @__PURE__ */ jsx(Button, {
|
|
189
|
+
type: "button",
|
|
190
|
+
size: "icon-sm",
|
|
191
|
+
onClick: handleStopOrSend,
|
|
192
|
+
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)]",
|
|
193
|
+
children: /* @__PURE__ */ jsx(StopIcon, { width: 14 })
|
|
194
|
+
}) : /* @__PURE__ */ jsx(Button, {
|
|
103
195
|
type: "submit",
|
|
104
196
|
size: "icon-sm",
|
|
105
|
-
disabled:
|
|
197
|
+
disabled: !input.trim(),
|
|
106
198
|
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:
|
|
199
|
+
children: /* @__PURE__ */ jsx(SendIcon, { width: 16 })
|
|
108
200
|
})
|
|
109
201
|
]
|
|
110
202
|
})
|
|
@@ -351,12 +443,6 @@ var x_message_actions_exports = /* @__PURE__ */ __export({
|
|
|
351
443
|
Feedback: () => Feedback,
|
|
352
444
|
Root: () => Root
|
|
353
445
|
});
|
|
354
|
-
/**
|
|
355
|
-
* Container for message actions. Use with composable children:
|
|
356
|
-
* - XMessageActions.Feedback
|
|
357
|
-
* - XMessageActions.Copy
|
|
358
|
-
* - XMessageActions.Debug
|
|
359
|
-
*/
|
|
360
446
|
function Root({ children }) {
|
|
361
447
|
return /* @__PURE__ */ jsx("div", {
|
|
362
448
|
className: "flex items-center gap-1",
|
|
@@ -364,12 +450,12 @@ function Root({ children }) {
|
|
|
364
450
|
});
|
|
365
451
|
}
|
|
366
452
|
function Feedback({ messageId }) {
|
|
367
|
-
const {
|
|
453
|
+
const { submitFeedback } = useXandi();
|
|
368
454
|
const [feedback, setFeedback] = useState(null);
|
|
369
455
|
const handleFeedback = (type) => {
|
|
370
456
|
const newFeedback = feedback === type ? null : type;
|
|
371
457
|
setFeedback(newFeedback);
|
|
372
|
-
|
|
458
|
+
submitFeedback(messageId, newFeedback);
|
|
373
459
|
};
|
|
374
460
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Tooltip.Root, { children: [/* @__PURE__ */ jsx(Tooltip.Trigger, { render: /* @__PURE__ */ jsx(Button, {
|
|
375
461
|
variant: "ghost",
|
|
@@ -519,10 +605,6 @@ function TextRenderer({ message }) {
|
|
|
519
605
|
|
|
520
606
|
//#endregion
|
|
521
607
|
//#region src/components/x-message-item.tsx
|
|
522
|
-
/**
|
|
523
|
-
* Router component that renders messages based on their type.
|
|
524
|
-
* Defaults to markdown rendering if no type is specified.
|
|
525
|
-
*/
|
|
526
608
|
function XMessageItem({ message }) {
|
|
527
609
|
const isUser = message.role === "user";
|
|
528
610
|
const messageType = message.type ?? "markdown";
|
|
@@ -543,9 +625,6 @@ function XMessageItem({ message }) {
|
|
|
543
625
|
})
|
|
544
626
|
});
|
|
545
627
|
}
|
|
546
|
-
/**
|
|
547
|
-
* Switch component that selects the appropriate renderer based on message type.
|
|
548
|
-
*/
|
|
549
628
|
function MessageRenderer({ type, message }) {
|
|
550
629
|
switch (type) {
|
|
551
630
|
case "text": return /* @__PURE__ */ jsx(TextRenderer, { message });
|
|
@@ -554,27 +633,17 @@ function MessageRenderer({ type, message }) {
|
|
|
554
633
|
}
|
|
555
634
|
}
|
|
556
635
|
|
|
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
636
|
//#endregion
|
|
569
637
|
//#region src/components/x-typing-indicator.tsx
|
|
570
638
|
function XTypingIndicator() {
|
|
639
|
+
const { config } = useXandi();
|
|
571
640
|
return /* @__PURE__ */ jsxs("div", {
|
|
572
641
|
className: "flex items-center gap-4",
|
|
573
642
|
children: [/* @__PURE__ */ jsx("div", {
|
|
574
643
|
className: "animate-[popUp_0.3s_ease-out_forwards]",
|
|
575
644
|
children: /* @__PURE__ */ jsx(Avatar, {
|
|
576
|
-
imgSrc:
|
|
577
|
-
name:
|
|
645
|
+
imgSrc: config.avatarUrl,
|
|
646
|
+
name: config.assistantName,
|
|
578
647
|
variant: "rounded",
|
|
579
648
|
size: "48px",
|
|
580
649
|
hideTooltip: true,
|
|
@@ -593,19 +662,79 @@ function XTypingIndicator() {
|
|
|
593
662
|
|
|
594
663
|
//#endregion
|
|
595
664
|
//#region src/components/x-message-container.tsx
|
|
596
|
-
function XMessageContainer({ height = 400 }) {
|
|
597
|
-
const {
|
|
665
|
+
function XMessageContainer({ height = 400, onLoadMore }) {
|
|
666
|
+
const { conversation, isLoading } = useXandi();
|
|
598
667
|
const containerRef = useRef(null);
|
|
668
|
+
const topSentinelRef = useRef(null);
|
|
669
|
+
const prevScrollHeightRef = useRef(0);
|
|
670
|
+
const prevScrollTopRef = useRef(0);
|
|
671
|
+
const prevLastMessageIdRef = useRef(null);
|
|
672
|
+
const prevMessageCountRef = useRef(0);
|
|
673
|
+
const skipScrollToBottomRef = useRef(false);
|
|
674
|
+
const messages = conversation.messages;
|
|
675
|
+
const messageCount = messages.length;
|
|
676
|
+
const lastMessageId = messageCount > 0 ? messages[messageCount - 1].id : null;
|
|
677
|
+
useLayoutEffect(() => {
|
|
678
|
+
const el = containerRef.current;
|
|
679
|
+
if (!el) return;
|
|
680
|
+
const prevCount = prevMessageCountRef.current;
|
|
681
|
+
const prevLastId = prevLastMessageIdRef.current;
|
|
682
|
+
if (prevCount > 0 && messageCount > prevCount && lastMessageId != null && lastMessageId === prevLastId) {
|
|
683
|
+
const prevScrollHeight = prevScrollHeightRef.current;
|
|
684
|
+
const prevScrollTop = prevScrollTopRef.current;
|
|
685
|
+
const newScrollHeight = el.scrollHeight;
|
|
686
|
+
el.scrollTop = Math.max(0, prevScrollTop + (newScrollHeight - prevScrollHeight));
|
|
687
|
+
skipScrollToBottomRef.current = true;
|
|
688
|
+
}
|
|
689
|
+
prevScrollHeightRef.current = el.scrollHeight;
|
|
690
|
+
prevScrollTopRef.current = el.scrollTop;
|
|
691
|
+
prevMessageCountRef.current = messageCount;
|
|
692
|
+
prevLastMessageIdRef.current = lastMessageId;
|
|
693
|
+
}, [
|
|
694
|
+
messageCount,
|
|
695
|
+
lastMessageId,
|
|
696
|
+
messages
|
|
697
|
+
]);
|
|
698
|
+
useEffect(() => {
|
|
699
|
+
const el = containerRef.current;
|
|
700
|
+
if (!el) return;
|
|
701
|
+
if (skipScrollToBottomRef.current) {
|
|
702
|
+
skipScrollToBottomRef.current = false;
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
el.scrollTop = el.scrollHeight - el.clientHeight;
|
|
706
|
+
}, [conversation.messages, isLoading]);
|
|
599
707
|
useEffect(() => {
|
|
600
|
-
if (
|
|
601
|
-
|
|
708
|
+
if (!onLoadMore) return;
|
|
709
|
+
const sentinel = topSentinelRef.current;
|
|
710
|
+
const container = containerRef.current;
|
|
711
|
+
if (!sentinel || !container) return;
|
|
712
|
+
const observer = new IntersectionObserver((entries) => {
|
|
713
|
+
const [entry] = entries;
|
|
714
|
+
if (entry?.isIntersecting) onLoadMore();
|
|
715
|
+
}, {
|
|
716
|
+
root: container,
|
|
717
|
+
rootMargin: "0px",
|
|
718
|
+
threshold: 0
|
|
719
|
+
});
|
|
720
|
+
observer.observe(sentinel);
|
|
721
|
+
return () => observer.disconnect();
|
|
722
|
+
}, [onLoadMore]);
|
|
602
723
|
return /* @__PURE__ */ jsx("div", {
|
|
603
724
|
ref: containerRef,
|
|
604
|
-
className: "overflow-y-auto py-[10px]",
|
|
725
|
+
className: "flex flex-col overflow-y-auto py-[10px]",
|
|
605
726
|
style: { height: typeof height === "number" ? `${height}px` : height },
|
|
606
727
|
children: /* @__PURE__ */ jsxs("div", {
|
|
607
|
-
className: "flex flex-col gap-5 p-4",
|
|
608
|
-
children: [
|
|
728
|
+
className: "flex flex-col-reverse gap-5 p-4",
|
|
729
|
+
children: [
|
|
730
|
+
isLoading && /* @__PURE__ */ jsx(XTypingIndicator, {}),
|
|
731
|
+
[...messages].reverse().map((message) => /* @__PURE__ */ jsx(XMessageItem, { message }, message.id)),
|
|
732
|
+
/* @__PURE__ */ jsx("div", {
|
|
733
|
+
ref: topSentinelRef,
|
|
734
|
+
className: "h-1 shrink-0",
|
|
735
|
+
"aria-hidden": "true"
|
|
736
|
+
})
|
|
737
|
+
]
|
|
609
738
|
})
|
|
610
739
|
});
|
|
611
740
|
}
|
|
@@ -613,6 +742,7 @@ function XMessageContainer({ height = 400 }) {
|
|
|
613
742
|
//#endregion
|
|
614
743
|
//#region src/components/x-welcome.tsx
|
|
615
744
|
function XWelcome({ message }) {
|
|
745
|
+
const { config } = useXandi();
|
|
616
746
|
return /* @__PURE__ */ jsxs("div", {
|
|
617
747
|
className: "flex flex-col items-center justify-center gap-4 py-12",
|
|
618
748
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -622,8 +752,8 @@ function XWelcome({ message }) {
|
|
|
622
752
|
/* @__PURE__ */ jsx("div", {
|
|
623
753
|
className: "relative rounded-full bg-ppx-neutral-18 p-1",
|
|
624
754
|
children: /* @__PURE__ */ jsx(Avatar, {
|
|
625
|
-
imgSrc:
|
|
626
|
-
name:
|
|
755
|
+
imgSrc: config.avatarUrl,
|
|
756
|
+
name: config.assistantName,
|
|
627
757
|
variant: "rounded",
|
|
628
758
|
size: "120px",
|
|
629
759
|
hideTooltip: true
|
|
@@ -646,9 +776,13 @@ function XWelcome({ message }) {
|
|
|
646
776
|
|
|
647
777
|
//#endregion
|
|
648
778
|
//#region src/components/xandi.tsx
|
|
649
|
-
function Xandi({ welcomeMessage = "How can I help you today?", suggestions = [] }) {
|
|
650
|
-
const {
|
|
651
|
-
|
|
779
|
+
function Xandi({ welcomeMessage = "How can I help you today?", suggestions = [], uiMode }) {
|
|
780
|
+
const { conversation, setUiModeOverride } = useXandi();
|
|
781
|
+
useEffect(() => {
|
|
782
|
+
setUiModeOverride(uiMode ?? null);
|
|
783
|
+
return () => setUiModeOverride(null);
|
|
784
|
+
}, [uiMode, setUiModeOverride]);
|
|
785
|
+
const isEmpty = conversation.messages.length === 0;
|
|
652
786
|
return /* @__PURE__ */ jsxs("div", {
|
|
653
787
|
className: "flex flex-col",
|
|
654
788
|
children: [isEmpty ? /* @__PURE__ */ jsx(XWelcome, { message: welcomeMessage }) : /* @__PURE__ */ jsx(XMessageContainer, {}), /* @__PURE__ */ jsx(XMainIntake, { suggestions: isEmpty ? suggestions : [] })]
|
|
@@ -657,9 +791,10 @@ function Xandi({ welcomeMessage = "How can I help you today?", suggestions = []
|
|
|
657
791
|
|
|
658
792
|
//#endregion
|
|
659
793
|
//#region src/components/x-header.tsx
|
|
660
|
-
function XHeader({
|
|
794
|
+
function XHeader({ onClose, onToggleHistory }) {
|
|
795
|
+
const { startNewConversation, config } = useXandi();
|
|
661
796
|
return /* @__PURE__ */ jsxs("header", {
|
|
662
|
-
className: "flex items-center justify-between border-b border-ppx-neutral-5 bg-
|
|
797
|
+
className: "flex items-center justify-between border-b border-ppx-neutral-5 bg-transparent px-3 py-2",
|
|
663
798
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
664
799
|
className: "flex items-center gap-2",
|
|
665
800
|
children: [/* @__PURE__ */ jsx(Button, {
|
|
@@ -671,14 +806,14 @@ function XHeader({ title = "Xandi", onClose, onNewChat, onToggleHistory }) {
|
|
|
671
806
|
}), /* @__PURE__ */ jsxs("div", {
|
|
672
807
|
className: "flex items-center gap-2",
|
|
673
808
|
children: [/* @__PURE__ */ jsx(Avatar, {
|
|
674
|
-
imgSrc:
|
|
675
|
-
name:
|
|
809
|
+
imgSrc: config.avatarUrl,
|
|
810
|
+
name: config.assistantName,
|
|
676
811
|
variant: "rounded",
|
|
677
812
|
size: "24px",
|
|
678
813
|
hideTooltip: true
|
|
679
814
|
}), /* @__PURE__ */ jsx("span", {
|
|
680
815
|
className: "font-medium text-ppx-foreground",
|
|
681
|
-
children:
|
|
816
|
+
children: config.assistantName
|
|
682
817
|
})]
|
|
683
818
|
})]
|
|
684
819
|
}), /* @__PURE__ */ jsxs("div", {
|
|
@@ -686,10 +821,10 @@ function XHeader({ title = "Xandi", onClose, onNewChat, onToggleHistory }) {
|
|
|
686
821
|
children: [/* @__PURE__ */ jsx(Button, {
|
|
687
822
|
variant: "ghost",
|
|
688
823
|
size: "icon-sm",
|
|
689
|
-
onClick:
|
|
824
|
+
onClick: startNewConversation,
|
|
690
825
|
"aria-label": "New chat",
|
|
691
826
|
children: /* @__PURE__ */ jsx(NewChatIcon, {})
|
|
692
|
-
}), /* @__PURE__ */ jsx(Button, {
|
|
827
|
+
}), config.uiMode !== "full" && /* @__PURE__ */ jsx(Button, {
|
|
693
828
|
variant: "ghost",
|
|
694
829
|
size: "icon-sm",
|
|
695
830
|
onClick: onClose,
|
|
@@ -702,7 +837,7 @@ function XHeader({ title = "Xandi", onClose, onNewChat, onToggleHistory }) {
|
|
|
702
837
|
|
|
703
838
|
//#endregion
|
|
704
839
|
//#region src/components/x-chat-history.tsx
|
|
705
|
-
function XChatHistory({
|
|
840
|
+
function XChatHistory({ items = [], isLoading = false, activeChatId, onSelectChat }) {
|
|
706
841
|
return /* @__PURE__ */ jsxs("div", {
|
|
707
842
|
className: "flex-1 overflow-y-auto",
|
|
708
843
|
children: [/* @__PURE__ */ jsx("div", {
|
|
@@ -711,30 +846,57 @@ function XChatHistory({ groups = [], activeChatId, onSelectChat }) {
|
|
|
711
846
|
className: "flex items-center gap-2 text-ppx-sm font-medium text-ppx-foreground",
|
|
712
847
|
children: [/* @__PURE__ */ jsx(ChatIcon, {}), "Chats"]
|
|
713
848
|
})
|
|
714
|
-
}),
|
|
849
|
+
}), isLoading && items.length === 0 ? /* @__PURE__ */ jsxs("div", {
|
|
850
|
+
className: "flex flex-col items-center justify-center gap-2 px-6 py-8",
|
|
851
|
+
children: [/* @__PURE__ */ jsx(Spinner, {
|
|
852
|
+
size: "medium",
|
|
853
|
+
className: "text-ppx-neutral-10"
|
|
854
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
855
|
+
className: "text-ppx-sm text-ppx-neutral-10",
|
|
856
|
+
children: "Loading conversations..."
|
|
857
|
+
})]
|
|
858
|
+
}) : items.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
715
859
|
className: "px-6 py-4 text-ppx-sm text-ppx-neutral-10",
|
|
716
860
|
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))]
|
|
861
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
862
|
+
className: "space-y-0.5",
|
|
863
|
+
children: items.map((item) => /* @__PURE__ */ jsx(Button, {
|
|
864
|
+
variant: "ghost",
|
|
865
|
+
onClick: () => onSelectChat?.(item.id),
|
|
866
|
+
className: `w-full justify-start truncate rounded-none px-6 ${activeChatId === item.id ? "bg-ppx-neutral-4 text-ppx-foreground" : "text-ppx-neutral-12"}`,
|
|
867
|
+
children: item.title
|
|
868
|
+
}, item.id))
|
|
869
|
+
})]
|
|
732
870
|
});
|
|
733
871
|
}
|
|
734
872
|
|
|
735
873
|
//#endregion
|
|
736
874
|
//#region src/components/x-sidebar.tsx
|
|
737
|
-
function XSidebar({ isOpen = true,
|
|
875
|
+
function XSidebar({ isOpen = true, onClose }) {
|
|
876
|
+
const { startNewConversation, getConvHistory, loadConversation, conversation } = useXandi();
|
|
877
|
+
const [chatHistoryItems, setChatHistoryItems] = useState([]);
|
|
878
|
+
const [isLoadingHistory, setIsLoadingHistory] = useState(false);
|
|
879
|
+
const fetchInProgressRef = useRef(false);
|
|
880
|
+
useEffect(() => {
|
|
881
|
+
if (!isOpen || !getConvHistory || fetchInProgressRef.current) return;
|
|
882
|
+
fetchInProgressRef.current = true;
|
|
883
|
+
setIsLoadingHistory(true);
|
|
884
|
+
getConvHistory().then((history) => {
|
|
885
|
+
setChatHistoryItems((history ?? []).map((item) => ({
|
|
886
|
+
id: item.id,
|
|
887
|
+
title: item.title,
|
|
888
|
+
timestamp: item.timestamp
|
|
889
|
+
})));
|
|
890
|
+
}).catch((error) => {
|
|
891
|
+
console.error("Failed to fetch conversation history:", error);
|
|
892
|
+
}).finally(() => {
|
|
893
|
+
fetchInProgressRef.current = false;
|
|
894
|
+
setIsLoadingHistory(false);
|
|
895
|
+
});
|
|
896
|
+
}, [isOpen, getConvHistory]);
|
|
897
|
+
const handleSelectChat = (chatId) => {
|
|
898
|
+
loadConversation(chatId);
|
|
899
|
+
};
|
|
738
900
|
if (!isOpen) return null;
|
|
739
901
|
return /* @__PURE__ */ jsxs("aside", {
|
|
740
902
|
className: "flex h-full w-64 flex-col border-r border-ppx-neutral-5 bg-ppx-neutral-2",
|
|
@@ -753,15 +915,16 @@ function XSidebar({ isOpen = true, chatHistory = [], activeChatId, onClose, onNe
|
|
|
753
915
|
className: "p-3",
|
|
754
916
|
children: /* @__PURE__ */ jsxs(Button, {
|
|
755
917
|
variant: "ghost",
|
|
756
|
-
onClick:
|
|
918
|
+
onClick: startNewConversation,
|
|
757
919
|
className: "w-full justify-start gap-3",
|
|
758
920
|
children: [/* @__PURE__ */ jsx(NewChatIcon, { className: "h-5 w-5" }), "New chat"]
|
|
759
921
|
})
|
|
760
922
|
}),
|
|
761
923
|
/* @__PURE__ */ jsx(XChatHistory, {
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
924
|
+
items: chatHistoryItems,
|
|
925
|
+
isLoading: isLoadingHistory,
|
|
926
|
+
activeChatId: conversation.id ?? void 0,
|
|
927
|
+
onSelectChat: handleSelectChat
|
|
765
928
|
})
|
|
766
929
|
]
|
|
767
930
|
});
|
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":["function getXandiAvatarUrl(): string {\n try {\n return new URL(\"./assets/images/xandi-avatar.png\", import.meta.url).href;\n } catch {\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\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 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\nexport interface XandiResponse {\n content: string;\n type?: MessageType;\n debugTrace?: unknown;\n conversationId?: string;\n}\n\nexport interface ConversationSummary {\n id: string;\n title: string;\n timestamp: Date;\n}\n\nexport interface Conversation {\n id: string | null;\n title: string;\n messages: Message[];\n createdAt: Date;\n updatedAt: Date;\n}\n\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\nexport type XandiUIMode = \"full\" | \"sidebar\" | \"floating\";\n\nexport interface XandiConfig {\n avatarUrl?: string;\n assistantName?: string;\n uiMode?: XandiUIMode;\n}\n\nconst defaultConfig: Required<XandiConfig> = {\n avatarUrl: XANDI_AVATAR_URL,\n assistantName: \"Xandi\",\n uiMode: \"full\",\n};\n\nexport interface FetchRespOptions {\n conversationId?: string;\n signal?: AbortSignal;\n}\n\nexport interface GetConvOptions {\n page?: number;\n perPage?: number;\n}\n\nconst defaultGetConvOptions: Required<GetConvOptions> = {\n page: 1,\n perPage: 20,\n};\n\nexport interface XandiHandlers {\n fetchResp: (message: string, options?: FetchRespOptions) => Promise<XandiResponse>;\n getConv?: (conversationId: string, options?: GetConvOptions) => Promise<Conversation>;\n getConvHistory?: () => Promise<ConversationSummary[]>;\n onFeedback?: (messageId: string, conversationId: string, feedback: FeedbackType) => void;\n onStop?: (conversationId: string) => void;\n}\n\nexport interface XandiContextValue {\n conversation: Conversation;\n isLoading: boolean;\n sendMessage: (text: string) => void;\n stopRequest: () => void;\n loadConversation: (conversationId: string, options?: GetConvOptions) => Promise<void>;\n startNewConversation: () => void;\n submitFeedback: (messageId: string, feedback: FeedbackType) => void;\n getConvHistory?: () => Promise<ConversationSummary[]>;\n config: Required<XandiConfig>;\n setUiModeOverride: (mode: XandiUIMode | null) => void;\n}\n\nconst XandiContext = createContext<XandiContextValue | null>(null);\n\nexport interface XandiProviderProps {\n handlers: XandiHandlers;\n conversationId?: string;\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 const config: Required<XandiConfig> = {\n ...defaultConfig,\n ...userConfig,\n uiMode: uiModeOverride ?? userConfig?.uiMode ?? defaultConfig.uiMode,\n };\n\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 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 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\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 };\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\nexport function Root({ children }: { children: React.ReactNode }) {\n return <div className=\"flex items-center gap-1\">{children}</div>;\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 <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 <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\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\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\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\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 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 useLayoutEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n\n const prevCount = prevMessageCountRef.current;\n const prevLastId = prevLastMessageIdRef.current;\n const isPrependOlder =\n prevCount > 0 &&\n messageCount > prevCount &&\n lastMessageId != null &&\n lastMessageId === prevLastId;\n\n if (isPrependOlder) {\n const prevScrollHeight = prevScrollHeightRef.current;\n const prevScrollTop = prevScrollTopRef.current;\n const newScrollHeight = el.scrollHeight;\n el.scrollTop = Math.max(0, 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 useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n if (skipScrollToBottomRef.current) {\n skipScrollToBottomRef.current = false;\n return;\n }\n el.scrollTop = el.scrollHeight - el.clientHeight;\n }, [conversation.messages, isLoading]);\n\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 <div className=\"flex flex-col-reverse gap-5 p-4\">\n {isLoading && <XTypingIndicator />}\n {[...messages].reverse().map((message) => (\n <XMessageItem key={message.id} message={message} />\n ))}\n <div ref={topSentinelRef} className=\"h-1 shrink-0\" aria-hidden=\"true\" />\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 <div className=\"absolute -inset-1 rounded-full bg-gradient-to-b from-ppx-green-4 via-ppx-green-5/50 to-transparent\" />\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 <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 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 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 <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 <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 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 <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 {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 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 <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 <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 <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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"}
|