@melony/react 0.1.12 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,25 +1,24 @@
1
- import * as React9 from 'react';
1
+ import * as React10 from 'react';
2
2
  import { createContext, useState, useEffect, useCallback, useMemo, useContext, useRef } from 'react';
3
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
+ import { generateId } from 'melony/client';
3
5
  import { clsx } from 'clsx';
4
6
  import { twMerge } from 'tailwind-merge';
5
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
- import { generateId } from '@melony/core/client';
7
- import { Separator as Separator$1 } from '@base-ui/react/separator';
7
+ import { Button as Button$1 } from '@base-ui/react/button';
8
+ import { cva } from 'class-variance-authority';
8
9
  import * as ICONS from '@tabler/icons-react';
9
- import { IconArrowUp, IconPlus, IconMessage, IconTrash, IconArrowLeft, IconHistory, IconX, IconUser, IconLogout, IconBrandGoogle, IconSelector, IconCheck, IconChevronUp, IconChevronDown } from '@tabler/icons-react';
10
+ import { IconArrowUp, IconPlus, IconMessage, IconTrash, IconHistory, IconX, IconArrowLeft, IconChevronLeft, IconChevronRight, IconUser, IconLogout, IconBrandGoogle, IconDeviceDesktop, IconMoon, IconSun, IconSelector, IconCheck, IconChevronUp, IconChevronDown } from '@tabler/icons-react';
11
+ import { Separator as Separator$1 } from '@base-ui/react/separator';
10
12
  import { mergeProps } from '@base-ui/react/merge-props';
11
13
  import { useRender } from '@base-ui/react/use-render';
12
- import { cva } from 'class-variance-authority';
13
14
  import { Input as Input$1 } from '@base-ui/react/input';
14
15
  import { Select as Select$1 } from '@base-ui/react/select';
15
- import { Button as Button$1 } from '@base-ui/react/button';
16
16
  import { AlertDialog as AlertDialog$1 } from '@base-ui/react/alert-dialog';
17
17
  import { Menu } from '@base-ui/react/menu';
18
18
 
19
19
  // src/providers/melony-provider.tsx
20
- function cn(...inputs) {
21
- return twMerge(clsx(inputs));
22
- }
20
+
21
+ // src/lib/group-events-to-messages.ts
23
22
  function groupEventsToMessages(events) {
24
23
  if (events.length === 0) return [];
25
24
  const messages = [];
@@ -282,6 +281,58 @@ var ThreadProvider = ({
282
281
  );
283
282
  return /* @__PURE__ */ jsx(ThreadContext.Provider, { value, children });
284
283
  };
284
+ var ThemeContext = createContext(void 0);
285
+ function ThemeProvider({ children }) {
286
+ const [theme, setThemeState] = useState("system");
287
+ const [resolvedTheme, setResolvedTheme] = useState("light");
288
+ useEffect(() => {
289
+ if (typeof window !== "undefined") {
290
+ const stored = localStorage.getItem("theme");
291
+ if (stored) {
292
+ setThemeState(stored);
293
+ }
294
+ }
295
+ }, []);
296
+ useEffect(() => {
297
+ if (typeof window !== "undefined") {
298
+ if (theme === "system") {
299
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
300
+ const updateResolvedTheme = () => {
301
+ setResolvedTheme(mediaQuery.matches ? "dark" : "light");
302
+ };
303
+ updateResolvedTheme();
304
+ mediaQuery.addEventListener("change", updateResolvedTheme);
305
+ return () => mediaQuery.removeEventListener("change", updateResolvedTheme);
306
+ } else {
307
+ setResolvedTheme(theme);
308
+ }
309
+ }
310
+ }, [theme]);
311
+ useEffect(() => {
312
+ if (typeof window !== "undefined") {
313
+ const root = document.documentElement;
314
+ if (resolvedTheme === "dark") {
315
+ root.classList.add("dark");
316
+ } else {
317
+ root.classList.remove("dark");
318
+ }
319
+ }
320
+ }, [resolvedTheme]);
321
+ const setTheme = (newTheme) => {
322
+ setThemeState(newTheme);
323
+ if (typeof window !== "undefined") {
324
+ localStorage.setItem("theme", newTheme);
325
+ }
326
+ };
327
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: { theme, setTheme, resolvedTheme }, children });
328
+ }
329
+ function useTheme() {
330
+ const context = useContext(ThemeContext);
331
+ if (context === void 0) {
332
+ throw new Error("useTheme must be used within a ThemeProvider");
333
+ }
334
+ return context;
335
+ }
285
336
  var useMelony = (options) => {
286
337
  const context = useContext(MelonyContext);
287
338
  if (context === void 0) {
@@ -310,6 +361,103 @@ var useThreads = () => {
310
361
  }
311
362
  return context;
312
363
  };
364
+ function cn(...inputs) {
365
+ return twMerge(clsx(inputs));
366
+ }
367
+ var buttonVariants = cva(
368
+ "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-4xl border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-[3px] aria-invalid:ring-[3px] [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none",
369
+ {
370
+ variants: {
371
+ variant: {
372
+ default: "bg-primary text-primary-foreground hover:bg-primary/80",
373
+ outline: "border-border bg-input/30 hover:bg-input/50 hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground",
374
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
375
+ ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
376
+ destructive: "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
377
+ link: "text-primary underline-offset-4 hover:underline"
378
+ },
379
+ size: {
380
+ default: "h-9 gap-1.5 px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5",
381
+ xs: "h-6 gap-1 px-2.5 text-xs has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3",
382
+ sm: "h-8 gap-1 px-3 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
383
+ lg: "h-10 gap-1.5 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
384
+ icon: "size-9",
385
+ "icon-xs": "size-6 [&_svg:not([class*='size-'])]:size-3",
386
+ "icon-sm": "size-8",
387
+ "icon-lg": "size-10"
388
+ }
389
+ },
390
+ defaultVariants: {
391
+ variant: "default",
392
+ size: "default"
393
+ }
394
+ }
395
+ );
396
+ function Button({
397
+ className,
398
+ variant = "default",
399
+ size = "default",
400
+ ...props
401
+ }) {
402
+ return /* @__PURE__ */ jsx(
403
+ Button$1,
404
+ {
405
+ "data-slot": "button",
406
+ className: cn(buttonVariants({ variant, size, className })),
407
+ ...props
408
+ }
409
+ );
410
+ }
411
+ function Textarea({ className, ...props }) {
412
+ return /* @__PURE__ */ jsx(
413
+ "textarea",
414
+ {
415
+ "data-slot": "textarea",
416
+ className: cn(
417
+ "border-input bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 resize-none rounded-xl border px-3 py-3 text-base transition-colors focus-visible:ring-[3px] aria-invalid:ring-[3px] md:text-sm placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full outline-none disabled:cursor-not-allowed disabled:opacity-50",
418
+ className
419
+ ),
420
+ ...props
421
+ }
422
+ );
423
+ }
424
+ function Composer({
425
+ value,
426
+ onChange,
427
+ onSubmit,
428
+ placeholder = "Type a message...",
429
+ isLoading,
430
+ className
431
+ }) {
432
+ const handleKeyDown = (e) => {
433
+ if (e.key === "Enter" && !e.shiftKey) {
434
+ e.preventDefault();
435
+ onSubmit();
436
+ }
437
+ };
438
+ return /* @__PURE__ */ jsx("div", { className: cn("relative flex flex-col w-full", className), children: /* @__PURE__ */ jsxs("div", { className: "relative flex flex-col w-full border-input border-[1.5px] rounded-3xl bg-background shadow-sm focus-within:border-ring transition-all p-2", children: [
439
+ /* @__PURE__ */ jsx(
440
+ Textarea,
441
+ {
442
+ value,
443
+ onChange: (e) => onChange(e.target.value),
444
+ onKeyDown: handleKeyDown,
445
+ placeholder,
446
+ className: "min-h-[44px] max-h-[200px] border-none bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 px-3 py-2 text-[15px] resize-none"
447
+ }
448
+ ),
449
+ /* @__PURE__ */ jsx("div", { className: "flex justify-end items-center", children: /* @__PURE__ */ jsx(
450
+ Button,
451
+ {
452
+ type: "submit",
453
+ disabled: !value.trim() && !isLoading || isLoading,
454
+ size: "icon-lg",
455
+ onClick: () => onSubmit(),
456
+ children: /* @__PURE__ */ jsx(IconArrowUp, { className: "h-5 w-5" })
457
+ }
458
+ ) })
459
+ ] }) });
460
+ }
313
461
  function Card({
314
462
  className,
315
463
  size = "default",
@@ -378,7 +526,7 @@ var Card2 = ({
378
526
  return /* @__PURE__ */ jsxs(
379
527
  Card,
380
528
  {
381
- className: cn("w-full max-w-2xl shadow-sm", className),
529
+ className: cn("min-w-96", className),
382
530
  style,
383
531
  children: [
384
532
  (title || subtitle) && /* @__PURE__ */ jsxs(CardHeader, { className: "pb-3", children: [
@@ -1199,19 +1347,6 @@ var Input2 = ({
1199
1347
  )
1200
1348
  ] });
1201
1349
  };
1202
- function Textarea({ className, ...props }) {
1203
- return /* @__PURE__ */ jsx(
1204
- "textarea",
1205
- {
1206
- "data-slot": "textarea",
1207
- className: cn(
1208
- "border-input bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 resize-none rounded-xl border px-3 py-3 text-base transition-colors focus-visible:ring-[3px] aria-invalid:ring-[3px] md:text-sm placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full outline-none disabled:cursor-not-allowed disabled:opacity-50",
1209
- className
1210
- ),
1211
- ...props
1212
- }
1213
- );
1214
- }
1215
1350
  var Textarea2 = ({
1216
1351
  placeholder,
1217
1352
  defaultValue,
@@ -1583,50 +1718,6 @@ var RadioGroup = ({
1583
1718
  )
1584
1719
  ] });
1585
1720
  };
1586
- var buttonVariants = cva(
1587
- "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-4xl border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-[3px] aria-invalid:ring-[3px] [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none",
1588
- {
1589
- variants: {
1590
- variant: {
1591
- default: "bg-primary text-primary-foreground hover:bg-primary/80",
1592
- outline: "border-border bg-input/30 hover:bg-input/50 hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground",
1593
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
1594
- ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
1595
- destructive: "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
1596
- link: "text-primary underline-offset-4 hover:underline"
1597
- },
1598
- size: {
1599
- default: "h-9 gap-1.5 px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5",
1600
- xs: "h-6 gap-1 px-2.5 text-xs has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3",
1601
- sm: "h-8 gap-1 px-3 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
1602
- lg: "h-10 gap-1.5 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
1603
- icon: "size-9",
1604
- "icon-xs": "size-6 [&_svg:not([class*='size-'])]:size-3",
1605
- "icon-sm": "size-8",
1606
- "icon-lg": "size-10"
1607
- }
1608
- },
1609
- defaultVariants: {
1610
- variant: "default",
1611
- size: "default"
1612
- }
1613
- }
1614
- );
1615
- function Button({
1616
- className,
1617
- variant = "default",
1618
- size = "default",
1619
- ...props
1620
- }) {
1621
- return /* @__PURE__ */ jsx(
1622
- Button$1,
1623
- {
1624
- "data-slot": "button",
1625
- className: cn(buttonVariants({ variant, size, className })),
1626
- ...props
1627
- }
1628
- );
1629
- }
1630
1721
  var Button2 = ({
1631
1722
  label,
1632
1723
  variant = "primary",
@@ -1643,6 +1734,8 @@ var Button2 = ({
1643
1734
  secondary: "secondary",
1644
1735
  danger: "destructive",
1645
1736
  outline: "outline",
1737
+ ghost: "ghost",
1738
+ link: "link",
1646
1739
  success: "default"
1647
1740
  // Success doesn't have a direct shadcn mapping in base variant, default is usually primary
1648
1741
  };
@@ -1711,6 +1804,32 @@ var Form = ({ children, onSubmitAction, className, style }) => {
1711
1804
  }
1712
1805
  );
1713
1806
  };
1807
+ function StarterPrompts({
1808
+ prompts,
1809
+ onPromptClick
1810
+ }) {
1811
+ if (!prompts || prompts.length === 0) {
1812
+ return null;
1813
+ }
1814
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col space-y-4 animate-in fade-in slide-in-from-bottom-4 duration-500 mt-auto max-w-2xl", children: [
1815
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: /* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold tracking-tight", children: "What can I help with today?" }) }),
1816
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 w-full", children: prompts.map((item, index) => /* @__PURE__ */ jsx(
1817
+ Button2,
1818
+ {
1819
+ label: item.label,
1820
+ variant: "ghost",
1821
+ size: "lg",
1822
+ onClickAction: {
1823
+ type: "text",
1824
+ role: "user",
1825
+ data: { content: item.prompt }
1826
+ },
1827
+ className: "w-full justify-start"
1828
+ },
1829
+ index
1830
+ )) })
1831
+ ] });
1832
+ }
1714
1833
  function UIRenderer({ node }) {
1715
1834
  const { type, props, children } = node;
1716
1835
  const typeMap = {
@@ -1749,46 +1868,57 @@ function UIRenderer({ node }) {
1749
1868
  const componentProps = { ...props };
1750
1869
  return /* @__PURE__ */ jsx(Component, { ...componentProps, children: renderedChildren });
1751
1870
  }
1752
- function Composer({
1753
- value,
1754
- onChange,
1755
- onSubmit,
1756
- placeholder = "Type a message...",
1757
- isLoading,
1758
- className
1759
- }) {
1760
- const handleKeyDown = (e) => {
1761
- if (e.key === "Enter" && !e.shiftKey) {
1762
- e.preventDefault();
1763
- onSubmit();
1871
+ function MessageContent({ events }) {
1872
+ return /* @__PURE__ */ jsx(Fragment, { children: events.map((event, index) => {
1873
+ if (event.type === "text-delta") {
1874
+ return /* @__PURE__ */ jsx("span", { children: event.data?.delta }, index);
1764
1875
  }
1765
- };
1766
- return /* @__PURE__ */ jsx("div", { className: cn("relative flex flex-col w-full", className), children: /* @__PURE__ */ jsxs("div", { className: "relative flex flex-col w-full border-input border-[1.5px] rounded-3xl bg-background shadow-sm focus-within:border-ring transition-all p-2", children: [
1767
- /* @__PURE__ */ jsx(
1768
- Textarea,
1769
- {
1770
- value,
1771
- onChange: (e) => onChange(e.target.value),
1772
- onKeyDown: handleKeyDown,
1773
- placeholder,
1774
- className: "min-h-[44px] max-h-[200px] border-none bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 px-3 py-2 text-[15px] resize-none"
1775
- }
1776
- ),
1777
- /* @__PURE__ */ jsx("div", { className: "flex justify-end items-center px-2 pb-0.5", children: /* @__PURE__ */ jsx(
1778
- Button,
1779
- {
1780
- type: "submit",
1781
- disabled: !value.trim() && !isLoading || isLoading,
1782
- size: "icon",
1783
- onClick: () => onSubmit(),
1784
- className: cn(
1785
- "h-8 w-8 rounded-full transition-all shrink-0",
1786
- value.trim() ? "bg-foreground text-background hover:bg-foreground/90" : "bg-muted-foreground/20 text-muted-foreground/40"
1787
- ),
1788
- children: /* @__PURE__ */ jsx(IconArrowUp, { className: "h-5 w-5" })
1789
- }
1790
- ) })
1791
- ] }) });
1876
+ if (event.type === "text") {
1877
+ return /* @__PURE__ */ jsx("p", { children: event.data?.content || event.data?.text }, index);
1878
+ }
1879
+ if (event.ui) {
1880
+ return /* @__PURE__ */ jsx(UIRenderer, { node: event.ui }, index);
1881
+ }
1882
+ return null;
1883
+ }) });
1884
+ }
1885
+ function MessageBubble({ message }) {
1886
+ const isUser = message.role === "user";
1887
+ return /* @__PURE__ */ jsx(
1888
+ "div",
1889
+ {
1890
+ className: cn(
1891
+ "flex flex-col",
1892
+ isUser ? "items-end" : "items-start"
1893
+ ),
1894
+ children: /* @__PURE__ */ jsx(
1895
+ "div",
1896
+ {
1897
+ className: cn(
1898
+ "flex flex-col items-start max-w-[85%] rounded-2xl px-4 py-2 space-y-4 whitespace-pre-wrap",
1899
+ isUser ? "bg-primary text-primary-foreground" : "px-0 py-0 text-foreground"
1900
+ ),
1901
+ children: /* @__PURE__ */ jsx(MessageContent, { events: message.content })
1902
+ }
1903
+ )
1904
+ }
1905
+ );
1906
+ }
1907
+ function LoadingIndicator() {
1908
+ return /* @__PURE__ */ jsx("div", { className: "text-muted-foreground animate-pulse", children: "Thinking..." });
1909
+ }
1910
+ function ErrorDisplay({ error }) {
1911
+ return /* @__PURE__ */ jsx("div", { className: "text-destructive p-2 border border-destructive rounded-md bg-destructive/10", children: error.message });
1912
+ }
1913
+ function MessageList({ messages, isLoading, error }) {
1914
+ if (messages.length === 0) {
1915
+ return null;
1916
+ }
1917
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
1918
+ messages.map((message, index) => /* @__PURE__ */ jsx(MessageBubble, { message }, index)),
1919
+ isLoading && /* @__PURE__ */ jsx(LoadingIndicator, {}),
1920
+ error && /* @__PURE__ */ jsx(ErrorDisplay, { error })
1921
+ ] });
1792
1922
  }
1793
1923
  function Thread({
1794
1924
  className,
@@ -1820,57 +1950,38 @@ function Thread({
1820
1950
  handleSubmit(void 0, prompt);
1821
1951
  }
1822
1952
  };
1823
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col h-full bg-background", className), children: [
1824
- /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto p-4 space-y-6", children: [
1825
- /* @__PURE__ */ jsxs("div", { className: "max-w-4xl mx-auto w-full", children: [
1826
- messages.length === 0 && starterPrompts && starterPrompts.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center min-h-[300px] space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500", children: [
1827
- /* @__PURE__ */ jsx("div", { className: "text-center space-y-2", children: /* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold tracking-tight", children: "What can I help with today?" }) }),
1828
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3 w-full max-w-2xl px-4", children: starterPrompts.map((item, i) => /* @__PURE__ */ jsxs(
1829
- "button",
1830
- {
1831
- onClick: () => handleStarterPromptClick(item.prompt),
1832
- className: "flex items-center gap-3 p-4 rounded-xl border bg-card hover:bg-muted/50 transition-all text-left group",
1833
- children: [
1834
- item.icon && /* @__PURE__ */ jsx("div", { className: "p-2 rounded-lg bg-muted group-hover:bg-background transition-colors", children: item.icon }),
1835
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: item.label })
1836
- ]
1837
- },
1838
- i
1839
- )) })
1840
- ] }),
1841
- messages.map((message, i) => /* @__PURE__ */ jsx(
1842
- "div",
1843
- {
1844
- className: cn(
1845
- "flex flex-col",
1846
- message.role === "user" ? "items-end" : "items-start"
1953
+ const showStarterPrompts = messages.length === 0 && starterPrompts && starterPrompts.length > 0;
1954
+ return /* @__PURE__ */ jsxs("div", { className: cn("relative flex flex-col h-full bg-background", className), children: [
1955
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto p-4 pb-36", children: [
1956
+ /* @__PURE__ */ jsxs(
1957
+ "div",
1958
+ {
1959
+ className: cn(
1960
+ "max-w-4xl mx-auto w-full p-4",
1961
+ showStarterPrompts && "min-h-full flex flex-col"
1962
+ ),
1963
+ children: [
1964
+ showStarterPrompts && /* @__PURE__ */ jsx(
1965
+ StarterPrompts,
1966
+ {
1967
+ prompts: starterPrompts,
1968
+ onPromptClick: handleStarterPromptClick
1969
+ }
1847
1970
  ),
1848
- children: /* @__PURE__ */ jsx(
1849
- "div",
1971
+ /* @__PURE__ */ jsx(
1972
+ MessageList,
1850
1973
  {
1851
- className: cn(
1852
- "max-w-[85%] rounded-2xl px-4 py-2 space-y-2",
1853
- message.role === "user" ? "bg-primary text-primary-foreground" : "px-0 py-0 text-foreground"
1854
- ),
1855
- children: message.content.map((event, j) => {
1856
- if (event.type === "text-delta")
1857
- return /* @__PURE__ */ jsx("span", { children: event.data?.delta }, j);
1858
- if (event.type === "text")
1859
- return /* @__PURE__ */ jsx("p", { children: event.data?.content || event.data?.text }, j);
1860
- if (event.ui) return /* @__PURE__ */ jsx(UIRenderer, { node: event.ui }, j);
1861
- return null;
1862
- })
1974
+ messages,
1975
+ isLoading,
1976
+ error
1863
1977
  }
1864
1978
  )
1865
- },
1866
- i
1867
- )),
1868
- isLoading && /* @__PURE__ */ jsx("div", { className: "text-muted-foreground animate-pulse", children: "Thinking..." }),
1869
- error && /* @__PURE__ */ jsx("div", { className: "text-destructive p-2 border border-destructive rounded-md bg-destructive/10", children: error.message })
1870
- ] }),
1979
+ ]
1980
+ }
1981
+ ),
1871
1982
  /* @__PURE__ */ jsx("div", { ref: messagesEndRef })
1872
1983
  ] }),
1873
- /* @__PURE__ */ jsx("div", { className: "p-4 border-t w-full", children: /* @__PURE__ */ jsx("div", { className: "max-w-4xl mx-auto", children: /* @__PURE__ */ jsx(
1984
+ /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 p-4 w-full", children: /* @__PURE__ */ jsx("div", { className: "max-w-4xl mx-auto", children: /* @__PURE__ */ jsx(
1874
1985
  Composer,
1875
1986
  {
1876
1987
  value: input,
@@ -1882,18 +1993,34 @@ function Thread({
1882
1993
  ) }) })
1883
1994
  ] });
1884
1995
  }
1996
+ function ChatHeader({
1997
+ title,
1998
+ leftContent,
1999
+ rightContent,
2000
+ className,
2001
+ titleClassName,
2002
+ children
2003
+ }) {
2004
+ if (children) {
2005
+ return /* @__PURE__ */ jsx("div", { className: cn("p-4 border-b border-border h-14 flex items-center shrink-0", className), children });
2006
+ }
2007
+ return /* @__PURE__ */ jsxs("div", { className: cn("p-4 border-b border-border h-14 flex items-center justify-between shrink-0", className), children: [
2008
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-1 min-w-0", children: [
2009
+ leftContent,
2010
+ title && /* @__PURE__ */ jsx("div", { className: cn(
2011
+ "text-sm font-semibold truncate",
2012
+ typeof title === "string" ? titleClassName : ""
2013
+ ), children: title })
2014
+ ] }),
2015
+ rightContent && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 shrink-0 ml-2", children: rightContent })
2016
+ ] });
2017
+ }
1885
2018
  var ThreadList = ({
1886
2019
  className,
1887
2020
  emptyState,
1888
2021
  onThreadSelect
1889
2022
  }) => {
1890
- const {
1891
- threads,
1892
- activeThreadId,
1893
- selectThread,
1894
- createThread,
1895
- deleteThread
1896
- } = useThreads();
2023
+ const { threads, activeThreadId, selectThread, createThread, deleteThread } = useThreads();
1897
2024
  const handleThreadClick = (threadId) => {
1898
2025
  if (threadId !== activeThreadId) {
1899
2026
  selectThread(threadId);
@@ -1931,10 +2058,10 @@ var ThreadList = ({
1931
2058
  return d.toLocaleDateString();
1932
2059
  };
1933
2060
  return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col h-full", className), children: [
1934
- /* @__PURE__ */ jsx("div", { className: "p-2 border-b", children: /* @__PURE__ */ jsxs(
2061
+ /* @__PURE__ */ jsx("div", { className: "p-2", children: /* @__PURE__ */ jsxs(
1935
2062
  Button,
1936
2063
  {
1937
- variant: "outline",
2064
+ variant: "ghost",
1938
2065
  size: "sm",
1939
2066
  onClick: handleNewThread,
1940
2067
  className: "w-full justify-start",
@@ -1955,31 +2082,13 @@ var ThreadList = ({
1955
2082
  {
1956
2083
  onClick: () => handleThreadClick(thread.id),
1957
2084
  className: cn(
1958
- "group relative flex items-center gap-3 p-3 rounded-lg cursor-pointer transition-colors",
1959
- isActive ? "bg-primary text-primary-foreground" : "hover:bg-muted"
2085
+ "group relative flex items-center gap-3 px-3 py-1.5 rounded-lg cursor-pointer transition-colors",
2086
+ isActive ? "bg-muted" : "hover:bg-muted"
1960
2087
  ),
1961
2088
  children: [
1962
2089
  /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
1963
- /* @__PURE__ */ jsx(
1964
- "p",
1965
- {
1966
- className: cn(
1967
- "text-sm font-medium truncate",
1968
- isActive && "text-primary-foreground"
1969
- ),
1970
- children: thread.title || `Thread ${thread.id.slice(0, 8)}`
1971
- }
1972
- ),
1973
- thread.updatedAt && /* @__PURE__ */ jsx(
1974
- "span",
1975
- {
1976
- className: cn(
1977
- "text-xs shrink-0",
1978
- isActive ? "text-primary-foreground/70" : "text-muted-foreground"
1979
- ),
1980
- children: formatDate(thread.updatedAt)
1981
- }
1982
- )
2090
+ /* @__PURE__ */ jsx("p", { className: cn("text-sm font-medium truncate"), children: thread.title || `Thread ${thread.id.slice(0, 8)}` }),
2091
+ thread.updatedAt && /* @__PURE__ */ jsx("span", { className: cn("text-xs shrink-0"), children: formatDate(thread.updatedAt) })
1983
2092
  ] }) }),
1984
2093
  /* @__PURE__ */ jsx(
1985
2094
  Button,
@@ -2005,7 +2114,8 @@ function ChatPopup({
2005
2114
  title = "Chat",
2006
2115
  placeholder = "Message the AI",
2007
2116
  starterPrompts,
2008
- defaultOpen = false
2117
+ defaultOpen = false,
2118
+ headerProps
2009
2119
  }) {
2010
2120
  const [isOpen, setIsOpen] = useState(defaultOpen);
2011
2121
  const [view, setView] = useState("chat");
@@ -2020,9 +2130,11 @@ function ChatPopup({
2020
2130
  };
2021
2131
  return /* @__PURE__ */ jsxs("div", { className: "fixed bottom-6 right-6 z-50 flex flex-col items-end gap-4 font-sans", children: [
2022
2132
  isOpen && /* @__PURE__ */ jsxs(Card, { className: "py-0 w-[440px] h-[640px] flex flex-col overflow-hidden border bg-background/95 backdrop-blur supports-backdrop-filter:bg-background/60 shadow-2xl animate-in fade-in zoom-in-95 duration-200 origin-bottom-right", children: [
2023
- /* @__PURE__ */ jsxs(CardHeader, { className: "p-4 border-b flex flex-row items-center justify-between space-y-0 h-14 shrink-0", children: [
2024
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2025
- view === "history" && /* @__PURE__ */ jsx(
2133
+ /* @__PURE__ */ jsx(
2134
+ ChatHeader,
2135
+ {
2136
+ title: view === "history" ? "History" : title,
2137
+ leftContent: view === "history" ? /* @__PURE__ */ jsx(
2026
2138
  Button,
2027
2139
  {
2028
2140
  variant: "ghost",
@@ -2031,44 +2143,44 @@ function ChatPopup({
2031
2143
  className: "text-muted-foreground hover:text-foreground",
2032
2144
  children: /* @__PURE__ */ jsx(IconArrowLeft, { className: "size-4" })
2033
2145
  }
2034
- ),
2035
- /* @__PURE__ */ jsx(CardTitle, { className: "text-sm font-semibold", children: view === "history" ? "History" : title })
2036
- ] }),
2037
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
2038
- view === "chat" && /* @__PURE__ */ jsx(
2039
- Button,
2040
- {
2041
- variant: "ghost",
2042
- size: "icon-xs",
2043
- onClick: () => setView("history"),
2044
- className: "text-muted-foreground hover:text-foreground",
2045
- title: "History",
2046
- children: /* @__PURE__ */ jsx(IconHistory, { className: "size-4" })
2047
- }
2048
- ),
2049
- /* @__PURE__ */ jsx(
2050
- Button,
2051
- {
2052
- variant: "ghost",
2053
- size: "icon-xs",
2054
- onClick: handleNewChat,
2055
- className: "text-muted-foreground hover:text-foreground",
2056
- title: "New Chat",
2057
- children: /* @__PURE__ */ jsx(IconPlus, { className: "size-4" })
2058
- }
2059
- ),
2060
- /* @__PURE__ */ jsx(
2061
- Button,
2062
- {
2063
- variant: "ghost",
2064
- size: "icon-xs",
2065
- onClick: () => setIsOpen(false),
2066
- className: "text-muted-foreground hover:text-foreground",
2067
- children: /* @__PURE__ */ jsx(IconX, { className: "size-4" })
2068
- }
2069
- )
2070
- ] })
2071
- ] }),
2146
+ ) : void 0,
2147
+ rightContent: /* @__PURE__ */ jsxs(Fragment, { children: [
2148
+ view === "chat" && /* @__PURE__ */ jsx(
2149
+ Button,
2150
+ {
2151
+ variant: "ghost",
2152
+ size: "icon-xs",
2153
+ onClick: () => setView("history"),
2154
+ className: "text-muted-foreground hover:text-foreground",
2155
+ title: "History",
2156
+ children: /* @__PURE__ */ jsx(IconHistory, { className: "size-4" })
2157
+ }
2158
+ ),
2159
+ /* @__PURE__ */ jsx(
2160
+ Button,
2161
+ {
2162
+ variant: "ghost",
2163
+ size: "icon-xs",
2164
+ onClick: handleNewChat,
2165
+ className: "text-muted-foreground hover:text-foreground",
2166
+ title: "New Chat",
2167
+ children: /* @__PURE__ */ jsx(IconPlus, { className: "size-4" })
2168
+ }
2169
+ ),
2170
+ /* @__PURE__ */ jsx(
2171
+ Button,
2172
+ {
2173
+ variant: "ghost",
2174
+ size: "icon-xs",
2175
+ onClick: () => setIsOpen(false),
2176
+ className: "text-muted-foreground hover:text-foreground",
2177
+ children: /* @__PURE__ */ jsx(IconX, { className: "size-4" })
2178
+ }
2179
+ )
2180
+ ] }),
2181
+ ...headerProps
2182
+ }
2183
+ ),
2072
2184
  /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-hidden", children: view === "chat" ? /* @__PURE__ */ jsx(
2073
2185
  Thread,
2074
2186
  {
@@ -2102,10 +2214,11 @@ function ChatSidebar({
2102
2214
  title = "Chat",
2103
2215
  placeholder = "Message the AI",
2104
2216
  starterPrompts,
2105
- className
2217
+ className,
2218
+ headerProps
2106
2219
  }) {
2107
2220
  return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col h-full border-r bg-background w-80", className), children: [
2108
- /* @__PURE__ */ jsx("div", { className: "p-4 border-b h-14 flex items-center shrink-0", children: /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold", children: title }) }),
2221
+ /* @__PURE__ */ jsx(ChatHeader, { title, ...headerProps }),
2109
2222
  /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-hidden", children: /* @__PURE__ */ jsx(
2110
2223
  Thread,
2111
2224
  {
@@ -2120,11 +2233,122 @@ function ChatFull({
2120
2233
  title = "Chat",
2121
2234
  placeholder = "Message the AI",
2122
2235
  starterPrompts,
2123
- className
2236
+ className,
2237
+ headerProps,
2238
+ leftSidebar,
2239
+ rightSidebar,
2240
+ leftSidebarClassName,
2241
+ rightSidebarClassName,
2242
+ leftSidebarCollapsible = false,
2243
+ rightSidebarCollapsible = false,
2244
+ defaultLeftSidebarCollapsed = false,
2245
+ defaultRightSidebarCollapsed = false,
2246
+ leftSidebarCollapsed: controlledLeftCollapsed,
2247
+ rightSidebarCollapsed: controlledRightCollapsed,
2248
+ onLeftSidebarCollapseChange,
2249
+ onRightSidebarCollapseChange
2124
2250
  }) {
2251
+ const [internalLeftCollapsed, setInternalLeftCollapsed] = useState(
2252
+ defaultLeftSidebarCollapsed
2253
+ );
2254
+ const [internalRightCollapsed, setInternalRightCollapsed] = useState(
2255
+ defaultRightSidebarCollapsed
2256
+ );
2257
+ const leftCollapsed = controlledLeftCollapsed !== void 0 ? controlledLeftCollapsed : internalLeftCollapsed;
2258
+ const rightCollapsed = controlledRightCollapsed !== void 0 ? controlledRightCollapsed : internalRightCollapsed;
2259
+ const handleLeftToggle = () => {
2260
+ const newCollapsed = !leftCollapsed;
2261
+ if (controlledLeftCollapsed === void 0) {
2262
+ setInternalLeftCollapsed(newCollapsed);
2263
+ }
2264
+ onLeftSidebarCollapseChange?.(newCollapsed);
2265
+ };
2266
+ const handleRightToggle = () => {
2267
+ const newCollapsed = !rightCollapsed;
2268
+ if (controlledRightCollapsed === void 0) {
2269
+ setInternalRightCollapsed(newCollapsed);
2270
+ }
2271
+ onRightSidebarCollapseChange?.(newCollapsed);
2272
+ };
2125
2273
  return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col h-full w-full bg-background", className), children: [
2126
- title && /* @__PURE__ */ jsx("div", { className: "p-4 border-b h-14 flex items-center shrink-0", children: /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold", children: title }) }),
2127
- /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-hidden", children: /* @__PURE__ */ jsx(Thread, { placeholder, starterPrompts }) })
2274
+ title && /* @__PURE__ */ jsx(ChatHeader, { title, ...headerProps }),
2275
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-hidden flex relative", children: [
2276
+ leftSidebar && /* @__PURE__ */ jsxs(Fragment, { children: [
2277
+ /* @__PURE__ */ jsx(
2278
+ "div",
2279
+ {
2280
+ className: cn(
2281
+ "flex-shrink-0 border-r border-border bg-background transition-all duration-300 ease-in-out overflow-hidden flex flex-col",
2282
+ leftCollapsed && leftSidebarCollapsible ? "w-0 border-r-0 min-w-0" : "",
2283
+ !leftCollapsed && leftSidebarClassName
2284
+ ),
2285
+ children: !leftCollapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
2286
+ leftSidebarCollapsible && /* @__PURE__ */ jsx("div", { className: "flex justify-end p-2 border-b border-border shrink-0", children: /* @__PURE__ */ jsx(
2287
+ Button,
2288
+ {
2289
+ variant: "ghost",
2290
+ size: "icon-sm",
2291
+ onClick: handleLeftToggle,
2292
+ "aria-label": "Collapse left sidebar",
2293
+ className: "h-8 w-8",
2294
+ children: /* @__PURE__ */ jsx(IconChevronLeft, { className: "h-4 w-4" })
2295
+ }
2296
+ ) }),
2297
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-hidden min-h-0", children: leftSidebar })
2298
+ ] })
2299
+ }
2300
+ ),
2301
+ leftSidebarCollapsible && leftCollapsed && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 border-r border-border bg-background flex items-center justify-center w-10", children: /* @__PURE__ */ jsx(
2302
+ Button,
2303
+ {
2304
+ variant: "ghost",
2305
+ size: "icon-sm",
2306
+ onClick: handleLeftToggle,
2307
+ "aria-label": "Expand left sidebar",
2308
+ className: "h-8 w-8",
2309
+ children: /* @__PURE__ */ jsx(IconChevronRight, { className: "h-4 w-4" })
2310
+ }
2311
+ ) })
2312
+ ] }),
2313
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-hidden min-w-0", children: /* @__PURE__ */ jsx(Thread, { placeholder, starterPrompts }) }),
2314
+ rightSidebar && /* @__PURE__ */ jsxs(Fragment, { children: [
2315
+ rightSidebarCollapsible && rightCollapsed && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 border-l border-border bg-background flex items-center justify-center w-10", children: /* @__PURE__ */ jsx(
2316
+ Button,
2317
+ {
2318
+ variant: "ghost",
2319
+ size: "icon-sm",
2320
+ onClick: handleRightToggle,
2321
+ "aria-label": "Expand right sidebar",
2322
+ className: "h-8 w-8",
2323
+ children: /* @__PURE__ */ jsx(IconChevronLeft, { className: "h-4 w-4" })
2324
+ }
2325
+ ) }),
2326
+ /* @__PURE__ */ jsx(
2327
+ "div",
2328
+ {
2329
+ className: cn(
2330
+ "flex-shrink-0 border-l border-border bg-background transition-all duration-300 ease-in-out overflow-hidden flex flex-col",
2331
+ rightCollapsed && rightSidebarCollapsible ? "w-0 border-l-0 min-w-0" : "",
2332
+ !rightCollapsed && rightSidebarClassName
2333
+ ),
2334
+ children: !rightCollapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
2335
+ rightSidebarCollapsible && /* @__PURE__ */ jsx("div", { className: "flex justify-start p-2 border-b border-border shrink-0", children: /* @__PURE__ */ jsx(
2336
+ Button,
2337
+ {
2338
+ variant: "ghost",
2339
+ size: "icon-sm",
2340
+ onClick: handleRightToggle,
2341
+ "aria-label": "Collapse right sidebar",
2342
+ className: "h-8 w-8",
2343
+ children: /* @__PURE__ */ jsx(IconChevronRight, { className: "h-4 w-4" })
2344
+ }
2345
+ ) }),
2346
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-hidden min-h-0", children: rightSidebar })
2347
+ ] })
2348
+ }
2349
+ )
2350
+ ] })
2351
+ ] })
2128
2352
  ] });
2129
2353
  }
2130
2354
  function AlertDialog({ ...props }) {
@@ -2268,8 +2492,8 @@ var AccountDialog = ({
2268
2492
  size
2269
2493
  }) => {
2270
2494
  const { isLoading, isAuthenticated, login, logout } = useAuth();
2271
- const [open, setOpen] = React9.useState(false);
2272
- const [error, setError] = React9.useState(null);
2495
+ const [open, setOpen] = React10.useState(false);
2496
+ const [error, setError] = React10.useState(null);
2273
2497
  const handleGoogleSignIn = async () => {
2274
2498
  login();
2275
2499
  };
@@ -2344,7 +2568,42 @@ var AccountDialog = ({
2344
2568
  ] }) })
2345
2569
  ] });
2346
2570
  };
2571
+ function ThemeToggle() {
2572
+ const { theme, setTheme, resolvedTheme } = useTheme();
2573
+ const cycleTheme = () => {
2574
+ if (theme === "light") {
2575
+ setTheme("dark");
2576
+ } else if (theme === "dark") {
2577
+ setTheme("system");
2578
+ } else {
2579
+ setTheme("light");
2580
+ }
2581
+ };
2582
+ const getIcon = () => {
2583
+ if (theme === "system") {
2584
+ return /* @__PURE__ */ jsx(IconDeviceDesktop, { className: "h-4 w-4" });
2585
+ }
2586
+ return resolvedTheme === "dark" ? /* @__PURE__ */ jsx(IconMoon, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(IconSun, { className: "h-4 w-4" });
2587
+ };
2588
+ const getLabel = () => {
2589
+ if (theme === "system") {
2590
+ return "System";
2591
+ }
2592
+ return resolvedTheme === "dark" ? "Dark" : "Light";
2593
+ };
2594
+ return /* @__PURE__ */ jsx(
2595
+ Button,
2596
+ {
2597
+ variant: "ghost",
2598
+ size: "icon",
2599
+ onClick: cycleTheme,
2600
+ "aria-label": `Toggle theme (current: ${getLabel()})`,
2601
+ title: `Current: ${getLabel()}. Click to cycle: Light \u2192 Dark \u2192 System`,
2602
+ children: getIcon()
2603
+ }
2604
+ );
2605
+ }
2347
2606
 
2348
- export { AccountDialog, AuthContext, AuthProvider, ChatFull, ChatPopup, ChatSidebar, Composer, MelonyClientProvider, MelonyContext, Thread, ThreadContext, ThreadList, ThreadProvider, UIRenderer, useAuth, useMelony, useThreads };
2607
+ export { AccountDialog, AuthContext, AuthProvider, ChatFull, ChatHeader, ChatPopup, ChatSidebar, Composer, MelonyClientProvider, MelonyContext, ThemeProvider, ThemeToggle, Thread, ThreadContext, ThreadList, ThreadProvider, UIRenderer, groupEventsToMessages, useAuth, useMelony, useTheme, useThreads };
2349
2608
  //# sourceMappingURL=index.js.map
2350
2609
  //# sourceMappingURL=index.js.map