@mordn/chat-widget 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +338 -0
- package/dist/api/index.d.mts +6 -0
- package/dist/api/index.d.ts +6 -0
- package/dist/api/index.js +242 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/index.mjs +197 -0
- package/dist/api/index.mjs.map +1 -0
- package/dist/db/index.d.mts +323 -0
- package/dist/db/index.d.ts +323 -0
- package/dist/db/index.js +223 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/index.mjs +186 -0
- package/dist/db/index.mjs.map +1 -0
- package/dist/index.d.mts +187 -0
- package/dist/index.d.ts +187 -0
- package/dist/index.js +2663 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2647 -0
- package/dist/index.mjs.map +1 -0
- package/dist/styles.css +2 -0
- package/package.json +100 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2663 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
"use client";
|
|
4
|
+
var __create = Object.create;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
9
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
+
var __export = (target, all) => {
|
|
11
|
+
for (var name in all)
|
|
12
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
13
|
+
};
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
+
for (let key of __getOwnPropNames(from))
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
18
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
23
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
24
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
25
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
26
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
27
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
28
|
+
mod
|
|
29
|
+
));
|
|
30
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
31
|
+
|
|
32
|
+
// src/index.ts
|
|
33
|
+
var src_exports = {};
|
|
34
|
+
__export(src_exports, {
|
|
35
|
+
Button: () => Button,
|
|
36
|
+
ChatWidget: () => ChatWidget,
|
|
37
|
+
Dialog: () => Dialog,
|
|
38
|
+
DialogContent: () => DialogContent,
|
|
39
|
+
DialogDescription: () => DialogDescription,
|
|
40
|
+
DialogHeader: () => DialogHeader,
|
|
41
|
+
DialogTitle: () => DialogTitle,
|
|
42
|
+
Input: () => Input,
|
|
43
|
+
default: () => ChatWidget_default,
|
|
44
|
+
fontOptions: () => fontOptions,
|
|
45
|
+
useChatTheme: () => useChatTheme
|
|
46
|
+
});
|
|
47
|
+
module.exports = __toCommonJS(src_exports);
|
|
48
|
+
|
|
49
|
+
// src/ChatWidget.tsx
|
|
50
|
+
var import_react11 = require("react");
|
|
51
|
+
|
|
52
|
+
// src/ui/button.tsx
|
|
53
|
+
var import_react_slot = require("@radix-ui/react-slot");
|
|
54
|
+
var import_class_variance_authority = require("class-variance-authority");
|
|
55
|
+
|
|
56
|
+
// src/utils/cn.ts
|
|
57
|
+
var import_clsx = require("clsx");
|
|
58
|
+
var import_tailwind_merge = require("tailwind-merge");
|
|
59
|
+
function cn(...inputs) {
|
|
60
|
+
return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/ui/button.tsx
|
|
64
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
65
|
+
var buttonVariants = (0, import_class_variance_authority.cva)(
|
|
66
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
67
|
+
{
|
|
68
|
+
variants: {
|
|
69
|
+
variant: {
|
|
70
|
+
default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
|
71
|
+
destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
72
|
+
outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
|
73
|
+
secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
|
74
|
+
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
75
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
76
|
+
},
|
|
77
|
+
size: {
|
|
78
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
79
|
+
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
80
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
81
|
+
icon: "size-9"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
defaultVariants: {
|
|
85
|
+
variant: "default",
|
|
86
|
+
size: "default"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
function Button({
|
|
91
|
+
className,
|
|
92
|
+
variant,
|
|
93
|
+
size,
|
|
94
|
+
asChild = false,
|
|
95
|
+
...props
|
|
96
|
+
}) {
|
|
97
|
+
const Comp = asChild ? import_react_slot.Slot : "button";
|
|
98
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
99
|
+
Comp,
|
|
100
|
+
{
|
|
101
|
+
"data-slot": "button",
|
|
102
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
103
|
+
...props
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/components/conversation.tsx
|
|
109
|
+
var import_lucide_react = require("lucide-react");
|
|
110
|
+
var import_react = require("react");
|
|
111
|
+
var import_use_stick_to_bottom = require("use-stick-to-bottom");
|
|
112
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
113
|
+
var Conversation = ({ className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
114
|
+
import_use_stick_to_bottom.StickToBottom,
|
|
115
|
+
{
|
|
116
|
+
className: cn("relative flex-1 overflow-y-auto", className),
|
|
117
|
+
initial: "smooth",
|
|
118
|
+
resize: "smooth",
|
|
119
|
+
role: "log",
|
|
120
|
+
...props
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
var ConversationContent = ({
|
|
124
|
+
className,
|
|
125
|
+
...props
|
|
126
|
+
}) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_use_stick_to_bottom.StickToBottom.Content, { className: cn("p-4", className), ...props });
|
|
127
|
+
var ConversationScrollButton = ({
|
|
128
|
+
className,
|
|
129
|
+
...props
|
|
130
|
+
}) => {
|
|
131
|
+
const { isAtBottom, scrollToBottom } = (0, import_use_stick_to_bottom.useStickToBottomContext)();
|
|
132
|
+
const handleScrollToBottom = (0, import_react.useCallback)(() => {
|
|
133
|
+
scrollToBottom();
|
|
134
|
+
}, [scrollToBottom]);
|
|
135
|
+
return !isAtBottom && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
136
|
+
Button,
|
|
137
|
+
{
|
|
138
|
+
className: cn(
|
|
139
|
+
"absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full",
|
|
140
|
+
className
|
|
141
|
+
),
|
|
142
|
+
onClick: handleScrollToBottom,
|
|
143
|
+
size: "icon",
|
|
144
|
+
type: "button",
|
|
145
|
+
variant: "outline",
|
|
146
|
+
...props,
|
|
147
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.ArrowDownIcon, { className: "size-4" })
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// src/ui/avatar.tsx
|
|
153
|
+
var AvatarPrimitive = __toESM(require("@radix-ui/react-avatar"));
|
|
154
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
155
|
+
|
|
156
|
+
// src/components/message.tsx
|
|
157
|
+
var import_class_variance_authority2 = require("class-variance-authority");
|
|
158
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
159
|
+
var Message = ({ className, from, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
160
|
+
"div",
|
|
161
|
+
{
|
|
162
|
+
className: cn(
|
|
163
|
+
"group flex w-full items-end justify-end gap-2",
|
|
164
|
+
from === "user" ? "is-user" : "is-assistant flex-row-reverse justify-end",
|
|
165
|
+
className
|
|
166
|
+
),
|
|
167
|
+
...props
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
var messageContentVariants = (0, import_class_variance_authority2.cva)(
|
|
171
|
+
"flex flex-col gap-2 overflow-hidden leading-relaxed chat-message-content",
|
|
172
|
+
{
|
|
173
|
+
variants: {
|
|
174
|
+
variant: {
|
|
175
|
+
contained: [
|
|
176
|
+
// User messages: compact bubbles on the right (max 85% width)
|
|
177
|
+
"group-[.is-user]:max-w-[85%] group-[.is-user]:rounded-2xl group-[.is-user]:rounded-br-lg group-[.is-user]:shadow-sm group-[.is-user]:px-4 group-[.is-user]:py-3",
|
|
178
|
+
// Assistant messages: no bubble, just text on background (max 100% width)
|
|
179
|
+
"group-[.is-assistant]:max-w-full"
|
|
180
|
+
],
|
|
181
|
+
flat: [
|
|
182
|
+
// User messages: compact on the right
|
|
183
|
+
"group-[.is-user]:max-w-[85%] group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:rounded-2xl group-[.is-user]:rounded-br-lg",
|
|
184
|
+
// Assistant messages: full width
|
|
185
|
+
"group-[.is-assistant]:max-w-full"
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
defaultVariants: {
|
|
190
|
+
variant: "contained"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
var MessageContent = ({
|
|
195
|
+
children,
|
|
196
|
+
className,
|
|
197
|
+
variant,
|
|
198
|
+
...props
|
|
199
|
+
}) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
200
|
+
"div",
|
|
201
|
+
{
|
|
202
|
+
className: cn(messageContentVariants({ variant, className })),
|
|
203
|
+
...props,
|
|
204
|
+
children
|
|
205
|
+
}
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
// src/ui/dropdown-menu.tsx
|
|
209
|
+
var DropdownMenuPrimitive = __toESM(require("@radix-ui/react-dropdown-menu"));
|
|
210
|
+
var import_lucide_react2 = require("lucide-react");
|
|
211
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
212
|
+
|
|
213
|
+
// src/ui/select.tsx
|
|
214
|
+
var SelectPrimitive = __toESM(require("@radix-ui/react-select"));
|
|
215
|
+
var import_lucide_react3 = require("lucide-react");
|
|
216
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
217
|
+
|
|
218
|
+
// src/ui/textarea.tsx
|
|
219
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
220
|
+
function Textarea({ className, ...props }) {
|
|
221
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
222
|
+
"textarea",
|
|
223
|
+
{
|
|
224
|
+
"data-slot": "textarea",
|
|
225
|
+
className: cn(
|
|
226
|
+
"border-input placeholder:text-muted-foreground focus-visible:border-ring aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base transition-colors outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
227
|
+
className
|
|
228
|
+
),
|
|
229
|
+
...props
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/components/prompt-input.tsx
|
|
235
|
+
var import_lucide_react4 = require("lucide-react");
|
|
236
|
+
var import_nanoid = require("nanoid");
|
|
237
|
+
var import_react2 = require("react");
|
|
238
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
239
|
+
var AttachmentsContext = (0, import_react2.createContext)(null);
|
|
240
|
+
var usePromptInputAttachments = () => {
|
|
241
|
+
const context = (0, import_react2.useContext)(AttachmentsContext);
|
|
242
|
+
if (!context) {
|
|
243
|
+
throw new Error(
|
|
244
|
+
"usePromptInputAttachments must be used within a PromptInput"
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
return context;
|
|
248
|
+
};
|
|
249
|
+
function PromptInputAttachment({
|
|
250
|
+
data,
|
|
251
|
+
className,
|
|
252
|
+
...props
|
|
253
|
+
}) {
|
|
254
|
+
const attachments = usePromptInputAttachments();
|
|
255
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
256
|
+
"div",
|
|
257
|
+
{
|
|
258
|
+
className: cn("group relative h-14 w-14 rounded-lg border", className),
|
|
259
|
+
...props,
|
|
260
|
+
children: [
|
|
261
|
+
data.mediaType?.startsWith("image/") && data.url ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
262
|
+
"img",
|
|
263
|
+
{
|
|
264
|
+
alt: data.filename || "attachment",
|
|
265
|
+
className: "size-full rounded-lg object-cover",
|
|
266
|
+
height: 56,
|
|
267
|
+
src: data.url,
|
|
268
|
+
width: 56
|
|
269
|
+
}
|
|
270
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex size-full items-center justify-center text-muted-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.PaperclipIcon, { className: "size-4" }) }),
|
|
271
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
272
|
+
Button,
|
|
273
|
+
{
|
|
274
|
+
"aria-label": "Remove attachment",
|
|
275
|
+
className: "-right-1 -top-1 absolute h-4 w-4 rounded-full opacity-0 group-hover:opacity-100 p-0 [&_svg]:h-2 [&_svg]:w-2",
|
|
276
|
+
onClick: () => attachments.remove(data.id),
|
|
277
|
+
size: "icon",
|
|
278
|
+
type: "button",
|
|
279
|
+
variant: "outline",
|
|
280
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.XIcon, { className: "h-2 w-2 shrink-0" })
|
|
281
|
+
}
|
|
282
|
+
)
|
|
283
|
+
]
|
|
284
|
+
},
|
|
285
|
+
data.id
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
function PromptInputAttachments({
|
|
289
|
+
className,
|
|
290
|
+
children,
|
|
291
|
+
...props
|
|
292
|
+
}) {
|
|
293
|
+
const attachments = usePromptInputAttachments();
|
|
294
|
+
const [height, setHeight] = (0, import_react2.useState)(0);
|
|
295
|
+
const contentRef = (0, import_react2.useRef)(null);
|
|
296
|
+
(0, import_react2.useLayoutEffect)(() => {
|
|
297
|
+
const el = contentRef.current;
|
|
298
|
+
if (!el) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const ro = new ResizeObserver(() => {
|
|
302
|
+
setHeight(el.getBoundingClientRect().height);
|
|
303
|
+
});
|
|
304
|
+
ro.observe(el);
|
|
305
|
+
setHeight(el.getBoundingClientRect().height);
|
|
306
|
+
return () => ro.disconnect();
|
|
307
|
+
}, []);
|
|
308
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
309
|
+
"div",
|
|
310
|
+
{
|
|
311
|
+
"aria-live": "polite",
|
|
312
|
+
className: cn(
|
|
313
|
+
"overflow-hidden transition-[height] duration-200 ease-out",
|
|
314
|
+
className
|
|
315
|
+
),
|
|
316
|
+
style: { height: attachments.files.length ? height : 0 },
|
|
317
|
+
...props,
|
|
318
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex flex-wrap gap-2 p-3 pt-3", ref: contentRef, children: attachments.files.map((file) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react2.Fragment, { children: children(file) }, file.id)) })
|
|
319
|
+
}
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
var PromptInput = ({
|
|
323
|
+
className,
|
|
324
|
+
accept,
|
|
325
|
+
multiple,
|
|
326
|
+
globalDrop,
|
|
327
|
+
syncHiddenInput,
|
|
328
|
+
maxFiles,
|
|
329
|
+
maxFileSize,
|
|
330
|
+
onError,
|
|
331
|
+
onSubmit,
|
|
332
|
+
...props
|
|
333
|
+
}) => {
|
|
334
|
+
const [items, setItems] = (0, import_react2.useState)([]);
|
|
335
|
+
const inputRef = (0, import_react2.useRef)(null);
|
|
336
|
+
const anchorRef = (0, import_react2.useRef)(null);
|
|
337
|
+
const formRef = (0, import_react2.useRef)(null);
|
|
338
|
+
(0, import_react2.useEffect)(() => {
|
|
339
|
+
const root = anchorRef.current?.closest("form");
|
|
340
|
+
if (root instanceof HTMLFormElement) {
|
|
341
|
+
formRef.current = root;
|
|
342
|
+
}
|
|
343
|
+
}, []);
|
|
344
|
+
const openFileDialog = (0, import_react2.useCallback)(() => {
|
|
345
|
+
inputRef.current?.click();
|
|
346
|
+
}, []);
|
|
347
|
+
const matchesAccept = (0, import_react2.useCallback)(
|
|
348
|
+
(f) => {
|
|
349
|
+
if (!accept || accept.trim() === "") {
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
if (accept.includes("image/*")) {
|
|
353
|
+
return f.type.startsWith("image/");
|
|
354
|
+
}
|
|
355
|
+
return true;
|
|
356
|
+
},
|
|
357
|
+
[accept]
|
|
358
|
+
);
|
|
359
|
+
const add = (0, import_react2.useCallback)(
|
|
360
|
+
(files) => {
|
|
361
|
+
const incoming = Array.from(files);
|
|
362
|
+
const accepted = incoming.filter((f) => matchesAccept(f));
|
|
363
|
+
if (accepted.length === 0) {
|
|
364
|
+
onError?.({
|
|
365
|
+
code: "accept",
|
|
366
|
+
message: "No files match the accepted types."
|
|
367
|
+
});
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const withinSize = (f) => maxFileSize ? f.size <= maxFileSize : true;
|
|
371
|
+
const sized = accepted.filter(withinSize);
|
|
372
|
+
if (sized.length === 0 && accepted.length > 0) {
|
|
373
|
+
onError?.({
|
|
374
|
+
code: "max_file_size",
|
|
375
|
+
message: "All files exceed the maximum size."
|
|
376
|
+
});
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
setItems((prev) => {
|
|
380
|
+
const capacity = typeof maxFiles === "number" ? Math.max(0, maxFiles - prev.length) : void 0;
|
|
381
|
+
const capped = typeof capacity === "number" ? sized.slice(0, capacity) : sized;
|
|
382
|
+
if (typeof capacity === "number" && sized.length > capacity) {
|
|
383
|
+
onError?.({
|
|
384
|
+
code: "max_files",
|
|
385
|
+
message: "Too many files. Some were not added."
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
const next = [];
|
|
389
|
+
for (const file of capped) {
|
|
390
|
+
next.push({
|
|
391
|
+
id: (0, import_nanoid.nanoid)(),
|
|
392
|
+
type: "file",
|
|
393
|
+
url: URL.createObjectURL(file),
|
|
394
|
+
mediaType: file.type,
|
|
395
|
+
filename: file.name
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
return prev.concat(next);
|
|
399
|
+
});
|
|
400
|
+
},
|
|
401
|
+
[matchesAccept, maxFiles, maxFileSize, onError]
|
|
402
|
+
);
|
|
403
|
+
const remove = (0, import_react2.useCallback)((id) => {
|
|
404
|
+
setItems((prev) => {
|
|
405
|
+
const found = prev.find((file) => file.id === id);
|
|
406
|
+
if (found?.url) {
|
|
407
|
+
URL.revokeObjectURL(found.url);
|
|
408
|
+
}
|
|
409
|
+
return prev.filter((file) => file.id !== id);
|
|
410
|
+
});
|
|
411
|
+
}, []);
|
|
412
|
+
const clear = (0, import_react2.useCallback)(() => {
|
|
413
|
+
setItems((prev) => {
|
|
414
|
+
for (const file of prev) {
|
|
415
|
+
if (file.url) {
|
|
416
|
+
URL.revokeObjectURL(file.url);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return [];
|
|
420
|
+
});
|
|
421
|
+
}, []);
|
|
422
|
+
(0, import_react2.useEffect)(() => {
|
|
423
|
+
if (syncHiddenInput && inputRef.current) {
|
|
424
|
+
if (items.length === 0) {
|
|
425
|
+
inputRef.current.value = "";
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}, [items, syncHiddenInput]);
|
|
429
|
+
(0, import_react2.useEffect)(() => {
|
|
430
|
+
const form = formRef.current;
|
|
431
|
+
if (!form) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
const onDragOver = (e) => {
|
|
435
|
+
if (e.dataTransfer?.types?.includes("Files")) {
|
|
436
|
+
e.preventDefault();
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
const onDrop = (e) => {
|
|
440
|
+
if (e.dataTransfer?.types?.includes("Files")) {
|
|
441
|
+
e.preventDefault();
|
|
442
|
+
}
|
|
443
|
+
if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {
|
|
444
|
+
add(e.dataTransfer.files);
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
form.addEventListener("dragover", onDragOver);
|
|
448
|
+
form.addEventListener("drop", onDrop);
|
|
449
|
+
return () => {
|
|
450
|
+
form.removeEventListener("dragover", onDragOver);
|
|
451
|
+
form.removeEventListener("drop", onDrop);
|
|
452
|
+
};
|
|
453
|
+
}, [add]);
|
|
454
|
+
(0, import_react2.useEffect)(() => {
|
|
455
|
+
if (!globalDrop) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
const onDragOver = (e) => {
|
|
459
|
+
if (e.dataTransfer?.types?.includes("Files")) {
|
|
460
|
+
e.preventDefault();
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
const onDrop = (e) => {
|
|
464
|
+
if (e.dataTransfer?.types?.includes("Files")) {
|
|
465
|
+
e.preventDefault();
|
|
466
|
+
}
|
|
467
|
+
if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {
|
|
468
|
+
add(e.dataTransfer.files);
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
document.addEventListener("dragover", onDragOver);
|
|
472
|
+
document.addEventListener("drop", onDrop);
|
|
473
|
+
return () => {
|
|
474
|
+
document.removeEventListener("dragover", onDragOver);
|
|
475
|
+
document.removeEventListener("drop", onDrop);
|
|
476
|
+
};
|
|
477
|
+
}, [add, globalDrop]);
|
|
478
|
+
const handleChange = (event) => {
|
|
479
|
+
if (event.currentTarget.files) {
|
|
480
|
+
add(event.currentTarget.files);
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
const handleSubmit = (event) => {
|
|
484
|
+
event.preventDefault();
|
|
485
|
+
const files = items.map(({ ...item }) => ({
|
|
486
|
+
...item
|
|
487
|
+
}));
|
|
488
|
+
onSubmit({ text: event.currentTarget.message.value, files }, event);
|
|
489
|
+
clear();
|
|
490
|
+
};
|
|
491
|
+
const ctx = (0, import_react2.useMemo)(
|
|
492
|
+
() => ({
|
|
493
|
+
files: items.map((item) => ({ ...item, id: item.id })),
|
|
494
|
+
add,
|
|
495
|
+
remove,
|
|
496
|
+
clear,
|
|
497
|
+
openFileDialog,
|
|
498
|
+
fileInputRef: inputRef
|
|
499
|
+
}),
|
|
500
|
+
[items, add, remove, clear, openFileDialog]
|
|
501
|
+
);
|
|
502
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(AttachmentsContext.Provider, { value: ctx, children: [
|
|
503
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { "aria-hidden": "true", className: "hidden", ref: anchorRef }),
|
|
504
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
505
|
+
"input",
|
|
506
|
+
{
|
|
507
|
+
accept,
|
|
508
|
+
className: "hidden",
|
|
509
|
+
multiple,
|
|
510
|
+
onChange: handleChange,
|
|
511
|
+
ref: inputRef,
|
|
512
|
+
type: "file"
|
|
513
|
+
}
|
|
514
|
+
),
|
|
515
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
516
|
+
"form",
|
|
517
|
+
{
|
|
518
|
+
className: cn(
|
|
519
|
+
"w-full divide-y overflow-hidden rounded-xl border bg-background focus-within:border-ring transition-colors",
|
|
520
|
+
"[&:focus-within]:shadow-none [&:focus]:shadow-none shadow-none",
|
|
521
|
+
className
|
|
522
|
+
),
|
|
523
|
+
onSubmit: handleSubmit,
|
|
524
|
+
style: { boxShadow: "none" },
|
|
525
|
+
...props
|
|
526
|
+
}
|
|
527
|
+
)
|
|
528
|
+
] });
|
|
529
|
+
};
|
|
530
|
+
var PromptInputBody = ({
|
|
531
|
+
className,
|
|
532
|
+
...props
|
|
533
|
+
}) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: cn(className, "flex flex-col"), ...props });
|
|
534
|
+
var PromptInputTextarea = ({
|
|
535
|
+
onChange,
|
|
536
|
+
className,
|
|
537
|
+
placeholder = "What would you like to know?",
|
|
538
|
+
...props
|
|
539
|
+
}) => {
|
|
540
|
+
const attachments = usePromptInputAttachments();
|
|
541
|
+
const handleKeyDown = (e) => {
|
|
542
|
+
if (e.key === "Enter") {
|
|
543
|
+
if (e.nativeEvent.isComposing) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (e.shiftKey) {
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
e.preventDefault();
|
|
550
|
+
const form = e.currentTarget.form;
|
|
551
|
+
if (form) {
|
|
552
|
+
form.requestSubmit();
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
const handlePaste = (event) => {
|
|
557
|
+
const items = event.clipboardData?.items;
|
|
558
|
+
if (!items) {
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
const files = [];
|
|
562
|
+
for (const item of items) {
|
|
563
|
+
if (item.kind === "file") {
|
|
564
|
+
const file = item.getAsFile();
|
|
565
|
+
if (file) {
|
|
566
|
+
files.push(file);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (files.length > 0) {
|
|
571
|
+
event.preventDefault();
|
|
572
|
+
attachments.add(files);
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
576
|
+
Textarea,
|
|
577
|
+
{
|
|
578
|
+
className: cn(
|
|
579
|
+
"w-full resize-none rounded-none border-none p-3 shadow-none outline-none ring-0",
|
|
580
|
+
"field-sizing-content",
|
|
581
|
+
"max-h-48 min-h-16",
|
|
582
|
+
"focus-visible:ring-0 focus-visible:border-red-500 focus-visible:shadow-none focus:ring-0 focus:shadow-none",
|
|
583
|
+
className
|
|
584
|
+
),
|
|
585
|
+
name: "message",
|
|
586
|
+
onChange: (e) => {
|
|
587
|
+
onChange?.(e);
|
|
588
|
+
},
|
|
589
|
+
onKeyDown: handleKeyDown,
|
|
590
|
+
onPaste: handlePaste,
|
|
591
|
+
placeholder,
|
|
592
|
+
...props
|
|
593
|
+
}
|
|
594
|
+
);
|
|
595
|
+
};
|
|
596
|
+
var PromptInputToolbar = ({
|
|
597
|
+
className,
|
|
598
|
+
...props
|
|
599
|
+
}) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
600
|
+
"div",
|
|
601
|
+
{
|
|
602
|
+
className: cn("flex items-center justify-between p-1", className),
|
|
603
|
+
...props
|
|
604
|
+
}
|
|
605
|
+
);
|
|
606
|
+
var PromptInputTools = ({
|
|
607
|
+
className,
|
|
608
|
+
...props
|
|
609
|
+
}) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
610
|
+
"div",
|
|
611
|
+
{
|
|
612
|
+
className: cn(
|
|
613
|
+
"flex items-center gap-1",
|
|
614
|
+
"[&_button:first-child]:rounded-bl-xl",
|
|
615
|
+
className
|
|
616
|
+
),
|
|
617
|
+
...props
|
|
618
|
+
}
|
|
619
|
+
);
|
|
620
|
+
var PromptInputButton = ({
|
|
621
|
+
variant = "ghost",
|
|
622
|
+
className,
|
|
623
|
+
size,
|
|
624
|
+
...props
|
|
625
|
+
}) => {
|
|
626
|
+
const newSize = size ?? import_react2.Children.count(props.children) > 1 ? "default" : "icon";
|
|
627
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
628
|
+
Button,
|
|
629
|
+
{
|
|
630
|
+
className: cn(
|
|
631
|
+
"shrink-0 gap-1.5 rounded-lg",
|
|
632
|
+
variant === "ghost" && "text-muted-foreground",
|
|
633
|
+
newSize === "default" && "px-3",
|
|
634
|
+
className
|
|
635
|
+
),
|
|
636
|
+
size: newSize,
|
|
637
|
+
type: "button",
|
|
638
|
+
variant,
|
|
639
|
+
...props
|
|
640
|
+
}
|
|
641
|
+
);
|
|
642
|
+
};
|
|
643
|
+
var PromptInputSubmit = ({
|
|
644
|
+
className,
|
|
645
|
+
variant = "default",
|
|
646
|
+
size = "icon",
|
|
647
|
+
status,
|
|
648
|
+
children,
|
|
649
|
+
...props
|
|
650
|
+
}) => {
|
|
651
|
+
let Icon2 = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.SendIcon, { className: "size-4" });
|
|
652
|
+
if (status === "submitted") {
|
|
653
|
+
Icon2 = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.Loader2Icon, { className: "size-4 animate-spin" });
|
|
654
|
+
} else if (status === "streaming") {
|
|
655
|
+
Icon2 = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.SquareIcon, { className: "size-4" });
|
|
656
|
+
} else if (status === "error") {
|
|
657
|
+
Icon2 = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.XIcon, { className: "size-4" });
|
|
658
|
+
}
|
|
659
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
660
|
+
Button,
|
|
661
|
+
{
|
|
662
|
+
className: cn("gap-1.5 rounded-lg", className),
|
|
663
|
+
size,
|
|
664
|
+
type: "submit",
|
|
665
|
+
variant,
|
|
666
|
+
...props,
|
|
667
|
+
children: children ?? Icon2
|
|
668
|
+
}
|
|
669
|
+
);
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
// src/components/message-attachments.tsx
|
|
673
|
+
var import_lucide_react5 = require("lucide-react");
|
|
674
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
675
|
+
function MessageAttachments({ attachments, className }) {
|
|
676
|
+
if (!attachments || attachments.length === 0) {
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
const handleAttachmentClick = (attachment) => {
|
|
680
|
+
if (attachment.url.startsWith("data:")) {
|
|
681
|
+
const newWindow = window.open("", "_blank");
|
|
682
|
+
if (newWindow) {
|
|
683
|
+
newWindow.document.write(`
|
|
684
|
+
<html>
|
|
685
|
+
<head><title>${attachment.filename}</title></head>
|
|
686
|
+
<body style="margin:0; padding:20px; background:#f5f5f5; display:flex; justify-content:center; align-items:center; min-height:100vh;">
|
|
687
|
+
<img src="${attachment.url}" alt="${attachment.filename}" style="max-width:100%; max-height:100%; object-fit:contain; border-radius:8px; box-shadow:0 4px 12px rgba(0,0,0,0.15);" />
|
|
688
|
+
</body>
|
|
689
|
+
</html>
|
|
690
|
+
`);
|
|
691
|
+
newWindow.document.close();
|
|
692
|
+
}
|
|
693
|
+
} else if (attachment.url.startsWith("blob:")) {
|
|
694
|
+
window.open(attachment.url, "_blank");
|
|
695
|
+
} else if (attachment.url.startsWith("http")) {
|
|
696
|
+
window.open(attachment.url, "_blank");
|
|
697
|
+
} else {
|
|
698
|
+
window.open(attachment.url, "_blank");
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: cn("flex flex-wrap gap-2", className), children: attachments.map((attachment, index) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
702
|
+
"div",
|
|
703
|
+
{
|
|
704
|
+
className: "group relative h-14 w-14 rounded-lg",
|
|
705
|
+
children: attachment.mediaType.startsWith("image/") ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
706
|
+
"img",
|
|
707
|
+
{
|
|
708
|
+
src: attachment.url,
|
|
709
|
+
alt: attachment.filename,
|
|
710
|
+
className: "size-full rounded-lg object-cover cursor-pointer hover:opacity-80 transition-opacity",
|
|
711
|
+
onClick: () => handleAttachmentClick(attachment)
|
|
712
|
+
}
|
|
713
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
714
|
+
"div",
|
|
715
|
+
{
|
|
716
|
+
className: "flex size-full items-center justify-center text-muted-foreground cursor-pointer hover:bg-secondary/50 rounded-lg transition-colors",
|
|
717
|
+
onClick: () => handleAttachmentClick(attachment),
|
|
718
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react5.PaperclipIcon, { className: "size-4" })
|
|
719
|
+
}
|
|
720
|
+
)
|
|
721
|
+
},
|
|
722
|
+
index
|
|
723
|
+
)) });
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// src/components/interface.tsx
|
|
727
|
+
var import_react8 = require("react");
|
|
728
|
+
var import_lucide_react10 = require("lucide-react");
|
|
729
|
+
var import_react9 = require("react");
|
|
730
|
+
var import_react10 = require("@ai-sdk/react");
|
|
731
|
+
var import_ai = require("ai");
|
|
732
|
+
|
|
733
|
+
// src/components/response.tsx
|
|
734
|
+
var import_react4 = require("react");
|
|
735
|
+
var import_streamdown = require("streamdown");
|
|
736
|
+
|
|
737
|
+
// src/hooks/use-code-scroll.ts
|
|
738
|
+
var import_react3 = require("react");
|
|
739
|
+
function useCodeBlockAutoScroll(isStreaming) {
|
|
740
|
+
const observerRef = (0, import_react3.useRef)(null);
|
|
741
|
+
const containerRef = (0, import_react3.useRef)(null);
|
|
742
|
+
(0, import_react3.useEffect)(() => {
|
|
743
|
+
if (!isStreaming || !containerRef.current) {
|
|
744
|
+
if (observerRef.current) {
|
|
745
|
+
observerRef.current.disconnect();
|
|
746
|
+
observerRef.current = null;
|
|
747
|
+
}
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
observerRef.current = new MutationObserver(() => {
|
|
751
|
+
if (!containerRef.current) return;
|
|
752
|
+
const codeBlocks = containerRef.current.querySelectorAll("pre");
|
|
753
|
+
codeBlocks.forEach((pre) => {
|
|
754
|
+
pre.scrollTop = pre.scrollHeight;
|
|
755
|
+
});
|
|
756
|
+
});
|
|
757
|
+
observerRef.current.observe(containerRef.current, {
|
|
758
|
+
childList: true,
|
|
759
|
+
subtree: true,
|
|
760
|
+
characterData: true
|
|
761
|
+
});
|
|
762
|
+
return () => {
|
|
763
|
+
if (observerRef.current) {
|
|
764
|
+
observerRef.current.disconnect();
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
}, [isStreaming]);
|
|
768
|
+
return containerRef;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// src/components/response.tsx
|
|
772
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
773
|
+
var Response = (0, import_react4.memo)(
|
|
774
|
+
({ className, isStreaming = false, ...props }) => {
|
|
775
|
+
const containerRef = useCodeBlockAutoScroll(isStreaming);
|
|
776
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { ref: containerRef, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
777
|
+
import_streamdown.Streamdown,
|
|
778
|
+
{
|
|
779
|
+
className: cn(
|
|
780
|
+
"size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
|
|
781
|
+
className
|
|
782
|
+
),
|
|
783
|
+
...props
|
|
784
|
+
}
|
|
785
|
+
) });
|
|
786
|
+
},
|
|
787
|
+
(prevProps, nextProps) => prevProps.children === nextProps.children
|
|
788
|
+
);
|
|
789
|
+
Response.displayName = "Response";
|
|
790
|
+
|
|
791
|
+
// src/ui/collapsible.tsx
|
|
792
|
+
var CollapsiblePrimitive = __toESM(require("@radix-ui/react-collapsible"));
|
|
793
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
794
|
+
function Collapsible({
|
|
795
|
+
...props
|
|
796
|
+
}) {
|
|
797
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(CollapsiblePrimitive.Root, { "data-slot": "collapsible", ...props });
|
|
798
|
+
}
|
|
799
|
+
function CollapsibleTrigger2({
|
|
800
|
+
...props
|
|
801
|
+
}) {
|
|
802
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
803
|
+
CollapsiblePrimitive.CollapsibleTrigger,
|
|
804
|
+
{
|
|
805
|
+
"data-slot": "collapsible-trigger",
|
|
806
|
+
...props
|
|
807
|
+
}
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
function CollapsibleContent2({
|
|
811
|
+
...props
|
|
812
|
+
}) {
|
|
813
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
814
|
+
CollapsiblePrimitive.CollapsibleContent,
|
|
815
|
+
{
|
|
816
|
+
"data-slot": "collapsible-content",
|
|
817
|
+
...props
|
|
818
|
+
}
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// src/components/sources.tsx
|
|
823
|
+
var import_lucide_react6 = require("lucide-react");
|
|
824
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
825
|
+
var Sources = ({ className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
826
|
+
Collapsible,
|
|
827
|
+
{
|
|
828
|
+
className: cn("not-prose mb-4 text-primary text-xs", className),
|
|
829
|
+
...props
|
|
830
|
+
}
|
|
831
|
+
);
|
|
832
|
+
var SourcesTrigger = ({
|
|
833
|
+
className,
|
|
834
|
+
count,
|
|
835
|
+
children,
|
|
836
|
+
...props
|
|
837
|
+
}) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
838
|
+
CollapsibleTrigger2,
|
|
839
|
+
{
|
|
840
|
+
className: cn("flex items-center gap-2", className),
|
|
841
|
+
...props,
|
|
842
|
+
children: children ?? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
|
|
843
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { className: "font-medium", children: [
|
|
844
|
+
"Used ",
|
|
845
|
+
count,
|
|
846
|
+
" sources"
|
|
847
|
+
] }),
|
|
848
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react6.ChevronDownIcon, { className: "h-4 w-4" })
|
|
849
|
+
] })
|
|
850
|
+
}
|
|
851
|
+
);
|
|
852
|
+
var SourcesContent = ({
|
|
853
|
+
className,
|
|
854
|
+
...props
|
|
855
|
+
}) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
856
|
+
CollapsibleContent2,
|
|
857
|
+
{
|
|
858
|
+
className: cn(
|
|
859
|
+
"mt-3 flex w-fit flex-col gap-2",
|
|
860
|
+
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
|
|
861
|
+
className
|
|
862
|
+
),
|
|
863
|
+
...props
|
|
864
|
+
}
|
|
865
|
+
);
|
|
866
|
+
var Source = ({ href, title, children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
867
|
+
"a",
|
|
868
|
+
{
|
|
869
|
+
className: "flex items-center gap-2",
|
|
870
|
+
href,
|
|
871
|
+
rel: "noreferrer",
|
|
872
|
+
target: "_blank",
|
|
873
|
+
...props,
|
|
874
|
+
children: children ?? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
|
|
875
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react6.BookIcon, { className: "h-4 w-4" }),
|
|
876
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "block font-medium", children: title })
|
|
877
|
+
] })
|
|
878
|
+
}
|
|
879
|
+
);
|
|
880
|
+
|
|
881
|
+
// src/components/reasoning.tsx
|
|
882
|
+
var import_react_use_controllable_state = require("@radix-ui/react-use-controllable-state");
|
|
883
|
+
var import_lucide_react7 = require("lucide-react");
|
|
884
|
+
var import_react5 = require("react");
|
|
885
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
886
|
+
var ReasoningContext = (0, import_react5.createContext)(null);
|
|
887
|
+
var useReasoning = () => {
|
|
888
|
+
const context = (0, import_react5.useContext)(ReasoningContext);
|
|
889
|
+
if (!context) {
|
|
890
|
+
throw new Error("Reasoning components must be used within Reasoning");
|
|
891
|
+
}
|
|
892
|
+
return context;
|
|
893
|
+
};
|
|
894
|
+
var AUTO_CLOSE_DELAY = 1e3;
|
|
895
|
+
var MS_IN_S = 1e3;
|
|
896
|
+
var Reasoning = (0, import_react5.memo)(
|
|
897
|
+
({
|
|
898
|
+
className,
|
|
899
|
+
isStreaming = false,
|
|
900
|
+
open,
|
|
901
|
+
defaultOpen = true,
|
|
902
|
+
onOpenChange,
|
|
903
|
+
duration: durationProp,
|
|
904
|
+
children,
|
|
905
|
+
...props
|
|
906
|
+
}) => {
|
|
907
|
+
const [isOpen, setIsOpen] = (0, import_react_use_controllable_state.useControllableState)({
|
|
908
|
+
prop: open,
|
|
909
|
+
defaultProp: defaultOpen,
|
|
910
|
+
onChange: onOpenChange
|
|
911
|
+
});
|
|
912
|
+
const [duration, setDuration] = (0, import_react_use_controllable_state.useControllableState)({
|
|
913
|
+
prop: durationProp,
|
|
914
|
+
defaultProp: void 0
|
|
915
|
+
});
|
|
916
|
+
const [hasAutoClosed, setHasAutoClosed] = (0, import_react5.useState)(false);
|
|
917
|
+
const [startTime, setStartTime] = (0, import_react5.useState)(null);
|
|
918
|
+
(0, import_react5.useEffect)(() => {
|
|
919
|
+
if (isStreaming) {
|
|
920
|
+
if (startTime === null) {
|
|
921
|
+
setStartTime(Date.now());
|
|
922
|
+
}
|
|
923
|
+
} else if (startTime !== null) {
|
|
924
|
+
setDuration(Math.ceil((Date.now() - startTime) / MS_IN_S));
|
|
925
|
+
setStartTime(null);
|
|
926
|
+
}
|
|
927
|
+
}, [isStreaming, startTime, setDuration]);
|
|
928
|
+
(0, import_react5.useEffect)(() => {
|
|
929
|
+
if (defaultOpen && !isStreaming && isOpen && !hasAutoClosed && durationProp !== void 0) {
|
|
930
|
+
const timer = setTimeout(() => {
|
|
931
|
+
setIsOpen(false);
|
|
932
|
+
setHasAutoClosed(true);
|
|
933
|
+
}, AUTO_CLOSE_DELAY);
|
|
934
|
+
return () => clearTimeout(timer);
|
|
935
|
+
}
|
|
936
|
+
}, [isStreaming, isOpen, defaultOpen, setIsOpen, hasAutoClosed, durationProp]);
|
|
937
|
+
const handleOpenChange = (newOpen) => {
|
|
938
|
+
setIsOpen(newOpen);
|
|
939
|
+
};
|
|
940
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
941
|
+
ReasoningContext.Provider,
|
|
942
|
+
{
|
|
943
|
+
value: { isStreaming, isOpen, setIsOpen, duration },
|
|
944
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
945
|
+
Collapsible,
|
|
946
|
+
{
|
|
947
|
+
className: cn("not-prose", className),
|
|
948
|
+
onOpenChange: handleOpenChange,
|
|
949
|
+
open: isOpen,
|
|
950
|
+
...props,
|
|
951
|
+
children
|
|
952
|
+
}
|
|
953
|
+
)
|
|
954
|
+
}
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
);
|
|
958
|
+
var getThinkingMessage = (isStreaming, duration) => {
|
|
959
|
+
if (isStreaming) {
|
|
960
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_jsx_runtime13.Fragment, { children: "Thinking..." });
|
|
961
|
+
}
|
|
962
|
+
if (duration === void 0) {
|
|
963
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_jsx_runtime13.Fragment, { children: "Thought process" });
|
|
964
|
+
}
|
|
965
|
+
if (duration === 0) {
|
|
966
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_jsx_runtime13.Fragment, { children: "Thought process" });
|
|
967
|
+
}
|
|
968
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
|
|
969
|
+
"Thought for ",
|
|
970
|
+
duration,
|
|
971
|
+
" seconds"
|
|
972
|
+
] });
|
|
973
|
+
};
|
|
974
|
+
var ReasoningTrigger = (0, import_react5.memo)(
|
|
975
|
+
({ className, children, ...props }) => {
|
|
976
|
+
const { isStreaming, isOpen, duration } = useReasoning();
|
|
977
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
978
|
+
CollapsibleTrigger2,
|
|
979
|
+
{
|
|
980
|
+
className: cn(
|
|
981
|
+
"flex w-full items-center gap-2 text-muted-foreground text-sm transition-colors hover:text-foreground",
|
|
982
|
+
className
|
|
983
|
+
),
|
|
984
|
+
...props,
|
|
985
|
+
children: children ?? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
|
|
986
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.BrainIcon, { className: "size-4 flex-shrink-0" }),
|
|
987
|
+
getThinkingMessage(isStreaming, duration),
|
|
988
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
989
|
+
import_lucide_react7.ChevronDownIcon,
|
|
990
|
+
{
|
|
991
|
+
className: cn(
|
|
992
|
+
"size-4 flex-shrink-0 transition-transform ml-1",
|
|
993
|
+
isOpen ? "rotate-180" : "rotate-0"
|
|
994
|
+
)
|
|
995
|
+
}
|
|
996
|
+
)
|
|
997
|
+
] })
|
|
998
|
+
}
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
);
|
|
1002
|
+
var ReasoningContent = (0, import_react5.memo)(
|
|
1003
|
+
({ className, children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1004
|
+
CollapsibleContent2,
|
|
1005
|
+
{
|
|
1006
|
+
className: cn(
|
|
1007
|
+
"mt-4 text-sm",
|
|
1008
|
+
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-muted-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
|
|
1009
|
+
className
|
|
1010
|
+
),
|
|
1011
|
+
...props,
|
|
1012
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Response, { className: "grid gap-2", children })
|
|
1013
|
+
}
|
|
1014
|
+
)
|
|
1015
|
+
);
|
|
1016
|
+
Reasoning.displayName = "Reasoning";
|
|
1017
|
+
ReasoningTrigger.displayName = "ReasoningTrigger";
|
|
1018
|
+
ReasoningContent.displayName = "ReasoningContent";
|
|
1019
|
+
|
|
1020
|
+
// src/components/loader.tsx
|
|
1021
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
1022
|
+
var LoaderIcon = ({ size = 16 }) => /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
|
|
1023
|
+
"svg",
|
|
1024
|
+
{
|
|
1025
|
+
height: size,
|
|
1026
|
+
strokeLinejoin: "round",
|
|
1027
|
+
style: { color: "currentcolor" },
|
|
1028
|
+
viewBox: "0 0 16 16",
|
|
1029
|
+
width: size,
|
|
1030
|
+
children: [
|
|
1031
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("title", { children: "Loader" }),
|
|
1032
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("g", { clipPath: "url(#clip0_2393_1490)", children: [
|
|
1033
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { d: "M8 0V4", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
1034
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1035
|
+
"path",
|
|
1036
|
+
{
|
|
1037
|
+
d: "M8 16V12",
|
|
1038
|
+
opacity: "0.5",
|
|
1039
|
+
stroke: "currentColor",
|
|
1040
|
+
strokeWidth: "1.5"
|
|
1041
|
+
}
|
|
1042
|
+
),
|
|
1043
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1044
|
+
"path",
|
|
1045
|
+
{
|
|
1046
|
+
d: "M3.29773 1.52783L5.64887 4.7639",
|
|
1047
|
+
opacity: "0.9",
|
|
1048
|
+
stroke: "currentColor",
|
|
1049
|
+
strokeWidth: "1.5"
|
|
1050
|
+
}
|
|
1051
|
+
),
|
|
1052
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1053
|
+
"path",
|
|
1054
|
+
{
|
|
1055
|
+
d: "M12.7023 1.52783L10.3511 4.7639",
|
|
1056
|
+
opacity: "0.1",
|
|
1057
|
+
stroke: "currentColor",
|
|
1058
|
+
strokeWidth: "1.5"
|
|
1059
|
+
}
|
|
1060
|
+
),
|
|
1061
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1062
|
+
"path",
|
|
1063
|
+
{
|
|
1064
|
+
d: "M12.7023 14.472L10.3511 11.236",
|
|
1065
|
+
opacity: "0.4",
|
|
1066
|
+
stroke: "currentColor",
|
|
1067
|
+
strokeWidth: "1.5"
|
|
1068
|
+
}
|
|
1069
|
+
),
|
|
1070
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1071
|
+
"path",
|
|
1072
|
+
{
|
|
1073
|
+
d: "M3.29773 14.472L5.64887 11.236",
|
|
1074
|
+
opacity: "0.6",
|
|
1075
|
+
stroke: "currentColor",
|
|
1076
|
+
strokeWidth: "1.5"
|
|
1077
|
+
}
|
|
1078
|
+
),
|
|
1079
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1080
|
+
"path",
|
|
1081
|
+
{
|
|
1082
|
+
d: "M15.6085 5.52783L11.8043 6.7639",
|
|
1083
|
+
opacity: "0.2",
|
|
1084
|
+
stroke: "currentColor",
|
|
1085
|
+
strokeWidth: "1.5"
|
|
1086
|
+
}
|
|
1087
|
+
),
|
|
1088
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1089
|
+
"path",
|
|
1090
|
+
{
|
|
1091
|
+
d: "M0.391602 10.472L4.19583 9.23598",
|
|
1092
|
+
opacity: "0.7",
|
|
1093
|
+
stroke: "currentColor",
|
|
1094
|
+
strokeWidth: "1.5"
|
|
1095
|
+
}
|
|
1096
|
+
),
|
|
1097
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1098
|
+
"path",
|
|
1099
|
+
{
|
|
1100
|
+
d: "M15.6085 10.4722L11.8043 9.2361",
|
|
1101
|
+
opacity: "0.3",
|
|
1102
|
+
stroke: "currentColor",
|
|
1103
|
+
strokeWidth: "1.5"
|
|
1104
|
+
}
|
|
1105
|
+
),
|
|
1106
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1107
|
+
"path",
|
|
1108
|
+
{
|
|
1109
|
+
d: "M0.391602 5.52783L4.19583 6.7639",
|
|
1110
|
+
opacity: "0.8",
|
|
1111
|
+
stroke: "currentColor",
|
|
1112
|
+
strokeWidth: "1.5"
|
|
1113
|
+
}
|
|
1114
|
+
)
|
|
1115
|
+
] }),
|
|
1116
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("clipPath", { id: "clip0_2393_1490", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("rect", { fill: "white", height: "16", width: "16" }) }) })
|
|
1117
|
+
]
|
|
1118
|
+
}
|
|
1119
|
+
);
|
|
1120
|
+
var Loader = ({ className, size = 16, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1121
|
+
"div",
|
|
1122
|
+
{
|
|
1123
|
+
className: cn(
|
|
1124
|
+
"inline-flex animate-spin items-center justify-center",
|
|
1125
|
+
className
|
|
1126
|
+
),
|
|
1127
|
+
...props,
|
|
1128
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(LoaderIcon, { size })
|
|
1129
|
+
}
|
|
1130
|
+
);
|
|
1131
|
+
|
|
1132
|
+
// src/ui/badge.tsx
|
|
1133
|
+
var import_react_slot2 = require("@radix-ui/react-slot");
|
|
1134
|
+
var import_class_variance_authority3 = require("class-variance-authority");
|
|
1135
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
1136
|
+
var badgeVariants = (0, import_class_variance_authority3.cva)(
|
|
1137
|
+
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
|
1138
|
+
{
|
|
1139
|
+
variants: {
|
|
1140
|
+
variant: {
|
|
1141
|
+
default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
|
1142
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
|
1143
|
+
destructive: "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
1144
|
+
outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground"
|
|
1145
|
+
}
|
|
1146
|
+
},
|
|
1147
|
+
defaultVariants: {
|
|
1148
|
+
variant: "default"
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
);
|
|
1152
|
+
function Badge({
|
|
1153
|
+
className,
|
|
1154
|
+
variant,
|
|
1155
|
+
asChild = false,
|
|
1156
|
+
...props
|
|
1157
|
+
}) {
|
|
1158
|
+
const Comp = asChild ? import_react_slot2.Slot : "span";
|
|
1159
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
1160
|
+
Comp,
|
|
1161
|
+
{
|
|
1162
|
+
"data-slot": "badge",
|
|
1163
|
+
className: cn(badgeVariants({ variant }), className),
|
|
1164
|
+
...props
|
|
1165
|
+
}
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// src/components/tool.tsx
|
|
1170
|
+
var import_lucide_react9 = require("lucide-react");
|
|
1171
|
+
var import_react7 = require("react");
|
|
1172
|
+
|
|
1173
|
+
// src/components/code-block.tsx
|
|
1174
|
+
var import_lucide_react8 = require("lucide-react");
|
|
1175
|
+
var import_react6 = require("react");
|
|
1176
|
+
var import_react_syntax_highlighter = require("react-syntax-highlighter");
|
|
1177
|
+
var import_prism = require("react-syntax-highlighter/dist/esm/styles/prism");
|
|
1178
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1179
|
+
var CodeBlockContext = (0, import_react6.createContext)({
|
|
1180
|
+
code: ""
|
|
1181
|
+
});
|
|
1182
|
+
var CodeBlock = ({
|
|
1183
|
+
code,
|
|
1184
|
+
language,
|
|
1185
|
+
showLineNumbers = false,
|
|
1186
|
+
className,
|
|
1187
|
+
children,
|
|
1188
|
+
...props
|
|
1189
|
+
}) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(CodeBlockContext.Provider, { value: { code }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1190
|
+
"div",
|
|
1191
|
+
{
|
|
1192
|
+
className: cn(
|
|
1193
|
+
"relative w-full overflow-hidden rounded-md border bg-background text-foreground",
|
|
1194
|
+
className
|
|
1195
|
+
),
|
|
1196
|
+
...props,
|
|
1197
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "relative max-h-96 overflow-y-auto", children: [
|
|
1198
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1199
|
+
import_react_syntax_highlighter.Prism,
|
|
1200
|
+
{
|
|
1201
|
+
className: "overflow-hidden dark:hidden",
|
|
1202
|
+
codeTagProps: {
|
|
1203
|
+
className: "font-mono text-sm"
|
|
1204
|
+
},
|
|
1205
|
+
customStyle: {
|
|
1206
|
+
margin: 0,
|
|
1207
|
+
padding: "1rem",
|
|
1208
|
+
fontSize: "0.875rem",
|
|
1209
|
+
background: "hsl(var(--background))",
|
|
1210
|
+
color: "hsl(var(--foreground))"
|
|
1211
|
+
},
|
|
1212
|
+
language,
|
|
1213
|
+
lineNumberStyle: {
|
|
1214
|
+
color: "hsl(var(--muted-foreground))",
|
|
1215
|
+
paddingRight: "1rem",
|
|
1216
|
+
minWidth: "2.5rem"
|
|
1217
|
+
},
|
|
1218
|
+
showLineNumbers,
|
|
1219
|
+
style: import_prism.oneLight,
|
|
1220
|
+
children: code
|
|
1221
|
+
}
|
|
1222
|
+
),
|
|
1223
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1224
|
+
import_react_syntax_highlighter.Prism,
|
|
1225
|
+
{
|
|
1226
|
+
className: "hidden overflow-hidden dark:block",
|
|
1227
|
+
codeTagProps: {
|
|
1228
|
+
className: "font-mono text-sm"
|
|
1229
|
+
},
|
|
1230
|
+
customStyle: {
|
|
1231
|
+
margin: 0,
|
|
1232
|
+
padding: "1rem",
|
|
1233
|
+
fontSize: "0.875rem",
|
|
1234
|
+
background: "hsl(var(--background))",
|
|
1235
|
+
color: "hsl(var(--foreground))"
|
|
1236
|
+
},
|
|
1237
|
+
language,
|
|
1238
|
+
lineNumberStyle: {
|
|
1239
|
+
color: "hsl(var(--muted-foreground))",
|
|
1240
|
+
paddingRight: "1rem",
|
|
1241
|
+
minWidth: "2.5rem"
|
|
1242
|
+
},
|
|
1243
|
+
showLineNumbers,
|
|
1244
|
+
style: import_prism.oneDark,
|
|
1245
|
+
children: code
|
|
1246
|
+
}
|
|
1247
|
+
),
|
|
1248
|
+
children && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "absolute top-2 right-2 flex items-center gap-2", children })
|
|
1249
|
+
] })
|
|
1250
|
+
}
|
|
1251
|
+
) });
|
|
1252
|
+
|
|
1253
|
+
// src/components/tool.tsx
|
|
1254
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
1255
|
+
var Tool = ({ className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
1256
|
+
Collapsible,
|
|
1257
|
+
{
|
|
1258
|
+
className: cn("not-prose w-full rounded-md border", className),
|
|
1259
|
+
...props
|
|
1260
|
+
}
|
|
1261
|
+
);
|
|
1262
|
+
var getStatusBadge = (status) => {
|
|
1263
|
+
const labels = {
|
|
1264
|
+
"input-streaming": "Pending",
|
|
1265
|
+
"input-available": "Running",
|
|
1266
|
+
"output-available": "Completed",
|
|
1267
|
+
"output-error": "Error"
|
|
1268
|
+
};
|
|
1269
|
+
const icons = {
|
|
1270
|
+
"input-streaming": /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react9.CircleIcon, { className: "size-4" }),
|
|
1271
|
+
"input-available": /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react9.ClockIcon, { className: "size-4 animate-pulse" }),
|
|
1272
|
+
"output-available": /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react9.CheckCircleIcon, { className: "size-4 text-green-600" }),
|
|
1273
|
+
"output-error": /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react9.XCircleIcon, { className: "size-4 text-red-600" })
|
|
1274
|
+
};
|
|
1275
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(Badge, { className: "gap-1.5 rounded-full text-xs", variant: "secondary", children: [
|
|
1276
|
+
icons[status],
|
|
1277
|
+
labels[status]
|
|
1278
|
+
] });
|
|
1279
|
+
};
|
|
1280
|
+
var ToolHeader = ({
|
|
1281
|
+
className,
|
|
1282
|
+
title,
|
|
1283
|
+
type,
|
|
1284
|
+
state,
|
|
1285
|
+
...props
|
|
1286
|
+
}) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
1287
|
+
CollapsibleTrigger2,
|
|
1288
|
+
{
|
|
1289
|
+
className: cn(
|
|
1290
|
+
"flex w-full items-center justify-between gap-4 p-2",
|
|
1291
|
+
className
|
|
1292
|
+
),
|
|
1293
|
+
...props,
|
|
1294
|
+
children: [
|
|
1295
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
1296
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react9.WrenchIcon, { className: "size-4 text-muted-foreground" }),
|
|
1297
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "font-medium text-sm", children: title ?? type.split("-").slice(1).join("-") }),
|
|
1298
|
+
getStatusBadge(state)
|
|
1299
|
+
] }),
|
|
1300
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react9.ChevronDownIcon, { className: "size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" })
|
|
1301
|
+
]
|
|
1302
|
+
}
|
|
1303
|
+
);
|
|
1304
|
+
var ToolContent = ({ className, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
1305
|
+
CollapsibleContent2,
|
|
1306
|
+
{
|
|
1307
|
+
className: cn(
|
|
1308
|
+
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
|
|
1309
|
+
className
|
|
1310
|
+
),
|
|
1311
|
+
...props
|
|
1312
|
+
}
|
|
1313
|
+
);
|
|
1314
|
+
var ToolInput = ({ className, input, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: cn("space-y-2 overflow-hidden p-2", className), ...props, children: [
|
|
1315
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("h4", { className: "font-medium text-muted-foreground text-xs uppercase tracking-wide", children: "Parameters" }),
|
|
1316
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "rounded-md bg-muted/50", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(CodeBlock, { code: JSON.stringify(input, null, 2), language: "json" }) })
|
|
1317
|
+
] });
|
|
1318
|
+
var ToolOutput = ({
|
|
1319
|
+
className,
|
|
1320
|
+
output,
|
|
1321
|
+
errorText,
|
|
1322
|
+
...props
|
|
1323
|
+
}) => {
|
|
1324
|
+
if (!(output || errorText)) {
|
|
1325
|
+
return null;
|
|
1326
|
+
}
|
|
1327
|
+
let Output = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { children: output });
|
|
1328
|
+
if (typeof output === "object" && !(0, import_react7.isValidElement)(output)) {
|
|
1329
|
+
Output = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(CodeBlock, { code: JSON.stringify(output, null, 2), language: "json" });
|
|
1330
|
+
} else if (typeof output === "string") {
|
|
1331
|
+
Output = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(CodeBlock, { code: output, language: "json" });
|
|
1332
|
+
}
|
|
1333
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: cn("space-y-2 p-2", className), ...props, children: [
|
|
1334
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("h4", { className: "font-medium text-muted-foreground text-xs uppercase tracking-wide", children: errorText ? "Error" : "Result" }),
|
|
1335
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
1336
|
+
"div",
|
|
1337
|
+
{
|
|
1338
|
+
className: cn(
|
|
1339
|
+
"overflow-x-auto rounded-md text-xs [&_table]:w-full",
|
|
1340
|
+
errorText ? "bg-destructive/10 text-destructive" : "bg-muted/50 text-foreground"
|
|
1341
|
+
),
|
|
1342
|
+
children: [
|
|
1343
|
+
errorText && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { children: errorText }),
|
|
1344
|
+
Output
|
|
1345
|
+
]
|
|
1346
|
+
}
|
|
1347
|
+
)
|
|
1348
|
+
] });
|
|
1349
|
+
};
|
|
1350
|
+
|
|
1351
|
+
// src/components/interface.tsx
|
|
1352
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
1353
|
+
function ChatInterface({ id, initialMessages, config, onClose } = {}) {
|
|
1354
|
+
const themeMode = config?.theme?.mode || "light";
|
|
1355
|
+
const [input, setInput] = (0, import_react8.useState)("");
|
|
1356
|
+
const [showHistory, setShowHistory] = (0, import_react8.useState)(false);
|
|
1357
|
+
const [conversations, setConversations] = (0, import_react8.useState)([]);
|
|
1358
|
+
const [loadingHistory, setLoadingHistory] = (0, import_react8.useState)(false);
|
|
1359
|
+
const [historyLoaded, setHistoryLoaded] = (0, import_react8.useState)(false);
|
|
1360
|
+
const [searchQuery, setSearchQuery] = (0, import_react8.useState)("");
|
|
1361
|
+
const [uploadError, setUploadError] = (0, import_react8.useState)(null);
|
|
1362
|
+
(0, import_react8.useEffect)(() => {
|
|
1363
|
+
if (uploadError) {
|
|
1364
|
+
const timeoutId = setTimeout(() => setUploadError(null), 5e3);
|
|
1365
|
+
return () => clearTimeout(timeoutId);
|
|
1366
|
+
}
|
|
1367
|
+
}, [uploadError]);
|
|
1368
|
+
const [tabs, setTabs] = (0, import_react8.useState)([]);
|
|
1369
|
+
const [activeTabId, setActiveTabId] = (0, import_react8.useState)("");
|
|
1370
|
+
const [initialTabCreated, setInitialTabCreated] = (0, import_react8.useState)(false);
|
|
1371
|
+
const [isInitializing, setIsInitializing] = (0, import_react8.useState)(true);
|
|
1372
|
+
const [componentWidth, setComponentWidth] = (0, import_react8.useState)(768);
|
|
1373
|
+
const [isResizing, setIsResizing] = (0, import_react8.useState)(false);
|
|
1374
|
+
const lastSyncedTabId = (0, import_react8.useRef)("");
|
|
1375
|
+
const hasInitialized = (0, import_react8.useRef)(false);
|
|
1376
|
+
const { messages, sendMessage, status, setMessages } = (0, import_react10.useChat)({
|
|
1377
|
+
id: activeTabId || "temp-id",
|
|
1378
|
+
transport: new import_ai.DefaultChatTransport({
|
|
1379
|
+
api: "/api/chat",
|
|
1380
|
+
headers: {
|
|
1381
|
+
"X-User-Id": config?.userId || ""
|
|
1382
|
+
}
|
|
1383
|
+
}),
|
|
1384
|
+
// Throttle UI updates to 200ms to prevent hanging during streaming
|
|
1385
|
+
experimental_throttle: 200
|
|
1386
|
+
});
|
|
1387
|
+
const handleSubmit = async (message) => {
|
|
1388
|
+
const hasText = Boolean(message.text);
|
|
1389
|
+
const hasAttachments = Boolean(message.files?.length);
|
|
1390
|
+
if (!(hasText || hasAttachments)) {
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1393
|
+
let uploadedFiles = [];
|
|
1394
|
+
if (message.files && message.files.length > 0) {
|
|
1395
|
+
try {
|
|
1396
|
+
const uploadPromises = message.files.map(async (file) => {
|
|
1397
|
+
try {
|
|
1398
|
+
const response = await fetch(file.url);
|
|
1399
|
+
const blob = await response.blob();
|
|
1400
|
+
const fileObj = new File([blob], file.filename || "unknown", { type: file.mediaType });
|
|
1401
|
+
const formData = new FormData();
|
|
1402
|
+
formData.append("file", fileObj);
|
|
1403
|
+
formData.append("conversationId", activeTabId || "default");
|
|
1404
|
+
formData.append("userId", config?.userId || "demo-user");
|
|
1405
|
+
const uploadResponse = await fetch("/api/chat/upload", {
|
|
1406
|
+
method: "POST",
|
|
1407
|
+
body: formData
|
|
1408
|
+
});
|
|
1409
|
+
if (!uploadResponse.ok) {
|
|
1410
|
+
const errorText = await uploadResponse.text();
|
|
1411
|
+
console.error(`Upload failed for ${file.filename}:`, errorText);
|
|
1412
|
+
return null;
|
|
1413
|
+
}
|
|
1414
|
+
const uploadResult = await uploadResponse.json();
|
|
1415
|
+
return {
|
|
1416
|
+
id: file.id || "unknown",
|
|
1417
|
+
type: "file",
|
|
1418
|
+
url: uploadResult.url,
|
|
1419
|
+
filename: uploadResult.filename,
|
|
1420
|
+
mediaType: uploadResult.mediaType,
|
|
1421
|
+
size: uploadResult.size
|
|
1422
|
+
};
|
|
1423
|
+
} catch (error) {
|
|
1424
|
+
console.error(`Error uploading ${file.filename}:`, error);
|
|
1425
|
+
return null;
|
|
1426
|
+
}
|
|
1427
|
+
});
|
|
1428
|
+
const results = await Promise.all(uploadPromises);
|
|
1429
|
+
uploadedFiles = results.filter((result) => result !== null);
|
|
1430
|
+
if (uploadedFiles.length === 0) {
|
|
1431
|
+
const errorMsg = "All file uploads failed. Please try again.";
|
|
1432
|
+
setUploadError(errorMsg);
|
|
1433
|
+
console.error(errorMsg);
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
if (uploadedFiles.length < message.files.length) {
|
|
1437
|
+
const warnMsg = `Warning: Only ${uploadedFiles.length} of ${message.files.length} files uploaded successfully.`;
|
|
1438
|
+
setUploadError(warnMsg);
|
|
1439
|
+
console.warn(warnMsg);
|
|
1440
|
+
}
|
|
1441
|
+
} catch (error) {
|
|
1442
|
+
const errorMsg = "Error uploading files. Please try again.";
|
|
1443
|
+
setUploadError(errorMsg);
|
|
1444
|
+
console.error("Error in file upload process:", error);
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
sendMessage({
|
|
1449
|
+
text: message.text || "Sent with attachments",
|
|
1450
|
+
files: uploadedFiles
|
|
1451
|
+
});
|
|
1452
|
+
const activeTab = tabs.find((tab) => tab.id === activeTabId);
|
|
1453
|
+
if (activeTab && activeTab.title === "New Chat" && message.text) {
|
|
1454
|
+
const newTitle = message.text.slice(0, 100);
|
|
1455
|
+
setTabs(
|
|
1456
|
+
(prevTabs) => prevTabs.map(
|
|
1457
|
+
(tab) => tab.id === activeTabId ? { ...tab, title: newTitle } : tab
|
|
1458
|
+
)
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
setInput("");
|
|
1462
|
+
};
|
|
1463
|
+
const AttachButton = () => {
|
|
1464
|
+
const attachments = usePromptInputAttachments();
|
|
1465
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
1466
|
+
PromptInputButton,
|
|
1467
|
+
{
|
|
1468
|
+
variant: "ghost",
|
|
1469
|
+
onClick: () => attachments.openFileDialog(),
|
|
1470
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react10.PlusIcon, { className: "size-4" })
|
|
1471
|
+
}
|
|
1472
|
+
);
|
|
1473
|
+
};
|
|
1474
|
+
const loadConversation = async (conversationId) => {
|
|
1475
|
+
if (!config?.userId) {
|
|
1476
|
+
console.log("Cannot load conversation - no userId");
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
try {
|
|
1480
|
+
const response = await fetch(`/api/chat/history/${conversationId}?userId=${config.userId}`);
|
|
1481
|
+
if (response.ok) {
|
|
1482
|
+
const data = await response.json();
|
|
1483
|
+
const loadedMessages = data.messages || [];
|
|
1484
|
+
setTimeout(() => {
|
|
1485
|
+
setMessages(loadedMessages);
|
|
1486
|
+
}, 0);
|
|
1487
|
+
} else if (response.status === 404) {
|
|
1488
|
+
console.log("Conversation not found in database yet (new chat)");
|
|
1489
|
+
setMessages([]);
|
|
1490
|
+
} else {
|
|
1491
|
+
console.error("Error loading messages:", response.status, response.statusText);
|
|
1492
|
+
}
|
|
1493
|
+
} catch (error) {
|
|
1494
|
+
console.error("Error loading conversation:", error);
|
|
1495
|
+
}
|
|
1496
|
+
};
|
|
1497
|
+
const generateUniqueTabId = () => {
|
|
1498
|
+
let newTabId;
|
|
1499
|
+
let attempts = 0;
|
|
1500
|
+
do {
|
|
1501
|
+
newTabId = `chat-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
1502
|
+
attempts++;
|
|
1503
|
+
} while (tabs.find((tab) => tab.id === newTabId) && attempts < 10);
|
|
1504
|
+
if (attempts >= 10) {
|
|
1505
|
+
throw new Error("Unable to generate unique tab ID");
|
|
1506
|
+
}
|
|
1507
|
+
return newTabId;
|
|
1508
|
+
};
|
|
1509
|
+
const createNewTab = (0, import_react8.useCallback)(() => {
|
|
1510
|
+
if (!initialTabCreated) {
|
|
1511
|
+
console.warn("Cannot create new tab while initializing");
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1514
|
+
const newTabId = generateUniqueTabId();
|
|
1515
|
+
setTabs((prevTabs) => {
|
|
1516
|
+
const existingTab = prevTabs.find((tab) => tab.id === newTabId);
|
|
1517
|
+
if (existingTab) {
|
|
1518
|
+
console.warn("Tab with ID already exists:", newTabId);
|
|
1519
|
+
return prevTabs;
|
|
1520
|
+
}
|
|
1521
|
+
const newTab = {
|
|
1522
|
+
id: newTabId,
|
|
1523
|
+
title: "New Chat",
|
|
1524
|
+
isActive: true
|
|
1525
|
+
};
|
|
1526
|
+
const updatedTabs = prevTabs.map((tab) => ({
|
|
1527
|
+
...tab,
|
|
1528
|
+
isActive: false
|
|
1529
|
+
}));
|
|
1530
|
+
return [...updatedTabs, newTab];
|
|
1531
|
+
});
|
|
1532
|
+
setActiveTabId(newTabId);
|
|
1533
|
+
setMessages([]);
|
|
1534
|
+
setInput("");
|
|
1535
|
+
}, [initialTabCreated]);
|
|
1536
|
+
const startNewConversation = (0, import_react8.useCallback)(() => {
|
|
1537
|
+
createNewTab();
|
|
1538
|
+
}, [createNewTab]);
|
|
1539
|
+
(0, import_react8.useEffect)(() => {
|
|
1540
|
+
return () => {
|
|
1541
|
+
};
|
|
1542
|
+
}, []);
|
|
1543
|
+
const switchToTab = async (tabId) => {
|
|
1544
|
+
const targetTab = tabs.find((tab) => tab.id === tabId);
|
|
1545
|
+
if (!targetTab) return;
|
|
1546
|
+
setTabs(
|
|
1547
|
+
(prevTabs) => prevTabs.map((tab) => ({
|
|
1548
|
+
...tab,
|
|
1549
|
+
isActive: tab.id === tabId
|
|
1550
|
+
}))
|
|
1551
|
+
);
|
|
1552
|
+
setActiveTabId(tabId);
|
|
1553
|
+
await loadConversation(tabId);
|
|
1554
|
+
};
|
|
1555
|
+
const closeTab = (tabId) => {
|
|
1556
|
+
if (tabs.length <= 1) return;
|
|
1557
|
+
const filteredTabs = tabs.filter((tab) => tab.id !== tabId);
|
|
1558
|
+
if (tabId === activeTabId && filteredTabs.length > 0) {
|
|
1559
|
+
const newActiveTab = filteredTabs[0];
|
|
1560
|
+
setTabs(filteredTabs.map((tab) => ({
|
|
1561
|
+
...tab,
|
|
1562
|
+
isActive: tab.id === newActiveTab.id
|
|
1563
|
+
})));
|
|
1564
|
+
switchToTab(newActiveTab.id);
|
|
1565
|
+
} else {
|
|
1566
|
+
setTabs(filteredTabs);
|
|
1567
|
+
}
|
|
1568
|
+
if (filteredTabs.length > 0) {
|
|
1569
|
+
localStorage.setItem("chat-tabs", JSON.stringify(filteredTabs));
|
|
1570
|
+
if (tabId === activeTabId) {
|
|
1571
|
+
const newActiveTab = filteredTabs[0];
|
|
1572
|
+
localStorage.setItem("active-tab-id", newActiveTab.id);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
};
|
|
1576
|
+
const fetchConversations = async () => {
|
|
1577
|
+
if (historyLoaded) return;
|
|
1578
|
+
if (!config?.userId) {
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
setLoadingHistory(true);
|
|
1582
|
+
try {
|
|
1583
|
+
const response = await fetch(`/api/chat/history?userId=${config.userId}`);
|
|
1584
|
+
if (response.ok) {
|
|
1585
|
+
const data = await response.json();
|
|
1586
|
+
setConversations(data.conversations || []);
|
|
1587
|
+
setHistoryLoaded(true);
|
|
1588
|
+
} else {
|
|
1589
|
+
console.error("[ChatInterface] Failed to fetch chat history, status:", response.status);
|
|
1590
|
+
const errorText = await response.text();
|
|
1591
|
+
console.error("[ChatInterface] Error response:", errorText);
|
|
1592
|
+
}
|
|
1593
|
+
} catch (error) {
|
|
1594
|
+
console.error("[ChatInterface] Error fetching chat history:", error);
|
|
1595
|
+
} finally {
|
|
1596
|
+
setLoadingHistory(false);
|
|
1597
|
+
}
|
|
1598
|
+
};
|
|
1599
|
+
(0, import_react8.useEffect)(() => {
|
|
1600
|
+
if (showHistory && !historyLoaded && config?.userId) {
|
|
1601
|
+
fetchConversations();
|
|
1602
|
+
}
|
|
1603
|
+
}, [showHistory, historyLoaded, config?.userId]);
|
|
1604
|
+
(0, import_react8.useEffect)(() => {
|
|
1605
|
+
if (!historyLoaded && config?.userId) {
|
|
1606
|
+
fetchConversations();
|
|
1607
|
+
}
|
|
1608
|
+
}, [historyLoaded, config?.userId]);
|
|
1609
|
+
(0, import_react8.useEffect)(() => {
|
|
1610
|
+
if (tabs.length > 0) {
|
|
1611
|
+
const timeoutId = setTimeout(() => {
|
|
1612
|
+
localStorage.setItem("chat-tabs", JSON.stringify(tabs));
|
|
1613
|
+
localStorage.setItem("active-tab-id", activeTabId);
|
|
1614
|
+
}, 500);
|
|
1615
|
+
return () => clearTimeout(timeoutId);
|
|
1616
|
+
}
|
|
1617
|
+
}, [tabs, activeTabId]);
|
|
1618
|
+
(0, import_react8.useEffect)(() => {
|
|
1619
|
+
if (hasInitialized.current) return;
|
|
1620
|
+
const loadInitialTabs = () => {
|
|
1621
|
+
try {
|
|
1622
|
+
const savedTabs = localStorage.getItem("chat-tabs");
|
|
1623
|
+
const savedActiveTabId = localStorage.getItem("active-tab-id");
|
|
1624
|
+
if (savedTabs && savedTabs !== "[]") {
|
|
1625
|
+
const parsedTabs = JSON.parse(savedTabs);
|
|
1626
|
+
setTabs(parsedTabs);
|
|
1627
|
+
const activeId = savedActiveTabId || parsedTabs[0]?.id;
|
|
1628
|
+
setActiveTabId(activeId);
|
|
1629
|
+
setInitialTabCreated(true);
|
|
1630
|
+
} else if (!initialTabCreated && tabs.length === 0) {
|
|
1631
|
+
const initialTabId = `chat-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
1632
|
+
const currentTab = {
|
|
1633
|
+
id: initialTabId,
|
|
1634
|
+
title: "New Chat",
|
|
1635
|
+
isActive: true
|
|
1636
|
+
};
|
|
1637
|
+
setTabs([currentTab]);
|
|
1638
|
+
setActiveTabId(initialTabId);
|
|
1639
|
+
setInitialTabCreated(true);
|
|
1640
|
+
}
|
|
1641
|
+
} finally {
|
|
1642
|
+
setIsInitializing(false);
|
|
1643
|
+
}
|
|
1644
|
+
};
|
|
1645
|
+
loadInitialTabs();
|
|
1646
|
+
hasInitialized.current = true;
|
|
1647
|
+
}, []);
|
|
1648
|
+
const hasLoadedInitialMessages = (0, import_react8.useRef)(false);
|
|
1649
|
+
(0, import_react8.useEffect)(() => {
|
|
1650
|
+
if (hasLoadedInitialMessages.current) return;
|
|
1651
|
+
if (!config?.userId) return;
|
|
1652
|
+
if (!activeTabId) return;
|
|
1653
|
+
if (isInitializing) return;
|
|
1654
|
+
loadConversation(activeTabId);
|
|
1655
|
+
hasLoadedInitialMessages.current = true;
|
|
1656
|
+
}, [config?.userId, activeTabId, isInitializing]);
|
|
1657
|
+
(0, import_react8.useEffect)(() => {
|
|
1658
|
+
if (isInitializing) return;
|
|
1659
|
+
if (activeTabId && tabs.length > 0 && activeTabId !== lastSyncedTabId.current) {
|
|
1660
|
+
lastSyncedTabId.current = activeTabId;
|
|
1661
|
+
setInput("");
|
|
1662
|
+
}
|
|
1663
|
+
}, [activeTabId, isInitializing, tabs.length]);
|
|
1664
|
+
const groupedConversations = (0, import_react8.useMemo)(() => {
|
|
1665
|
+
const filtered = conversations.filter(
|
|
1666
|
+
(conv) => searchQuery === "" || conv.title.toLowerCase().includes(searchQuery.toLowerCase())
|
|
1667
|
+
);
|
|
1668
|
+
const groups = {};
|
|
1669
|
+
const now = /* @__PURE__ */ new Date();
|
|
1670
|
+
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
1671
|
+
const yesterday = new Date(today);
|
|
1672
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
1673
|
+
filtered.forEach((conv) => {
|
|
1674
|
+
const convDate = new Date(conv.updated_at);
|
|
1675
|
+
const diffTime = now.getTime() - convDate.getTime();
|
|
1676
|
+
const diffDays = Math.floor(diffTime / (1e3 * 60 * 60 * 24));
|
|
1677
|
+
const diffWeeks = Math.floor(diffDays / 7);
|
|
1678
|
+
const diffMonths = Math.floor(diffDays / 30);
|
|
1679
|
+
let groupKey;
|
|
1680
|
+
if (convDate >= today) {
|
|
1681
|
+
groupKey = "Today";
|
|
1682
|
+
} else if (convDate >= yesterday) {
|
|
1683
|
+
groupKey = "Yesterday";
|
|
1684
|
+
} else if (diffDays <= 7) {
|
|
1685
|
+
groupKey = `${diffDays}d ago`;
|
|
1686
|
+
} else if (diffWeeks <= 4) {
|
|
1687
|
+
groupKey = `${diffWeeks}w ago`;
|
|
1688
|
+
} else if (diffMonths <= 12) {
|
|
1689
|
+
groupKey = `${diffMonths}mo ago`;
|
|
1690
|
+
} else {
|
|
1691
|
+
const diffYears = Math.floor(diffMonths / 12);
|
|
1692
|
+
groupKey = `${diffYears}y ago`;
|
|
1693
|
+
}
|
|
1694
|
+
if (!groups[groupKey]) {
|
|
1695
|
+
groups[groupKey] = [];
|
|
1696
|
+
}
|
|
1697
|
+
groups[groupKey].push(conv);
|
|
1698
|
+
});
|
|
1699
|
+
const sortedGroups = Object.entries(groups).sort((a, b) => {
|
|
1700
|
+
const order = ["Today", "Yesterday"];
|
|
1701
|
+
const aIndex = order.indexOf(a[0]);
|
|
1702
|
+
const bIndex = order.indexOf(b[0]);
|
|
1703
|
+
if (aIndex !== -1 && bIndex !== -1) return aIndex - bIndex;
|
|
1704
|
+
if (aIndex !== -1) return -1;
|
|
1705
|
+
if (bIndex !== -1) return 1;
|
|
1706
|
+
const aMatch = a[0].match(/(\d+)([dw]|mo|y)/);
|
|
1707
|
+
const bMatch = b[0].match(/(\d+)([dw]|mo|y)/);
|
|
1708
|
+
if (aMatch && bMatch) {
|
|
1709
|
+
const aNum = parseInt(aMatch[1]);
|
|
1710
|
+
const bNum = parseInt(bMatch[1]);
|
|
1711
|
+
const aUnit = aMatch[2];
|
|
1712
|
+
const bUnit = bMatch[2];
|
|
1713
|
+
const unitToDays = { "d": 1, "w": 7, "mo": 30, "y": 365 };
|
|
1714
|
+
const aDays = aNum * unitToDays[aUnit];
|
|
1715
|
+
const bDays = bNum * unitToDays[bUnit];
|
|
1716
|
+
return aDays - bDays;
|
|
1717
|
+
}
|
|
1718
|
+
return 0;
|
|
1719
|
+
});
|
|
1720
|
+
return sortedGroups;
|
|
1721
|
+
}, [conversations, searchQuery]);
|
|
1722
|
+
const renderMessages = () => messages.map((message, index) => {
|
|
1723
|
+
const sourceParts = message.parts?.filter((part) => part.type === "source-url") || [];
|
|
1724
|
+
const fileParts = message.parts?.filter((part) => part.type === "file") || [];
|
|
1725
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: index > 0 ? "mt-6" : "", children: [
|
|
1726
|
+
message.role === "assistant" && sourceParts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Sources, { children: [
|
|
1727
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SourcesTrigger, { count: sourceParts.length }),
|
|
1728
|
+
sourceParts.map((part, i) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SourcesContent, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
1729
|
+
Source,
|
|
1730
|
+
{
|
|
1731
|
+
href: part.url,
|
|
1732
|
+
title: part.url
|
|
1733
|
+
},
|
|
1734
|
+
`${message.id}-${i}`
|
|
1735
|
+
) }, `${message.id}-${i}`))
|
|
1736
|
+
] }),
|
|
1737
|
+
fileParts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: cn(
|
|
1738
|
+
"flex mb-1",
|
|
1739
|
+
message.role === "user" ? "justify-end" : "justify-start"
|
|
1740
|
+
), children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
1741
|
+
MessageAttachments,
|
|
1742
|
+
{
|
|
1743
|
+
attachments: fileParts.map((part) => ({
|
|
1744
|
+
filename: part.filename || "unknown",
|
|
1745
|
+
mediaType: part.mediaType,
|
|
1746
|
+
url: part.url,
|
|
1747
|
+
size: part.size || 0
|
|
1748
|
+
}))
|
|
1749
|
+
}
|
|
1750
|
+
) }),
|
|
1751
|
+
message.parts ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "space-y-2", children: message.parts.map((part, i) => {
|
|
1752
|
+
switch (part.type) {
|
|
1753
|
+
case "text":
|
|
1754
|
+
const isTextStreaming = status === "streaming" && i === message.parts.length - 1 && message.id === messages.at(-1)?.id;
|
|
1755
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react9.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Message, { from: message.role, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MessageContent, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Response, { isStreaming: isTextStreaming, children: part.text }) }) }) }, `${message.id}-${i}`);
|
|
1756
|
+
case "reasoning":
|
|
1757
|
+
const isCurrentlyStreaming = status === "streaming" && i === message.parts.length - 1 && message.id === messages.at(-1)?.id;
|
|
1758
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
|
|
1759
|
+
Reasoning,
|
|
1760
|
+
{
|
|
1761
|
+
className: "w-full",
|
|
1762
|
+
isStreaming: isCurrentlyStreaming,
|
|
1763
|
+
defaultOpen: false,
|
|
1764
|
+
open: isCurrentlyStreaming ? true : void 0,
|
|
1765
|
+
children: [
|
|
1766
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ReasoningTrigger, {}),
|
|
1767
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ReasoningContent, { children: part.text })
|
|
1768
|
+
]
|
|
1769
|
+
},
|
|
1770
|
+
`${message.id}-${i}`
|
|
1771
|
+
);
|
|
1772
|
+
default:
|
|
1773
|
+
if (part.type.startsWith("tool-") || part.type === "dynamic-tool") {
|
|
1774
|
+
const toolPart = part;
|
|
1775
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Tool, { children: [
|
|
1776
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ToolHeader, { type: part.type, state: toolPart.state }),
|
|
1777
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(ToolContent, { children: [
|
|
1778
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ToolInput, { input: toolPart.input }),
|
|
1779
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ToolOutput, { output: toolPart.output, errorText: toolPart.errorText })
|
|
1780
|
+
] })
|
|
1781
|
+
] }, `${message.id}-${i}`);
|
|
1782
|
+
}
|
|
1783
|
+
return null;
|
|
1784
|
+
}
|
|
1785
|
+
}) }) : (
|
|
1786
|
+
/* Handle standard AI SDK messages with content or text property */
|
|
1787
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react9.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Message, { from: message.role, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MessageContent, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Response, { children: message.content || message.text }) }) }) }, `${message.id}-content`)
|
|
1788
|
+
)
|
|
1789
|
+
] }, message.id);
|
|
1790
|
+
});
|
|
1791
|
+
const handleSelectConversation = async (selectedConversationId, conversationTitle) => {
|
|
1792
|
+
if (!config?.userId) return;
|
|
1793
|
+
try {
|
|
1794
|
+
const existingTab = tabs.find((tab) => tab.id === selectedConversationId);
|
|
1795
|
+
if (existingTab) {
|
|
1796
|
+
switchToTab(selectedConversationId);
|
|
1797
|
+
setShowHistory(false);
|
|
1798
|
+
return;
|
|
1799
|
+
}
|
|
1800
|
+
const newTab = {
|
|
1801
|
+
id: selectedConversationId,
|
|
1802
|
+
title: conversationTitle,
|
|
1803
|
+
isActive: true
|
|
1804
|
+
};
|
|
1805
|
+
setTabs((prevTabs) => {
|
|
1806
|
+
const updatedTabs = prevTabs.map((tab) => ({
|
|
1807
|
+
...tab,
|
|
1808
|
+
isActive: false
|
|
1809
|
+
}));
|
|
1810
|
+
return [...updatedTabs, newTab];
|
|
1811
|
+
});
|
|
1812
|
+
setActiveTabId(selectedConversationId);
|
|
1813
|
+
await loadConversation(selectedConversationId);
|
|
1814
|
+
setShowHistory(false);
|
|
1815
|
+
} catch (error) {
|
|
1816
|
+
console.error("Error loading conversation:", error);
|
|
1817
|
+
}
|
|
1818
|
+
};
|
|
1819
|
+
const dropdownRef = (0, import_react8.useRef)(null);
|
|
1820
|
+
(0, import_react8.useEffect)(() => {
|
|
1821
|
+
const handleClickOutside = (event) => {
|
|
1822
|
+
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
|
1823
|
+
setShowHistory(false);
|
|
1824
|
+
}
|
|
1825
|
+
};
|
|
1826
|
+
if (showHistory) {
|
|
1827
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
1828
|
+
}
|
|
1829
|
+
return () => {
|
|
1830
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
1831
|
+
};
|
|
1832
|
+
}, [showHistory]);
|
|
1833
|
+
(0, import_react8.useEffect)(() => {
|
|
1834
|
+
if (!isResizing) return;
|
|
1835
|
+
const handleMouseMove = (e) => {
|
|
1836
|
+
const newWidth = window.innerWidth - e.clientX;
|
|
1837
|
+
const minWidth = 300;
|
|
1838
|
+
const maxWidth = window.innerWidth * 0.8;
|
|
1839
|
+
setComponentWidth(Math.max(minWidth, Math.min(maxWidth, newWidth)));
|
|
1840
|
+
};
|
|
1841
|
+
const handleMouseUp = () => {
|
|
1842
|
+
setIsResizing(false);
|
|
1843
|
+
};
|
|
1844
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
1845
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
1846
|
+
return () => {
|
|
1847
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
1848
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
1849
|
+
};
|
|
1850
|
+
}, [isResizing]);
|
|
1851
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: cn("w-full h-full flex flex-col bg-white dark:bg-gray-900 overflow-hidden ring-1 ring-black/[0.02] dark:ring-white/[0.03]", themeMode === "dark" && "dark"), children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
|
|
1852
|
+
"div",
|
|
1853
|
+
{
|
|
1854
|
+
className: cn(
|
|
1855
|
+
"flex flex-col h-full w-full overflow-hidden relative chat-widget-container",
|
|
1856
|
+
themeMode === "dark" && "dark"
|
|
1857
|
+
),
|
|
1858
|
+
children: [
|
|
1859
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-2 px-3 py-2 border-b backdrop-blur-sm relative z-20", style: {
|
|
1860
|
+
borderColor: themeMode === "dark" ? "rgba(255,255,255,0.08)" : "rgba(0,0,0,0.08)",
|
|
1861
|
+
backgroundColor: themeMode === "dark" ? "rgba(37,37,37,0.8)" : "rgba(255,255,255,0.8)"
|
|
1862
|
+
}, children: [
|
|
1863
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "flex items-center gap-1 flex-1 min-w-0 overflow-x-auto scrollbar-hide py-0.5 scroll-smooth", children: tabs.map((tab, index) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
|
|
1864
|
+
"div",
|
|
1865
|
+
{
|
|
1866
|
+
className: "relative flex items-center gap-1.5 px-3 py-1.5 rounded-lg cursor-pointer transition-all duration-150 group flex-shrink-0 min-w-0",
|
|
1867
|
+
style: {
|
|
1868
|
+
backgroundColor: tab.isActive ? themeMode === "dark" ? "#444444" : "#f5f5f5" : "transparent",
|
|
1869
|
+
color: tab.isActive ? themeMode === "dark" ? "#e5e5e5" : "#171717" : themeMode === "dark" ? "#999999" : "#737373"
|
|
1870
|
+
},
|
|
1871
|
+
onMouseEnter: (e) => {
|
|
1872
|
+
if (!tab.isActive) {
|
|
1873
|
+
e.currentTarget.style.backgroundColor = themeMode === "dark" ? "rgba(68,68,68,0.5)" : "rgba(245,245,245,0.5)";
|
|
1874
|
+
}
|
|
1875
|
+
},
|
|
1876
|
+
onMouseLeave: (e) => {
|
|
1877
|
+
if (!tab.isActive) {
|
|
1878
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
1879
|
+
}
|
|
1880
|
+
},
|
|
1881
|
+
onClick: (e) => {
|
|
1882
|
+
e.stopPropagation();
|
|
1883
|
+
switchToTab(tab.id);
|
|
1884
|
+
},
|
|
1885
|
+
children: [
|
|
1886
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "truncate max-w-28 text-[13px] font-medium transition-colors", children: tab.title }),
|
|
1887
|
+
tabs.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
1888
|
+
"button",
|
|
1889
|
+
{
|
|
1890
|
+
onClick: (e) => {
|
|
1891
|
+
e.stopPropagation();
|
|
1892
|
+
closeTab(tab.id);
|
|
1893
|
+
},
|
|
1894
|
+
className: "rounded-lg p-1 transition-all duration-150 flex-shrink-0 -mr-1",
|
|
1895
|
+
style: {
|
|
1896
|
+
opacity: tab.isActive ? 0.6 : 0
|
|
1897
|
+
},
|
|
1898
|
+
onMouseEnter: (e) => {
|
|
1899
|
+
e.currentTarget.style.opacity = "1";
|
|
1900
|
+
e.currentTarget.style.backgroundColor = themeMode === "dark" ? "#555555" : "#e5e5e5";
|
|
1901
|
+
},
|
|
1902
|
+
onMouseLeave: (e) => {
|
|
1903
|
+
e.currentTarget.style.opacity = tab.isActive ? "0.6" : "0";
|
|
1904
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
1905
|
+
},
|
|
1906
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react10.XIcon, { className: "h-3 w-3", strokeWidth: 2.5 })
|
|
1907
|
+
}
|
|
1908
|
+
)
|
|
1909
|
+
]
|
|
1910
|
+
},
|
|
1911
|
+
tab.id
|
|
1912
|
+
)) }),
|
|
1913
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-0.5 flex-shrink-0", children: [
|
|
1914
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
1915
|
+
"button",
|
|
1916
|
+
{
|
|
1917
|
+
onClick: createNewTab,
|
|
1918
|
+
className: "flex items-center justify-center w-7 h-7 rounded-lg transition-all duration-150",
|
|
1919
|
+
style: {
|
|
1920
|
+
color: themeMode === "dark" ? "#999999" : "#737373"
|
|
1921
|
+
},
|
|
1922
|
+
onMouseEnter: (e) => {
|
|
1923
|
+
e.currentTarget.style.color = themeMode === "dark" ? "#e5e5e5" : "#171717";
|
|
1924
|
+
e.currentTarget.style.backgroundColor = themeMode === "dark" ? "#444444" : "#f5f5f5";
|
|
1925
|
+
},
|
|
1926
|
+
onMouseLeave: (e) => {
|
|
1927
|
+
e.currentTarget.style.color = themeMode === "dark" ? "#999999" : "#737373";
|
|
1928
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
1929
|
+
},
|
|
1930
|
+
title: "New Chat",
|
|
1931
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react10.PlusIcon, { className: "h-4 w-4", strokeWidth: 2 })
|
|
1932
|
+
}
|
|
1933
|
+
),
|
|
1934
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "relative", ref: dropdownRef, children: [
|
|
1935
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
1936
|
+
"button",
|
|
1937
|
+
{
|
|
1938
|
+
onClick: () => setShowHistory(!showHistory),
|
|
1939
|
+
className: "flex items-center justify-center w-7 h-7 rounded-lg transition-all duration-150",
|
|
1940
|
+
style: {
|
|
1941
|
+
color: showHistory ? themeMode === "dark" ? "#e5e5e5" : "#171717" : themeMode === "dark" ? "#999999" : "#737373",
|
|
1942
|
+
backgroundColor: showHistory ? themeMode === "dark" ? "#444444" : "#f5f5f5" : "transparent"
|
|
1943
|
+
},
|
|
1944
|
+
onMouseEnter: (e) => {
|
|
1945
|
+
if (!showHistory) {
|
|
1946
|
+
e.currentTarget.style.color = themeMode === "dark" ? "#e5e5e5" : "#171717";
|
|
1947
|
+
e.currentTarget.style.backgroundColor = themeMode === "dark" ? "#444444" : "#f5f5f5";
|
|
1948
|
+
}
|
|
1949
|
+
},
|
|
1950
|
+
onMouseLeave: (e) => {
|
|
1951
|
+
if (!showHistory) {
|
|
1952
|
+
e.currentTarget.style.color = themeMode === "dark" ? "#999999" : "#737373";
|
|
1953
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
1954
|
+
}
|
|
1955
|
+
},
|
|
1956
|
+
title: "Chat History",
|
|
1957
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react10.HistoryIcon, { className: "h-4 w-4", strokeWidth: 2 })
|
|
1958
|
+
}
|
|
1959
|
+
),
|
|
1960
|
+
showHistory && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "absolute right-0 top-full mt-1.5 w-72 rounded-xl shadow-[0_4px_24px_rgba(0,0,0,0.08)] dark:shadow-[0_4px_24px_rgba(0,0,0,0.3)] z-50 animate-in fade-in slide-in-from-top-1 duration-150 overflow-hidden", style: {
|
|
1961
|
+
backgroundColor: themeMode === "dark" ? "#252525" : "#ffffff",
|
|
1962
|
+
border: `1px solid ${themeMode === "dark" ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)"}`
|
|
1963
|
+
}, children: [
|
|
1964
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "p-2.5 border-b", style: {
|
|
1965
|
+
borderColor: themeMode === "dark" ? "rgba(255,255,255,0.08)" : "rgba(0,0,0,0.08)",
|
|
1966
|
+
backgroundColor: themeMode === "dark" ? "rgba(0,0,0,0.2)" : "rgba(0,0,0,0.02)"
|
|
1967
|
+
}, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "relative", children: [
|
|
1968
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react10.SearchIcon, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5", style: {
|
|
1969
|
+
color: themeMode === "dark" ? "#666666" : "#999999"
|
|
1970
|
+
}, strokeWidth: 2 }),
|
|
1971
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
1972
|
+
"input",
|
|
1973
|
+
{
|
|
1974
|
+
type: "text",
|
|
1975
|
+
placeholder: "Search",
|
|
1976
|
+
value: searchQuery,
|
|
1977
|
+
onChange: (e) => setSearchQuery(e.target.value),
|
|
1978
|
+
className: "w-full h-7 pl-8 pr-2.5 text-[13px] rounded-lg focus:outline-none transition-all",
|
|
1979
|
+
style: {
|
|
1980
|
+
backgroundColor: themeMode === "dark" ? "#1a1a1a" : "#ffffff",
|
|
1981
|
+
border: `1px solid ${themeMode === "dark" ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)"}`,
|
|
1982
|
+
color: themeMode === "dark" ? "#e5e5e5" : "#171717"
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
)
|
|
1986
|
+
] }) }),
|
|
1987
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "max-h-[300px] overflow-y-auto ai-assistant-scrollbar", children: loadingHistory ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "text-[13px]", style: { color: themeMode === "dark" ? "#999999" : "#737373" }, children: "Loading..." }) }) : conversations.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex flex-col items-center justify-center py-12 px-4 text-center", children: [
|
|
1988
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "w-12 h-12 rounded-full flex items-center justify-center mb-3", style: {
|
|
1989
|
+
backgroundColor: themeMode === "dark" ? "#333333" : "#f5f5f5"
|
|
1990
|
+
}, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react10.MessageSquareIcon, { className: "h-5 w-5", style: { color: themeMode === "dark" ? "#666666" : "#a3a3a3" }, strokeWidth: 2 }) }),
|
|
1991
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-[13px] font-medium mb-0.5", style: { color: themeMode === "dark" ? "#e5e5e5" : "#171717" }, children: "No Conversations" }),
|
|
1992
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-[12px]", style: { color: themeMode === "dark" ? "#999999" : "#737373" }, children: "Start a new chat to begin" })
|
|
1993
|
+
] }) : groupedConversations.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex flex-col items-center justify-center py-12 px-4 text-center", children: [
|
|
1994
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "w-12 h-12 rounded-full flex items-center justify-center mb-3", style: {
|
|
1995
|
+
backgroundColor: themeMode === "dark" ? "#333333" : "#f5f5f5"
|
|
1996
|
+
}, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react10.SearchIcon, { className: "h-5 w-5", style: { color: themeMode === "dark" ? "#666666" : "#a3a3a3" }, strokeWidth: 2 }) }),
|
|
1997
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-[13px] font-medium mb-0.5", style: { color: themeMode === "dark" ? "#e5e5e5" : "#171717" }, children: "No Results" }),
|
|
1998
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-[12px]", style: { color: themeMode === "dark" ? "#999999" : "#737373" }, children: "Try a different search" })
|
|
1999
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "py-0.5", children: groupedConversations.map(([groupName, groupConversations]) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "mb-0.5", children: [
|
|
2000
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "px-2.5 py-1 sticky top-0 backdrop-blur-sm z-10", style: {
|
|
2001
|
+
backgroundColor: themeMode === "dark" ? "rgba(37,37,37,0.95)" : "rgba(255,255,255,0.95)"
|
|
2002
|
+
}, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h3", { className: "text-[10px] font-semibold uppercase tracking-wide", style: { color: themeMode === "dark" ? "#999999" : "#737373" }, children: groupName }) }),
|
|
2003
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "px-1 space-y-0.5", children: groupConversations.map((conversation) => {
|
|
2004
|
+
const isActiveConversation = activeTabId === conversation.id;
|
|
2005
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
2006
|
+
"button",
|
|
2007
|
+
{
|
|
2008
|
+
className: "w-full px-2 py-1 rounded-md transition-all duration-150 text-left group relative",
|
|
2009
|
+
style: {
|
|
2010
|
+
backgroundColor: isActiveConversation ? themeMode === "dark" ? "#333333" : "#f5f5f5" : "transparent"
|
|
2011
|
+
},
|
|
2012
|
+
onMouseEnter: (e) => {
|
|
2013
|
+
if (!isActiveConversation) {
|
|
2014
|
+
e.currentTarget.style.backgroundColor = themeMode === "dark" ? "rgba(51,51,51,0.5)" : "rgba(245,245,245,0.5)";
|
|
2015
|
+
}
|
|
2016
|
+
},
|
|
2017
|
+
onMouseLeave: (e) => {
|
|
2018
|
+
if (!isActiveConversation) {
|
|
2019
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
2020
|
+
}
|
|
2021
|
+
},
|
|
2022
|
+
onClick: () => handleSelectConversation(conversation.id, conversation.title),
|
|
2023
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-1.5", children: [
|
|
2024
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-[12px] line-clamp-1 transition-colors leading-tight", style: {
|
|
2025
|
+
fontWeight: isActiveConversation ? 500 : 400,
|
|
2026
|
+
color: isActiveConversation ? themeMode === "dark" ? "#e5e5e5" : "#171717" : themeMode === "dark" ? "#cccccc" : "#404040"
|
|
2027
|
+
}, children: conversation.title }) }),
|
|
2028
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
2029
|
+
import_lucide_react10.ChevronRightIcon,
|
|
2030
|
+
{
|
|
2031
|
+
className: "h-3 w-3 transition-all duration-150 flex-shrink-0",
|
|
2032
|
+
style: {
|
|
2033
|
+
color: themeMode === "dark" ? "#666666" : "#a3a3a3",
|
|
2034
|
+
opacity: isActiveConversation ? 1 : 0
|
|
2035
|
+
},
|
|
2036
|
+
strokeWidth: 2
|
|
2037
|
+
}
|
|
2038
|
+
)
|
|
2039
|
+
] })
|
|
2040
|
+
},
|
|
2041
|
+
conversation.id
|
|
2042
|
+
);
|
|
2043
|
+
}) })
|
|
2044
|
+
] }, groupName)) }) })
|
|
2045
|
+
] })
|
|
2046
|
+
] }),
|
|
2047
|
+
onClose && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
2048
|
+
"button",
|
|
2049
|
+
{
|
|
2050
|
+
onClick: onClose,
|
|
2051
|
+
className: "flex items-center justify-center w-7 h-7 rounded-lg transition-all duration-150",
|
|
2052
|
+
style: {
|
|
2053
|
+
color: themeMode === "dark" ? "#999999" : "#737373"
|
|
2054
|
+
},
|
|
2055
|
+
onMouseEnter: (e) => {
|
|
2056
|
+
e.currentTarget.style.color = themeMode === "dark" ? "#e5e5e5" : "#171717";
|
|
2057
|
+
e.currentTarget.style.backgroundColor = themeMode === "dark" ? "#444444" : "#f5f5f5";
|
|
2058
|
+
},
|
|
2059
|
+
onMouseLeave: (e) => {
|
|
2060
|
+
e.currentTarget.style.color = themeMode === "dark" ? "#999999" : "#737373";
|
|
2061
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
2062
|
+
},
|
|
2063
|
+
title: "Close Chat",
|
|
2064
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react10.XIcon, { className: "h-4 w-4", strokeWidth: 2 })
|
|
2065
|
+
}
|
|
2066
|
+
)
|
|
2067
|
+
] })
|
|
2068
|
+
] }),
|
|
2069
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Conversation, { className: "flex-1 max-w-full ai-assistant-scrollbar", children: [
|
|
2070
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(ConversationContent, { className: "max-w-[96%] mx-auto py-6", children: [
|
|
2071
|
+
renderMessages(),
|
|
2072
|
+
status === "submitted" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "mt-6", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Message, { from: "assistant", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MessageContent, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Loader, { size: 16 }) }) }) })
|
|
2073
|
+
] }),
|
|
2074
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ConversationScrollButton, {})
|
|
2075
|
+
] }),
|
|
2076
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "px-5 pb-5", children: [
|
|
2077
|
+
uploadError && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "mb-3 px-4 py-3 bg-red-50 dark:bg-red-900/20 border border-red-200/60 dark:border-red-800/60 rounded-2xl text-sm text-red-700 dark:text-red-400 shadow-sm", children: uploadError }),
|
|
2078
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(PromptInput, { onSubmit: handleSubmit, globalDrop: true, multiple: true, accept: "image/*", children: [
|
|
2079
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(PromptInputBody, { children: [
|
|
2080
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(PromptInputAttachments, { children: (attachment) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(PromptInputAttachment, { data: attachment }) }),
|
|
2081
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
2082
|
+
PromptInputTextarea,
|
|
2083
|
+
{
|
|
2084
|
+
onChange: (e) => setInput(e.target.value),
|
|
2085
|
+
value: input
|
|
2086
|
+
}
|
|
2087
|
+
)
|
|
2088
|
+
] }),
|
|
2089
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(PromptInputToolbar, { children: [
|
|
2090
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(PromptInputTools, { children: config?.features?.fileUpload !== false && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(AttachButton, {}) }),
|
|
2091
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(PromptInputSubmit, { disabled: !input, status })
|
|
2092
|
+
] })
|
|
2093
|
+
] })
|
|
2094
|
+
] })
|
|
2095
|
+
]
|
|
2096
|
+
}
|
|
2097
|
+
) });
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
// src/ChatWidget.tsx
|
|
2101
|
+
var import_lucide_react11 = require("lucide-react");
|
|
2102
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
2103
|
+
function ChatWidget({
|
|
2104
|
+
userId,
|
|
2105
|
+
conversationId,
|
|
2106
|
+
initialMessages,
|
|
2107
|
+
className,
|
|
2108
|
+
model,
|
|
2109
|
+
systemPrompt,
|
|
2110
|
+
temperature,
|
|
2111
|
+
theme,
|
|
2112
|
+
features,
|
|
2113
|
+
display
|
|
2114
|
+
}) {
|
|
2115
|
+
const initialWidth = display?.width || "30vw";
|
|
2116
|
+
const showToggleButton = display?.showToggleButton !== false;
|
|
2117
|
+
const [isOpen, setIsOpen] = (0, import_react11.useState)(display?.defaultOpen || false);
|
|
2118
|
+
const [width, setWidth] = (0, import_react11.useState)(() => {
|
|
2119
|
+
const value = parseInt(initialWidth);
|
|
2120
|
+
if (initialWidth.includes("vw")) {
|
|
2121
|
+
return value / 100 * window.innerWidth;
|
|
2122
|
+
}
|
|
2123
|
+
return value || window.innerWidth * 0.3;
|
|
2124
|
+
});
|
|
2125
|
+
const isResizing = (0, import_react11.useRef)(false);
|
|
2126
|
+
const handleMouseDown = (0, import_react11.useCallback)((e) => {
|
|
2127
|
+
e.preventDefault();
|
|
2128
|
+
isResizing.current = true;
|
|
2129
|
+
document.body.style.cursor = "ew-resize";
|
|
2130
|
+
document.body.style.userSelect = "none";
|
|
2131
|
+
}, []);
|
|
2132
|
+
(0, import_react11.useEffect)(() => {
|
|
2133
|
+
const getConstraints = () => ({
|
|
2134
|
+
minWidth: window.innerWidth * 0.2,
|
|
2135
|
+
maxWidth: window.innerWidth * 0.6
|
|
2136
|
+
});
|
|
2137
|
+
const handleMouseMove = (e) => {
|
|
2138
|
+
if (!isResizing.current) return;
|
|
2139
|
+
const { minWidth, maxWidth } = getConstraints();
|
|
2140
|
+
const newWidth = window.innerWidth - e.clientX;
|
|
2141
|
+
setWidth(Math.min(maxWidth, Math.max(minWidth, newWidth)));
|
|
2142
|
+
};
|
|
2143
|
+
const handleMouseUp = () => {
|
|
2144
|
+
if (isResizing.current) {
|
|
2145
|
+
isResizing.current = false;
|
|
2146
|
+
document.body.style.cursor = "";
|
|
2147
|
+
document.body.style.userSelect = "";
|
|
2148
|
+
}
|
|
2149
|
+
};
|
|
2150
|
+
const handleWindowResize = () => {
|
|
2151
|
+
const { minWidth, maxWidth } = getConstraints();
|
|
2152
|
+
setWidth((prev) => Math.min(maxWidth, Math.max(minWidth, prev)));
|
|
2153
|
+
};
|
|
2154
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
2155
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
2156
|
+
window.addEventListener("resize", handleWindowResize);
|
|
2157
|
+
return () => {
|
|
2158
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
2159
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
2160
|
+
window.removeEventListener("resize", handleWindowResize);
|
|
2161
|
+
};
|
|
2162
|
+
}, []);
|
|
2163
|
+
const config = (0, import_react11.useMemo)(() => ({
|
|
2164
|
+
userId,
|
|
2165
|
+
model,
|
|
2166
|
+
systemPrompt,
|
|
2167
|
+
temperature,
|
|
2168
|
+
theme,
|
|
2169
|
+
features
|
|
2170
|
+
}), [userId, model, systemPrompt, temperature, theme, features]);
|
|
2171
|
+
const togglePosition = display?.toggleButtonPosition || { bottom: "24px", right: "24px" };
|
|
2172
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
|
|
2173
|
+
showToggleButton && !isOpen && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2174
|
+
"button",
|
|
2175
|
+
{
|
|
2176
|
+
onClick: () => setIsOpen(true),
|
|
2177
|
+
className: "fixed z-50 rounded-full bg-primary text-primary-foreground shadow-lg hover:opacity-90 transition-all p-4",
|
|
2178
|
+
style: togglePosition,
|
|
2179
|
+
"aria-label": "Open chat",
|
|
2180
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react11.MessageCircle, { className: "h-6 w-6" })
|
|
2181
|
+
}
|
|
2182
|
+
),
|
|
2183
|
+
isOpen && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
2184
|
+
"div",
|
|
2185
|
+
{
|
|
2186
|
+
className: `fixed top-0 right-0 h-screen z-50 border-l border-gray-200 dark:border-gray-800 ${className || ""}`,
|
|
2187
|
+
style: {
|
|
2188
|
+
animation: "chat-slide-in-right 0.3s ease-out forwards",
|
|
2189
|
+
width: `${width}px`
|
|
2190
|
+
},
|
|
2191
|
+
children: [
|
|
2192
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2193
|
+
"div",
|
|
2194
|
+
{
|
|
2195
|
+
onMouseDown: handleMouseDown,
|
|
2196
|
+
className: "absolute left-0 top-0 h-full w-1 cursor-ew-resize hover:bg-primary/20 active:bg-primary/30 transition-colors z-10"
|
|
2197
|
+
}
|
|
2198
|
+
),
|
|
2199
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "w-full h-full overflow-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2200
|
+
ChatInterface,
|
|
2201
|
+
{
|
|
2202
|
+
id: conversationId,
|
|
2203
|
+
initialMessages,
|
|
2204
|
+
config,
|
|
2205
|
+
onClose: () => setIsOpen(false)
|
|
2206
|
+
}
|
|
2207
|
+
) })
|
|
2208
|
+
]
|
|
2209
|
+
}
|
|
2210
|
+
)
|
|
2211
|
+
] });
|
|
2212
|
+
}
|
|
2213
|
+
var ChatWidget_default = ChatWidget;
|
|
2214
|
+
|
|
2215
|
+
// src/hooks/use-chat-theme.ts
|
|
2216
|
+
var import_react12 = require("react");
|
|
2217
|
+
|
|
2218
|
+
// src/utils/models.ts
|
|
2219
|
+
var MODELS = [
|
|
2220
|
+
{
|
|
2221
|
+
name: "GPT-5 Nano",
|
|
2222
|
+
value: "openai/gpt-5-nano"
|
|
2223
|
+
},
|
|
2224
|
+
// Anthropic models
|
|
2225
|
+
{
|
|
2226
|
+
name: "Claude Sonnet 4.5",
|
|
2227
|
+
value: "anthropic/claude-sonnet-4-5"
|
|
2228
|
+
},
|
|
2229
|
+
{
|
|
2230
|
+
name: "Claude Sonnet 4",
|
|
2231
|
+
value: "anthropic/claude-sonnet-4-0"
|
|
2232
|
+
},
|
|
2233
|
+
{
|
|
2234
|
+
name: "Claude Haiku 3.5",
|
|
2235
|
+
value: "anthropic/claude-3-5-haiku-latest"
|
|
2236
|
+
},
|
|
2237
|
+
// OpenAI models
|
|
2238
|
+
{
|
|
2239
|
+
name: "GPT-5",
|
|
2240
|
+
value: "openai/gpt-5"
|
|
2241
|
+
},
|
|
2242
|
+
{
|
|
2243
|
+
name: "GPT-OSS-120B",
|
|
2244
|
+
value: "openai/gpt-oss-120b"
|
|
2245
|
+
},
|
|
2246
|
+
{
|
|
2247
|
+
name: "GPT 4o",
|
|
2248
|
+
value: "openai/gpt-4o"
|
|
2249
|
+
},
|
|
2250
|
+
// Google models
|
|
2251
|
+
{
|
|
2252
|
+
name: "Gemini 2.5 Flash Lite",
|
|
2253
|
+
value: "google/gemini-2.5-flash-lite"
|
|
2254
|
+
},
|
|
2255
|
+
{
|
|
2256
|
+
name: "Gemini 2.5 Flash",
|
|
2257
|
+
value: "google/gemini-2.5-flash"
|
|
2258
|
+
},
|
|
2259
|
+
{
|
|
2260
|
+
name: "Gemini 2.5 Pro",
|
|
2261
|
+
value: "google/gemini-2.5-pro"
|
|
2262
|
+
}
|
|
2263
|
+
];
|
|
2264
|
+
var DEFAULT_MODEL = MODELS[0].value;
|
|
2265
|
+
|
|
2266
|
+
// src/hooks/use-chat-theme.ts
|
|
2267
|
+
function hexToHSL(hex) {
|
|
2268
|
+
hex = hex.replace("#", "");
|
|
2269
|
+
const r = parseInt(hex.substring(0, 2), 16) / 255;
|
|
2270
|
+
const g = parseInt(hex.substring(2, 4), 16) / 255;
|
|
2271
|
+
const b = parseInt(hex.substring(4, 6), 16) / 255;
|
|
2272
|
+
const max = Math.max(r, g, b);
|
|
2273
|
+
const min = Math.min(r, g, b);
|
|
2274
|
+
let h = 0;
|
|
2275
|
+
let s = 0;
|
|
2276
|
+
const l = (max + min) / 2;
|
|
2277
|
+
if (max !== min) {
|
|
2278
|
+
const d = max - min;
|
|
2279
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
2280
|
+
switch (max) {
|
|
2281
|
+
case r:
|
|
2282
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
2283
|
+
break;
|
|
2284
|
+
case g:
|
|
2285
|
+
h = ((b - r) / d + 2) / 6;
|
|
2286
|
+
break;
|
|
2287
|
+
case b:
|
|
2288
|
+
h = ((r - g) / d + 4) / 6;
|
|
2289
|
+
break;
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
return `${Math.round(h * 360)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`;
|
|
2293
|
+
}
|
|
2294
|
+
var defaultTheme = {
|
|
2295
|
+
// Light mode
|
|
2296
|
+
lightPrimary: "#3b82f6",
|
|
2297
|
+
// Blue
|
|
2298
|
+
lightSecondary: "#f5f5f5",
|
|
2299
|
+
// Light gray
|
|
2300
|
+
lightText: "#0a0a0a",
|
|
2301
|
+
// Near black
|
|
2302
|
+
// Dark mode
|
|
2303
|
+
darkPrimary: "#3b82f6",
|
|
2304
|
+
// Blue (same as light)
|
|
2305
|
+
darkSecondary: "#262626",
|
|
2306
|
+
// Dark gray
|
|
2307
|
+
darkText: "#ffffff",
|
|
2308
|
+
// White
|
|
2309
|
+
// Typography
|
|
2310
|
+
fontFamily: "system-ui",
|
|
2311
|
+
fontSize: 14
|
|
2312
|
+
};
|
|
2313
|
+
var fontOptions = [
|
|
2314
|
+
{ value: "system-ui", label: "System Default" },
|
|
2315
|
+
{ value: "Inter, sans-serif", label: "Inter" },
|
|
2316
|
+
{ value: "Roboto, sans-serif", label: "Roboto" },
|
|
2317
|
+
{ value: "Open Sans, sans-serif", label: "Open Sans" },
|
|
2318
|
+
{ value: "Lato, sans-serif", label: "Lato" },
|
|
2319
|
+
{ value: "Poppins, sans-serif", label: "Poppins" },
|
|
2320
|
+
{ value: "Montserrat, sans-serif", label: "Montserrat" },
|
|
2321
|
+
{ value: "Georgia, serif", label: "Georgia" },
|
|
2322
|
+
{ value: "ui-monospace, monospace", label: "Monospace" }
|
|
2323
|
+
];
|
|
2324
|
+
var defaultConversationStarters = [
|
|
2325
|
+
{ text: "How can I help you today?", enabled: true },
|
|
2326
|
+
{ text: "What features does this product offer?", enabled: true },
|
|
2327
|
+
{ text: "Tell me about your capabilities", enabled: true }
|
|
2328
|
+
];
|
|
2329
|
+
var defaultModel = DEFAULT_MODEL;
|
|
2330
|
+
var defaultSystemPrompt = "You are a helpful AI assistant.";
|
|
2331
|
+
var defaultTemperature = 0.7;
|
|
2332
|
+
var defaultThemeMode = "light";
|
|
2333
|
+
function useChatTheme() {
|
|
2334
|
+
const [theme, setTheme] = (0, import_react12.useState)(defaultTheme);
|
|
2335
|
+
const [conversationStarters, setConversationStarters] = (0, import_react12.useState)(defaultConversationStarters);
|
|
2336
|
+
const [model, setModel] = (0, import_react12.useState)(defaultModel);
|
|
2337
|
+
const [systemPrompt, setSystemPrompt] = (0, import_react12.useState)(defaultSystemPrompt);
|
|
2338
|
+
const [temperature, setTemperature] = (0, import_react12.useState)(defaultTemperature);
|
|
2339
|
+
const [themeMode, setThemeMode] = (0, import_react12.useState)(defaultThemeMode);
|
|
2340
|
+
(0, import_react12.useEffect)(() => {
|
|
2341
|
+
const savedTheme = localStorage.getItem("chat-theme");
|
|
2342
|
+
if (savedTheme) {
|
|
2343
|
+
try {
|
|
2344
|
+
setTheme(JSON.parse(savedTheme));
|
|
2345
|
+
} catch (error) {
|
|
2346
|
+
console.error("Error loading theme:", error);
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
const savedStarters = localStorage.getItem("chat-conversation-starters");
|
|
2350
|
+
if (savedStarters) {
|
|
2351
|
+
try {
|
|
2352
|
+
setConversationStarters(JSON.parse(savedStarters));
|
|
2353
|
+
} catch (error) {
|
|
2354
|
+
console.error("Error loading conversation starters:", error);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
const savedModel = localStorage.getItem("chat-model");
|
|
2358
|
+
if (savedModel) {
|
|
2359
|
+
try {
|
|
2360
|
+
setModel(savedModel);
|
|
2361
|
+
} catch (error) {
|
|
2362
|
+
console.error("Error loading model:", error);
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
const savedSystemPrompt = localStorage.getItem("chat-system-prompt");
|
|
2366
|
+
if (savedSystemPrompt) {
|
|
2367
|
+
try {
|
|
2368
|
+
setSystemPrompt(savedSystemPrompt);
|
|
2369
|
+
} catch (error) {
|
|
2370
|
+
console.error("Error loading system prompt:", error);
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
const savedTemperature = localStorage.getItem("chat-temperature");
|
|
2374
|
+
if (savedTemperature) {
|
|
2375
|
+
try {
|
|
2376
|
+
setTemperature(parseFloat(savedTemperature));
|
|
2377
|
+
} catch (error) {
|
|
2378
|
+
console.error("Error loading temperature:", error);
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
const savedThemeMode = localStorage.getItem("chat-theme-mode");
|
|
2382
|
+
if (savedThemeMode) {
|
|
2383
|
+
try {
|
|
2384
|
+
setThemeMode(savedThemeMode);
|
|
2385
|
+
} catch (error) {
|
|
2386
|
+
console.error("Error loading theme mode:", error);
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
const handleThemeChange = (e) => {
|
|
2390
|
+
const customEvent = e;
|
|
2391
|
+
setTheme(customEvent.detail);
|
|
2392
|
+
};
|
|
2393
|
+
const handleStartersChange = (e) => {
|
|
2394
|
+
const customEvent = e;
|
|
2395
|
+
setConversationStarters(customEvent.detail);
|
|
2396
|
+
};
|
|
2397
|
+
const handleSystemPromptChange = (e) => {
|
|
2398
|
+
const customEvent = e;
|
|
2399
|
+
setSystemPrompt(customEvent.detail);
|
|
2400
|
+
};
|
|
2401
|
+
const handleModelChange = (e) => {
|
|
2402
|
+
const customEvent = e;
|
|
2403
|
+
setModel(customEvent.detail);
|
|
2404
|
+
};
|
|
2405
|
+
const handleTemperatureChange = (e) => {
|
|
2406
|
+
const customEvent = e;
|
|
2407
|
+
setTemperature(customEvent.detail);
|
|
2408
|
+
};
|
|
2409
|
+
const handleThemeModeChange = (e) => {
|
|
2410
|
+
const customEvent = e;
|
|
2411
|
+
setThemeMode(customEvent.detail);
|
|
2412
|
+
};
|
|
2413
|
+
window.addEventListener("chat-theme-change", handleThemeChange);
|
|
2414
|
+
window.addEventListener("chat-starters-change", handleStartersChange);
|
|
2415
|
+
window.addEventListener("chat-model-change", handleModelChange);
|
|
2416
|
+
window.addEventListener("chat-system-prompt-change", handleSystemPromptChange);
|
|
2417
|
+
window.addEventListener("chat-temperature-change", handleTemperatureChange);
|
|
2418
|
+
window.addEventListener("chat-theme-mode-change", handleThemeModeChange);
|
|
2419
|
+
return () => {
|
|
2420
|
+
window.removeEventListener("chat-theme-change", handleThemeChange);
|
|
2421
|
+
window.removeEventListener("chat-starters-change", handleStartersChange);
|
|
2422
|
+
window.removeEventListener("chat-model-change", handleModelChange);
|
|
2423
|
+
window.removeEventListener("chat-system-prompt-change", handleSystemPromptChange);
|
|
2424
|
+
window.removeEventListener("chat-temperature-change", handleTemperatureChange);
|
|
2425
|
+
window.removeEventListener("chat-theme-mode-change", handleThemeModeChange);
|
|
2426
|
+
};
|
|
2427
|
+
}, []);
|
|
2428
|
+
(0, import_react12.useEffect)(() => {
|
|
2429
|
+
localStorage.setItem("chat-theme", JSON.stringify(theme));
|
|
2430
|
+
const root = document.documentElement;
|
|
2431
|
+
if (themeMode === "light") {
|
|
2432
|
+
root.style.setProperty("--chat-primary", hexToHSL(theme.lightPrimary));
|
|
2433
|
+
root.style.setProperty("--chat-secondary", hexToHSL(theme.lightSecondary));
|
|
2434
|
+
root.style.setProperty("--chat-text", hexToHSL(theme.lightText));
|
|
2435
|
+
} else {
|
|
2436
|
+
root.style.setProperty("--chat-primary", hexToHSL(theme.darkPrimary));
|
|
2437
|
+
root.style.setProperty("--chat-secondary", hexToHSL(theme.darkSecondary));
|
|
2438
|
+
root.style.setProperty("--chat-text", hexToHSL(theme.darkText));
|
|
2439
|
+
}
|
|
2440
|
+
root.style.setProperty("--chat-font-family", theme.fontFamily);
|
|
2441
|
+
root.style.setProperty("--chat-font-size", `${theme.fontSize}px`);
|
|
2442
|
+
window.dispatchEvent(new CustomEvent("chat-theme-change", { detail: theme }));
|
|
2443
|
+
}, [theme, themeMode]);
|
|
2444
|
+
(0, import_react12.useEffect)(() => {
|
|
2445
|
+
localStorage.setItem("chat-conversation-starters", JSON.stringify(conversationStarters));
|
|
2446
|
+
window.dispatchEvent(new CustomEvent("chat-starters-change", { detail: conversationStarters }));
|
|
2447
|
+
}, [conversationStarters]);
|
|
2448
|
+
(0, import_react12.useEffect)(() => {
|
|
2449
|
+
localStorage.setItem("chat-model", model);
|
|
2450
|
+
window.dispatchEvent(new CustomEvent("chat-model-change", { detail: model }));
|
|
2451
|
+
}, [model]);
|
|
2452
|
+
(0, import_react12.useEffect)(() => {
|
|
2453
|
+
localStorage.setItem("chat-system-prompt", systemPrompt);
|
|
2454
|
+
window.dispatchEvent(new CustomEvent("chat-system-prompt-change", { detail: systemPrompt }));
|
|
2455
|
+
}, [systemPrompt]);
|
|
2456
|
+
(0, import_react12.useEffect)(() => {
|
|
2457
|
+
localStorage.setItem("chat-temperature", temperature.toString());
|
|
2458
|
+
window.dispatchEvent(new CustomEvent("chat-temperature-change", { detail: temperature }));
|
|
2459
|
+
}, [temperature]);
|
|
2460
|
+
(0, import_react12.useEffect)(() => {
|
|
2461
|
+
localStorage.setItem("chat-theme-mode", themeMode);
|
|
2462
|
+
window.dispatchEvent(new CustomEvent("chat-theme-mode-change", { detail: themeMode }));
|
|
2463
|
+
}, [themeMode]);
|
|
2464
|
+
const updateColor = (key, value) => {
|
|
2465
|
+
setTheme((prev) => ({ ...prev, [key]: value }));
|
|
2466
|
+
};
|
|
2467
|
+
const updateLightColors = (colors) => {
|
|
2468
|
+
setTheme((prev) => ({
|
|
2469
|
+
...prev,
|
|
2470
|
+
...colors.primary && { lightPrimary: colors.primary },
|
|
2471
|
+
...colors.secondary && { lightSecondary: colors.secondary },
|
|
2472
|
+
...colors.text && { lightText: colors.text }
|
|
2473
|
+
}));
|
|
2474
|
+
};
|
|
2475
|
+
const updateDarkColors = (colors) => {
|
|
2476
|
+
setTheme((prev) => ({
|
|
2477
|
+
...prev,
|
|
2478
|
+
...colors.primary && { darkPrimary: colors.primary },
|
|
2479
|
+
...colors.secondary && { darkSecondary: colors.secondary },
|
|
2480
|
+
...colors.text && { darkText: colors.text }
|
|
2481
|
+
}));
|
|
2482
|
+
};
|
|
2483
|
+
const resetTheme = () => {
|
|
2484
|
+
setTheme(defaultTheme);
|
|
2485
|
+
};
|
|
2486
|
+
const updateFontSize = (size) => {
|
|
2487
|
+
setTheme((prev) => ({ ...prev, fontSize: size }));
|
|
2488
|
+
};
|
|
2489
|
+
const updateFontFamily = (family) => {
|
|
2490
|
+
setTheme((prev) => ({ ...prev, fontFamily: family }));
|
|
2491
|
+
};
|
|
2492
|
+
const updateConversationStarters = (starters) => {
|
|
2493
|
+
setConversationStarters(starters);
|
|
2494
|
+
};
|
|
2495
|
+
const updateSystemPrompt = (prompt) => {
|
|
2496
|
+
setSystemPrompt(prompt);
|
|
2497
|
+
};
|
|
2498
|
+
const updateModel = (selectedModel) => {
|
|
2499
|
+
setModel(selectedModel);
|
|
2500
|
+
};
|
|
2501
|
+
const updateTemperature = (temp) => {
|
|
2502
|
+
setTemperature(temp);
|
|
2503
|
+
};
|
|
2504
|
+
const updateThemeMode = (mode) => {
|
|
2505
|
+
setThemeMode(mode);
|
|
2506
|
+
};
|
|
2507
|
+
return {
|
|
2508
|
+
theme,
|
|
2509
|
+
updateColor,
|
|
2510
|
+
updateLightColors,
|
|
2511
|
+
updateDarkColors,
|
|
2512
|
+
resetTheme,
|
|
2513
|
+
updateFontSize,
|
|
2514
|
+
updateFontFamily,
|
|
2515
|
+
conversationStarters,
|
|
2516
|
+
updateConversationStarters,
|
|
2517
|
+
model,
|
|
2518
|
+
updateModel,
|
|
2519
|
+
systemPrompt,
|
|
2520
|
+
updateSystemPrompt,
|
|
2521
|
+
temperature,
|
|
2522
|
+
updateTemperature,
|
|
2523
|
+
themeMode,
|
|
2524
|
+
updateThemeMode
|
|
2525
|
+
};
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
// src/ui/input.tsx
|
|
2529
|
+
var React = __toESM(require("react"));
|
|
2530
|
+
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
2531
|
+
var Input = React.forwardRef(
|
|
2532
|
+
({ className, type, ...props }, ref) => {
|
|
2533
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
2534
|
+
"input",
|
|
2535
|
+
{
|
|
2536
|
+
type,
|
|
2537
|
+
className: cn(
|
|
2538
|
+
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
|
2539
|
+
className
|
|
2540
|
+
),
|
|
2541
|
+
ref,
|
|
2542
|
+
...props
|
|
2543
|
+
}
|
|
2544
|
+
);
|
|
2545
|
+
}
|
|
2546
|
+
);
|
|
2547
|
+
Input.displayName = "Input";
|
|
2548
|
+
|
|
2549
|
+
// src/ui/dialog.tsx
|
|
2550
|
+
var DialogPrimitive = __toESM(require("@radix-ui/react-dialog"));
|
|
2551
|
+
var import_lucide_react12 = require("lucide-react");
|
|
2552
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
2553
|
+
function Dialog({
|
|
2554
|
+
...props
|
|
2555
|
+
}) {
|
|
2556
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
|
|
2557
|
+
}
|
|
2558
|
+
function DialogPortal({
|
|
2559
|
+
...props
|
|
2560
|
+
}) {
|
|
2561
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
|
|
2562
|
+
}
|
|
2563
|
+
function DialogOverlay({
|
|
2564
|
+
className,
|
|
2565
|
+
...props
|
|
2566
|
+
}) {
|
|
2567
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
2568
|
+
DialogPrimitive.Overlay,
|
|
2569
|
+
{
|
|
2570
|
+
"data-slot": "dialog-overlay",
|
|
2571
|
+
className: cn(
|
|
2572
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
|
2573
|
+
className
|
|
2574
|
+
),
|
|
2575
|
+
...props
|
|
2576
|
+
}
|
|
2577
|
+
);
|
|
2578
|
+
}
|
|
2579
|
+
function DialogContent({
|
|
2580
|
+
className,
|
|
2581
|
+
children,
|
|
2582
|
+
showCloseButton = true,
|
|
2583
|
+
...props
|
|
2584
|
+
}) {
|
|
2585
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(DialogPortal, { "data-slot": "dialog-portal", children: [
|
|
2586
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DialogOverlay, {}),
|
|
2587
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
2588
|
+
DialogPrimitive.Content,
|
|
2589
|
+
{
|
|
2590
|
+
"data-slot": "dialog-content",
|
|
2591
|
+
className: cn(
|
|
2592
|
+
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
|
2593
|
+
className
|
|
2594
|
+
),
|
|
2595
|
+
...props,
|
|
2596
|
+
children: [
|
|
2597
|
+
children,
|
|
2598
|
+
showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
2599
|
+
DialogPrimitive.Close,
|
|
2600
|
+
{
|
|
2601
|
+
"data-slot": "dialog-close",
|
|
2602
|
+
className: "ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
2603
|
+
children: [
|
|
2604
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react12.XIcon, {}),
|
|
2605
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "sr-only", children: "Close" })
|
|
2606
|
+
]
|
|
2607
|
+
}
|
|
2608
|
+
)
|
|
2609
|
+
]
|
|
2610
|
+
}
|
|
2611
|
+
)
|
|
2612
|
+
] });
|
|
2613
|
+
}
|
|
2614
|
+
function DialogHeader({ className, ...props }) {
|
|
2615
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
2616
|
+
"div",
|
|
2617
|
+
{
|
|
2618
|
+
"data-slot": "dialog-header",
|
|
2619
|
+
className: cn("flex flex-col gap-2 text-center sm:text-left", className),
|
|
2620
|
+
...props
|
|
2621
|
+
}
|
|
2622
|
+
);
|
|
2623
|
+
}
|
|
2624
|
+
function DialogTitle({
|
|
2625
|
+
className,
|
|
2626
|
+
...props
|
|
2627
|
+
}) {
|
|
2628
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
2629
|
+
DialogPrimitive.Title,
|
|
2630
|
+
{
|
|
2631
|
+
"data-slot": "dialog-title",
|
|
2632
|
+
className: cn("text-lg leading-none font-semibold", className),
|
|
2633
|
+
...props
|
|
2634
|
+
}
|
|
2635
|
+
);
|
|
2636
|
+
}
|
|
2637
|
+
function DialogDescription({
|
|
2638
|
+
className,
|
|
2639
|
+
...props
|
|
2640
|
+
}) {
|
|
2641
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
2642
|
+
DialogPrimitive.Description,
|
|
2643
|
+
{
|
|
2644
|
+
"data-slot": "dialog-description",
|
|
2645
|
+
className: cn("text-muted-foreground text-sm", className),
|
|
2646
|
+
...props
|
|
2647
|
+
}
|
|
2648
|
+
);
|
|
2649
|
+
}
|
|
2650
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2651
|
+
0 && (module.exports = {
|
|
2652
|
+
Button,
|
|
2653
|
+
ChatWidget,
|
|
2654
|
+
Dialog,
|
|
2655
|
+
DialogContent,
|
|
2656
|
+
DialogDescription,
|
|
2657
|
+
DialogHeader,
|
|
2658
|
+
DialogTitle,
|
|
2659
|
+
Input,
|
|
2660
|
+
fontOptions,
|
|
2661
|
+
useChatTheme
|
|
2662
|
+
});
|
|
2663
|
+
//# sourceMappingURL=index.js.map
|