@timbal-ai/timbal-react 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -868,6 +868,7 @@ function findParentIdFromAuiParent(messages, auiParentId) {
868
868
  import { parseSSELine as parseSSELine2 } from "@timbal-ai/timbal-sdk";
869
869
 
870
870
  // src/components/thread.tsx
871
+ import { useEffect as useEffect5 } from "react";
871
872
  import {
872
873
  ActionBarMorePrimitive,
873
874
  ActionBarPrimitive,
@@ -1108,7 +1109,7 @@ import { forwardRef as forwardRef2 } from "react";
1108
1109
  import * as React from "react";
1109
1110
  import { Slot } from "radix-ui";
1110
1111
 
1111
- // src/ui/timbal-v2-button-tokens.ts
1112
+ // src/design/button-tokens.ts
1112
1113
  var TIMBAL_V2_SIZE_HEIGHT = {
1113
1114
  xs: "min-h-8 h-8",
1114
1115
  sm: "min-h-9 h-9",
@@ -1128,39 +1129,62 @@ var TIMBAL_V2_SIZE_LABEL_PX = {
1128
1129
  lg: "px-6"
1129
1130
  };
1130
1131
  var TIMBAL_V2_FILL = {
1131
- primary: "bg-gradient-to-b from-neutral-800 to-black group-hover/tbv2:from-neutral-700 group-hover/tbv2:to-neutral-900 group-active/tbv2:from-black group-active/tbv2:to-black dark:from-white dark:to-neutral-200 dark:group-hover/tbv2:from-white dark:group-hover/tbv2:to-neutral-100 dark:group-active/tbv2:from-neutral-200 dark:group-active/tbv2:to-neutral-400",
1132
- informative: "bg-blue-600 group-active/tbv2:[background-image:linear-gradient(to_top,rgba(0,0,0,0.08),transparent_55%)]",
1133
- destructive: "bg-gradient-to-b from-white to-neutral-50/75 group-hover/tbv2:from-red-50/90 group-hover/tbv2:to-red-100/70 group-active/tbv2:from-red-100/90 group-active/tbv2:to-red-200/65 dark:from-white/[0.05] dark:to-white/[0.025] dark:group-hover/tbv2:from-red-500/12 dark:group-hover/tbv2:to-red-500/8 dark:group-active/tbv2:from-red-500/20 dark:group-active/tbv2:to-red-500/12",
1134
- secondary: "bg-gradient-to-b from-white to-neutral-50/70 group-hover/tbv2:from-neutral-50/50 group-hover/tbv2:to-neutral-100/65 group-active/tbv2:from-neutral-100/70 group-active/tbv2:to-neutral-200/65 dark:from-white/[0.05] dark:to-white/[0.025] dark:group-hover/tbv2:from-white/[0.07] dark:group-hover/tbv2:to-white/[0.045] dark:group-active/tbv2:from-white/[0.10] dark:group-active/tbv2:to-white/[0.07]",
1135
- ghost: "bg-transparent group-hover/tbv2:bg-neutral-100/70 group-active/tbv2:bg-neutral-200/70 dark:group-hover/tbv2:bg-white/10 dark:group-active/tbv2:bg-white/15",
1132
+ primary: [
1133
+ "bg-gradient-to-b from-primary-fill-from to-primary-fill-to",
1134
+ "group-hover/tbv2:from-primary-fill-hover-from group-hover/tbv2:to-primary-fill-hover-to",
1135
+ "group-active/tbv2:from-primary-fill-active-from group-active/tbv2:to-primary-fill-active-to"
1136
+ ].join(" "),
1137
+ informative: [
1138
+ "bg-primary",
1139
+ "group-active/tbv2:[background-image:linear-gradient(to_top,rgba(0,0,0,0.08),transparent_55%)]"
1140
+ ].join(" "),
1141
+ destructive: [
1142
+ "bg-gradient-to-b from-elevated-from to-elevated-to",
1143
+ "group-hover/tbv2:from-destructive-fill-hover-from group-hover/tbv2:to-destructive-fill-hover-to",
1144
+ "group-active/tbv2:from-destructive-fill-active-from group-active/tbv2:to-destructive-fill-active-to"
1145
+ ].join(" "),
1146
+ secondary: [
1147
+ "bg-gradient-to-b from-elevated-from to-elevated-to",
1148
+ "group-hover/tbv2:from-secondary-fill-hover-from group-hover/tbv2:to-secondary-fill-hover-to",
1149
+ "group-active/tbv2:from-secondary-fill-active-from group-active/tbv2:to-secondary-fill-active-to"
1150
+ ].join(" "),
1151
+ ghost: [
1152
+ "bg-transparent",
1153
+ "group-hover/tbv2:bg-ghost-fill-hover",
1154
+ "group-active/tbv2:bg-ghost-fill-active"
1155
+ ].join(" "),
1136
1156
  link: "bg-transparent"
1137
1157
  };
1138
1158
  var TIMBAL_V2_LABEL = {
1139
- primary: "text-white dark:text-neutral-900",
1140
- informative: "text-white",
1141
- destructive: "text-destructive dark:text-red-400",
1159
+ primary: "text-primary-foreground",
1160
+ informative: "text-primary-foreground",
1161
+ destructive: "text-destructive",
1142
1162
  secondary: "text-foreground",
1143
1163
  ghost: "text-foreground",
1144
- link: "text-foreground underline decoration-black/25 underline-offset-2 group-hover/tbv2:decoration-black/45 dark:decoration-white/25 dark:group-hover/tbv2:decoration-white/45"
1164
+ link: "text-foreground underline decoration-foreground/25 underline-offset-2 group-hover/tbv2:decoration-foreground/45"
1145
1165
  };
1146
1166
  var TIMBAL_V2_BORDER = {
1147
1167
  primary: "",
1148
- informative: "border border-white/15 dark:border-white/10",
1149
- destructive: "border border-destructive/45 dark:border-red-500/55",
1150
- secondary: "border border-neutral-200/80 dark:border-white/[0.08]",
1168
+ informative: "border border-foreground/15",
1169
+ destructive: "border border-destructive/45",
1170
+ secondary: "border border-border",
1151
1171
  ghost: "",
1152
1172
  link: ""
1153
1173
  };
1154
1174
  var TIMBAL_V2_SHADOW = {
1155
- primary: "shadow-sm shadow-black/15 dark:shadow-black/40",
1156
- informative: "shadow-sm shadow-blue-900/20 dark:shadow-black/40",
1157
- destructive: "shadow-[0_1px_2px_-0.5px_rgba(0,0,0,0.05)] dark:shadow-[0_1px_3px_rgba(0,0,0,0.22)]",
1158
- secondary: "shadow-[0_1px_2px_-0.5px_rgba(0,0,0,0.05)] dark:shadow-[0_1px_3px_rgba(0,0,0,0.22)]",
1175
+ primary: "shadow-card",
1176
+ informative: "shadow-card",
1177
+ destructive: "shadow-card",
1178
+ secondary: "shadow-card",
1159
1179
  ghost: "",
1160
1180
  link: ""
1161
1181
  };
1162
- var TIMBAL_V2_PILL_SURFACE = "bg-gradient-to-b from-white to-neutral-50/70 border border-neutral-200/80 shadow-[0_1px_2px_-0.5px_rgba(0,0,0,0.05)] dark:from-white/[0.05] dark:to-white/[0.025] dark:border-white/[0.08] dark:shadow-[0_1px_3px_rgba(0,0,0,0.22)]";
1163
- var TIMBAL_V2_SECONDARY_CHROME = "bg-gradient-to-b from-white to-neutral-50/70 border border-neutral-200/80 shadow-[0_1px_2px_-0.5px_rgba(0,0,0,0.05)] transition-[background-color,box-shadow,border-color] duration-200 ease-in-out hover:from-neutral-50/40 hover:to-neutral-100/60 active:from-neutral-100/65 active:to-neutral-200/65 dark:from-white/[0.05] dark:to-white/[0.025] dark:border-white/[0.08] dark:shadow-[0_1px_3px_rgba(0,0,0,0.22)] dark:hover:from-white/[0.07] dark:hover:to-white/[0.045] dark:active:from-white/[0.10] dark:active:to-white/[0.07]";
1182
+ var TIMBAL_V2_SECONDARY_CHROME = [
1183
+ "bg-gradient-to-b from-elevated-from to-elevated-to border border-border shadow-card",
1184
+ "transition-[background-color,box-shadow,border-color] duration-200 ease-in-out",
1185
+ "hover:from-secondary-fill-hover-from hover:to-secondary-fill-hover-to",
1186
+ "active:from-secondary-fill-active-from active:to-secondary-fill-active-to"
1187
+ ].join(" ");
1164
1188
 
1165
1189
  // src/ui/timbal-v2-button.tsx
1166
1190
  import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -1192,7 +1216,7 @@ var TimbalV2Button = React.forwardRef(function TimbalV2Button2({
1192
1216
  "data-variant": variant,
1193
1217
  className: cn(
1194
1218
  "group/tbv2 relative box-border inline-flex flex-col items-stretch overflow-hidden border-0 bg-transparent p-0 text-sm font-normal shadow-none transition duration-200 ease-in-out",
1195
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-400/60 focus-visible:ring-offset-1 focus-visible:ring-offset-background",
1219
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/60 focus-visible:ring-offset-1 focus-visible:ring-offset-background",
1196
1220
  sizeClass,
1197
1221
  radiusClass,
1198
1222
  TIMBAL_V2_BORDER[variant],
@@ -1369,9 +1393,9 @@ var AttachmentRemove = () => {
1369
1393
  TooltipIconButton,
1370
1394
  {
1371
1395
  tooltip: "Remove file",
1372
- className: "aui-attachment-tile-remove absolute top-1.5 right-1.5 size-3.5 rounded-full bg-white text-muted-foreground opacity-100 shadow-sm hover:bg-white! [&_svg]:text-black hover:[&_svg]:text-destructive",
1396
+ className: "aui-attachment-tile-remove absolute top-1.5 right-1.5 size-3.5 rounded-full bg-card text-foreground opacity-100 shadow-card hover:bg-card! [&_svg]:text-foreground hover:[&_svg]:text-destructive",
1373
1397
  side: "top",
1374
- children: /* @__PURE__ */ jsx7(XIcon2, { className: "aui-attachment-remove-icon size-3 dark:stroke-[2.5px]" })
1398
+ children: /* @__PURE__ */ jsx7(XIcon2, { className: "aui-attachment-remove-icon size-3" })
1375
1399
  }
1376
1400
  ) });
1377
1401
  };
@@ -1393,7 +1417,7 @@ var ComposerAddAttachment = () => {
1393
1417
  tooltip: "Add Attachment",
1394
1418
  side: "bottom",
1395
1419
  variant: "secondary",
1396
- className: "aui-composer-add-attachment shrink-0 text-neutral-500 dark:text-muted-foreground",
1420
+ className: "aui-composer-add-attachment shrink-0 text-muted-foreground",
1397
1421
  "aria-label": "Add Attachment",
1398
1422
  children: /* @__PURE__ */ jsx7(PlusIcon, { className: "aui-attachment-add-icon size-4 stroke-[1.5]" })
1399
1423
  }
@@ -1747,30 +1771,19 @@ import { useCallback as useCallback2, useState as useState3 } from "react";
1747
1771
  import { useThreadRuntime } from "@assistant-ui/react";
1748
1772
  import { CheckIcon } from "lucide-react";
1749
1773
 
1750
- // src/ui/chrome.ts
1751
- var STUDIO_TOPBAR_GAP = "0.5rem";
1752
- var STUDIO_TOPBAR_HEIGHT = "3rem";
1753
- var STUDIO_PILL_HEIGHT = "2.5rem";
1754
- var STUDIO_SIDEBAR_GAP = "0.5rem";
1755
- var STUDIO_SIDEBAR_WIDTH = "3rem";
1756
- var STUDIO_INSET_LEFT = `calc(${STUDIO_SIDEBAR_GAP} + ${STUDIO_SIDEBAR_WIDTH})`;
1757
- var studioChromeShellStyle = {
1758
- "--studio-topbar-gap": STUDIO_TOPBAR_GAP,
1759
- "--studio-topbar-height": STUDIO_TOPBAR_HEIGHT,
1760
- "--studio-chrome-pill-height": STUDIO_PILL_HEIGHT,
1761
- "--studio-inset-top": `calc(${STUDIO_TOPBAR_GAP} + ${STUDIO_TOPBAR_HEIGHT})`,
1762
- "--studio-sidebar-gap": STUDIO_SIDEBAR_GAP,
1763
- "--studio-sidebar-width": STUDIO_SIDEBAR_WIDTH,
1764
- "--studio-inset-left": STUDIO_INSET_LEFT
1765
- };
1774
+ // src/design/classes.ts
1766
1775
  var studioTopbarPillHeightClass = "h-[var(--studio-chrome-pill-height)] min-h-[var(--studio-chrome-pill-height)]";
1767
1776
  var studioTopbarIconPillClass = "shrink-0 flex-none size-[var(--studio-chrome-pill-height)] min-h-[var(--studio-chrome-pill-height)] min-w-[var(--studio-chrome-pill-height)]";
1768
- var studioPlaygroundGradientClass = "bg-gradient-to-b from-neutral-200/60 via-neutral-100/30 to-background dark:from-zinc-800 dark:via-zinc-900 dark:to-zinc-950";
1769
- var studioComposeInputShellClass = "flex w-full flex-col rounded-2xl border border-neutral-200/60 bg-background shadow-lg shadow-black/5 outline-none transition-[box-shadow,border-color] focus-within:border-neutral-400/80 focus-within:ring-2 focus-within:ring-foreground/5 focus-within:shadow-xl focus-within:shadow-black/10 dark:border-white/12 dark:bg-zinc-900 dark:shadow-black/20 dark:focus-within:border-white/22 dark:focus-within:ring-0";
1770
- var studioPillSurfaceClass = TIMBAL_V2_PILL_SURFACE;
1777
+ var studioPlaygroundGradientClass = "bg-gradient-to-b from-playground-from via-playground-via to-playground-to";
1778
+ var studioComposeInputShellClass = cn(
1779
+ "flex w-full flex-col rounded-2xl bg-composer-bg shadow-card-elevated outline-none",
1780
+ "border border-composer-border",
1781
+ "transition-[box-shadow,border-color]",
1782
+ "focus-within:border-composer-border-focus focus-within:ring-2 focus-within:ring-foreground/5"
1783
+ );
1771
1784
  var studioSecondaryChromeClass = TIMBAL_V2_SECONDARY_CHROME;
1772
- var studioIntegrationSurfaceSolid = "bg-white bg-gradient-to-b from-white to-neutral-50/70 shadow-[0_1px_2px_-0.5px_rgba(0,0,0,0.05)] dark:bg-zinc-900 dark:from-white/[0.05] dark:to-white/[0.025] dark:shadow-[0_1px_3px_rgba(0,0,0,0.22)]";
1773
- var studioIntegrationBorder = "border border-neutral-200/80 dark:border-white/[0.08]";
1785
+ var studioIntegrationSurfaceSolid = "bg-gradient-to-b from-elevated-from to-elevated-to shadow-card";
1786
+ var studioIntegrationBorder = "border border-border";
1774
1787
  var studioIntegrationCardClass = cn(
1775
1788
  "rounded-xl",
1776
1789
  studioIntegrationSurfaceSolid,
@@ -1785,8 +1798,8 @@ var studioListRowButtonClass = cn(
1785
1798
  "flex w-full cursor-pointer items-center gap-3 rounded-xl px-3 py-2.5 text-left",
1786
1799
  studioIntegrationCardClass,
1787
1800
  "transition-[background-color,box-shadow,border-color] duration-200 ease-in-out",
1788
- "hover:border-neutral-300 dark:hover:border-white/15",
1789
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15 focus-visible:ring-offset-2 focus-visible:ring-offset-background dark:focus-visible:ring-white/20"
1801
+ "hover:border-foreground/20",
1802
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15 focus-visible:ring-offset-2 focus-visible:ring-offset-background"
1790
1803
  );
1791
1804
  var studioComposerIoWellClass = cn(
1792
1805
  "rounded-lg",
@@ -1797,6 +1810,43 @@ var studioToolCardShellClass = cn(
1797
1810
  studioIntegrationCardClass,
1798
1811
  "my-2 min-h-0 overflow-hidden"
1799
1812
  );
1813
+ var studioSidebarPanelClass = cn(
1814
+ "bg-sidebar text-sidebar-foreground",
1815
+ "border border-sidebar-border",
1816
+ "shadow-card-elevated"
1817
+ );
1818
+ var studioSidebarNavItemClass = cn(
1819
+ "flex items-center rounded-lg text-sm",
1820
+ "transition-[color,background-color,box-shadow,border-color] duration-200 ease-in-out",
1821
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15 focus-visible:ring-offset-2"
1822
+ );
1823
+ function studioSidebarNavItemLayout(iconOnly) {
1824
+ return iconOnly ? "box-border size-8 min-h-8 min-w-8 shrink-0 justify-center rounded-lg p-0 focus-visible:ring-offset-0" : "w-full gap-2 px-2.5 py-2";
1825
+ }
1826
+ var studioSidebarNavItemSurfaceClass = cn(
1827
+ "bg-gradient-to-b from-elevated-from to-elevated-to text-foreground",
1828
+ "border border-border",
1829
+ "shadow-card"
1830
+ );
1831
+ var studioSidebarNavItemIdleClass = cn(
1832
+ "border border-transparent text-muted-foreground shadow-none",
1833
+ "hover:text-foreground",
1834
+ "hover:bg-gradient-to-b hover:from-elevated-from hover:to-elevated-to",
1835
+ "hover:border-border hover:shadow-card"
1836
+ );
1837
+ var studioSidebarCollapsedRailItemClass = cn(
1838
+ "border border-border shadow-card bg-sidebar-accent"
1839
+ );
1840
+ var studioSidebarCollapsedRailItemIdleClass = cn(
1841
+ studioSidebarCollapsedRailItemClass,
1842
+ "text-muted-foreground hover:text-foreground"
1843
+ );
1844
+ var studioSidebarCollapsedRailItemActiveClass = cn(
1845
+ studioSidebarCollapsedRailItemClass,
1846
+ studioSidebarNavItemSurfaceClass,
1847
+ "text-foreground"
1848
+ );
1849
+ var studioSidebarNavItemActiveClass = studioSidebarNavItemSurfaceClass;
1800
1850
  var studioTimelineRowButtonClass = "group flex w-full min-w-0 cursor-pointer items-center justify-start rounded-md border-0 bg-transparent py-1 text-left shadow-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15 focus-visible:ring-offset-2";
1801
1851
  var studioTimelineTextClass = "text-xs font-normal leading-snug";
1802
1852
  var studioTimelineActionClass = cn(
@@ -1822,10 +1872,10 @@ var studioArtifactShellClass = cn(
1822
1872
  studioIntegrationCardClass,
1823
1873
  "my-2 w-full min-w-0 overflow-hidden"
1824
1874
  );
1825
- var studioQuestionOptionClass = "flex w-full items-center gap-2 rounded-lg border border-transparent px-2 py-1.5 text-left text-sm transition-[background-color,border-color,box-shadow] duration-200 hover:bg-neutral-100/80 dark:hover:bg-white/[0.05] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15 focus-visible:ring-offset-2";
1875
+ var studioQuestionOptionClass = "flex w-full items-center gap-2 rounded-lg border border-transparent px-2 py-1.5 text-left text-sm transition-[background-color,border-color,box-shadow] duration-200 hover:bg-muted/70 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15 focus-visible:ring-offset-2";
1826
1876
  var studioQuestionOptionSelectedClass = cn(
1827
1877
  studioQuestionOptionClass,
1828
- "border-neutral-200/80 bg-neutral-50/90 ring-1 ring-foreground/10 dark:border-white/[0.12] dark:bg-white/[0.06] dark:ring-white/10"
1878
+ "border-border bg-accent ring-1 ring-foreground/10"
1829
1879
  );
1830
1880
 
1831
1881
  // src/artifacts/question-artifact.tsx
@@ -1839,7 +1889,7 @@ var OptionRadio = ({ selected }) => /* @__PURE__ */ jsx10(
1839
1889
  {
1840
1890
  className: cn(
1841
1891
  "flex size-4 shrink-0 items-center justify-center rounded-full border-2 transition-colors",
1842
- selected ? "border-foreground bg-foreground text-background" : "border-neutral-300 bg-background dark:border-white/20"
1892
+ selected ? "border-foreground bg-foreground text-background" : "border-border bg-background"
1843
1893
  ),
1844
1894
  "aria-hidden": true,
1845
1895
  children: selected ? /* @__PURE__ */ jsx10(CheckIcon, { className: "size-2.5 stroke-[3]" }) : null
@@ -2098,15 +2148,15 @@ import { cva } from "class-variance-authority";
2098
2148
  import { Slot as Slot2 } from "radix-ui";
2099
2149
  import { jsx as jsx15 } from "react/jsx-runtime";
2100
2150
  var buttonVariants = cva(
2101
- "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",
2151
+ "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/30 aria-invalid:border-destructive",
2102
2152
  {
2103
2153
  variants: {
2104
2154
  variant: {
2105
2155
  default: "bg-primary text-primary-foreground hover:bg-primary/90",
2106
- destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
2107
- 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",
2156
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90 focus-visible:ring-destructive/30",
2157
+ outline: "border border-border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
2108
2158
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
2109
- ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
2159
+ ghost: "hover:bg-accent hover:text-accent-foreground",
2110
2160
  link: "text-primary underline-offset-4 hover:underline"
2111
2161
  },
2112
2162
  size: {
@@ -2738,7 +2788,7 @@ var CodeHeader = ({ language, code }) => {
2738
2788
  if (!code || isCopied) return;
2739
2789
  copyToClipboard(code);
2740
2790
  };
2741
- return /* @__PURE__ */ jsxs11("div", { className: "aui-code-header flex items-center justify-between rounded-t-lg border border-b-0 border-border/50 bg-zinc-100 px-4 py-2 dark:bg-zinc-800/80", children: [
2791
+ return /* @__PURE__ */ jsxs11("div", { className: "aui-code-header flex items-center justify-between rounded-t-lg border border-b-0 border-border/50 bg-code-header-bg px-4 py-2", children: [
2742
2792
  /* @__PURE__ */ jsxs11("span", { className: "flex items-center gap-2 text-xs font-semibold tracking-wide text-muted-foreground/80 uppercase", children: [
2743
2793
  /* @__PURE__ */ jsx20("span", { className: "inline-block h-2 w-2 rounded-full bg-primary/40" }),
2744
2794
  language
@@ -2945,7 +2995,7 @@ var defaultComponents = memoizeMarkdownComponents({
2945
2995
  "pre",
2946
2996
  {
2947
2997
  className: cn(
2948
- "aui-md-pre overflow-x-auto rounded-t-none rounded-b-lg border border-t-0 border-border/50 bg-zinc-50 p-4 text-[13px] leading-relaxed dark:bg-zinc-900/80",
2998
+ "aui-md-pre overflow-x-auto rounded-t-none rounded-b-lg border border-t-0 border-border/50 bg-code-block-bg p-4 text-[13px] leading-relaxed",
2949
2999
  className
2950
3000
  ),
2951
3001
  ...props
@@ -2957,7 +3007,7 @@ var defaultComponents = memoizeMarkdownComponents({
2957
3007
  "code",
2958
3008
  {
2959
3009
  className: cn(
2960
- !isCodeBlock && "aui-md-inline-code rounded-[5px] border border-border/60 bg-muted/60 px-[0.4em] py-[0.15em] font-mono text-[0.85em] font-medium text-foreground/90 dark:bg-muted/40",
3010
+ !isCodeBlock && "aui-md-inline-code rounded-[5px] border border-border/60 bg-muted/60 px-[0.4em] py-[0.15em] font-mono text-[0.85em] font-medium text-foreground/90",
2961
3011
  className
2962
3012
  ),
2963
3013
  ...props
@@ -3382,7 +3432,7 @@ var ComposerInput = ({
3382
3432
  ComposerPrimitive2.Input,
3383
3433
  {
3384
3434
  placeholder,
3385
- className: "aui-composer-input max-h-60 min-h-14 w-full resize-none bg-transparent px-3 pt-3 pb-1 text-sm outline-none placeholder:text-neutral-400 focus-visible:ring-0 dark:placeholder:text-neutral-500",
3435
+ className: "aui-composer-input max-h-60 min-h-14 w-full resize-none bg-transparent px-3 pt-3 pb-1 text-sm outline-none placeholder:text-muted-foreground/70 focus-visible:ring-0",
3386
3436
  rows: 1,
3387
3437
  autoFocus,
3388
3438
  "aria-label": "Message input",
@@ -3468,9 +3518,9 @@ var SuggestionRow = ({ suggestion }) => {
3468
3518
  onClick,
3469
3519
  className: cn("aui-thread-suggestion", studioListRowButtonClass),
3470
3520
  children: [
3471
- /* @__PURE__ */ jsx26("span", { className: "aui-thread-suggestion-icon shrink-0 text-neutral-500 dark:text-muted-foreground", children: suggestion.icon ?? /* @__PURE__ */ jsx26(ArrowUpIcon2, { className: "size-4", strokeWidth: 1.75, "aria-hidden": true }) }),
3521
+ /* @__PURE__ */ jsx26("span", { className: "aui-thread-suggestion-icon shrink-0 text-muted-foreground", children: suggestion.icon ?? /* @__PURE__ */ jsx26(ArrowUpIcon2, { className: "size-4", strokeWidth: 1.75, "aria-hidden": true }) }),
3472
3522
  /* @__PURE__ */ jsxs14("span", { className: "aui-thread-suggestion-text min-w-0 flex-1 text-left", children: [
3473
- /* @__PURE__ */ jsx26("span", { className: "aui-thread-suggestion-text-1 block truncate text-sm font-normal text-foreground dark:text-foreground/95", children: suggestion.title }),
3523
+ /* @__PURE__ */ jsx26("span", { className: "aui-thread-suggestion-text-1 block truncate text-sm font-normal text-foreground", children: suggestion.title }),
3474
3524
  suggestion.description && /* @__PURE__ */ jsx26("span", { className: "aui-thread-suggestion-text-2 mt-0.5 block truncate text-xs text-muted-foreground", children: suggestion.description })
3475
3525
  ] })
3476
3526
  ]
@@ -3503,6 +3553,75 @@ function useResolvedSuggestions(source) {
3503
3553
  return useMemo6(() => resolved, [resolved]);
3504
3554
  }
3505
3555
 
3556
+ // src/design/theme-sanity.ts
3557
+ var scheduled = false;
3558
+ var warned = false;
3559
+ function isDev() {
3560
+ if (typeof process !== "undefined" && process.env?.NODE_ENV === "production") {
3561
+ return false;
3562
+ }
3563
+ return true;
3564
+ }
3565
+ function parseLuminance(color) {
3566
+ const value = color.trim();
3567
+ if (!value) return null;
3568
+ const oklch = value.match(/oklch\(\s*([0-9.]+)/i);
3569
+ if (oklch) {
3570
+ const lightness = Number.parseFloat(oklch[1]);
3571
+ if (Number.isFinite(lightness)) return lightness;
3572
+ }
3573
+ const rgb = value.match(/rgba?\(\s*([0-9.]+)[\s,]+([0-9.]+)[\s,]+([0-9.]+)/i);
3574
+ if (rgb) {
3575
+ const r = Number.parseFloat(rgb[1]) / 255;
3576
+ const g = Number.parseFloat(rgb[2]) / 255;
3577
+ const b = Number.parseFloat(rgb[3]) / 255;
3578
+ if ([r, g, b].every(Number.isFinite)) {
3579
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
3580
+ }
3581
+ }
3582
+ const hsl = value.match(/hsla?\(\s*[0-9.]+[\s,]+[0-9.]+%[\s,]+([0-9.]+)%/i);
3583
+ if (hsl) {
3584
+ const lightness = Number.parseFloat(hsl[1]) / 100;
3585
+ if (Number.isFinite(lightness)) return lightness;
3586
+ }
3587
+ return null;
3588
+ }
3589
+ function runCheck() {
3590
+ if (warned) return;
3591
+ if (typeof window === "undefined" || typeof document === "undefined") return;
3592
+ const root = document.documentElement;
3593
+ const styles = window.getComputedStyle(root);
3594
+ const background = styles.getPropertyValue("--background").trim();
3595
+ if (!background) {
3596
+ warned = true;
3597
+ console.warn(
3598
+ '[@timbal-ai/timbal-react] No `--background` CSS variable found on `<html>`. Did you `import "@timbal-ai/timbal-react/styles.css"` in your app entry? Components rely on semantic tokens (bg-background, text-foreground, \u2026) and will fall back to browser defaults.'
3599
+ );
3600
+ return;
3601
+ }
3602
+ const luminance = parseLuminance(background);
3603
+ if (luminance === null) return;
3604
+ const hasDarkClass = root.classList.contains("dark");
3605
+ const looksDark = luminance < 0.5;
3606
+ if (hasDarkClass !== looksDark) {
3607
+ warned = true;
3608
+ console.warn(
3609
+ `[@timbal-ai/timbal-react] Theme mismatch detected. \`<html>\` has${hasDarkClass ? "" : " no"} \`.dark\` class but the resolved \`--background\` is ${looksDark ? "dark" : "light"} (${background}). This usually means the consumer's CSS overrides \`--background\` only in one mode. Import \`@timbal-ai/timbal-react/styles.css\` for a complete light + dark token set, or declare matching \`:root\` AND \`.dark\` blocks in your own CSS.`
3610
+ );
3611
+ }
3612
+ }
3613
+ function scheduleThemeSanityCheck() {
3614
+ if (scheduled) return;
3615
+ if (!isDev()) return;
3616
+ if (typeof window === "undefined") return;
3617
+ scheduled = true;
3618
+ if (typeof queueMicrotask === "function") {
3619
+ queueMicrotask(() => setTimeout(runCheck, 0));
3620
+ } else {
3621
+ setTimeout(runCheck, 0);
3622
+ }
3623
+ }
3624
+
3506
3625
  // src/components/thread.tsx
3507
3626
  import { jsx as jsx27, jsxs as jsxs15 } from "react/jsx-runtime";
3508
3627
  var Thread = ({
@@ -3522,6 +3641,9 @@ var Thread = ({
3522
3641
  const EditComposerSlot = components?.EditComposer ?? EditComposer;
3523
3642
  const ScrollToBottomSlot = components?.ScrollToBottom ?? ThreadScrollToBottom;
3524
3643
  const SuggestionsSlot = components?.Suggestions ?? Suggestions;
3644
+ useEffect5(() => {
3645
+ scheduleThemeSanityCheck();
3646
+ }, []);
3525
3647
  return /* @__PURE__ */ jsx27(
3526
3648
  ArtifactRegistryProvider,
3527
3649
  {
@@ -3560,7 +3682,7 @@ var Thread = ({
3560
3682
  }
3561
3683
  }
3562
3684
  ),
3563
- /* @__PURE__ */ jsxs15(ThreadPrimitive.ViewportFooter, { className: "aui-thread-viewport-footer sticky bottom-0 mx-auto mt-auto flex w-full max-w-(--thread-max-width) flex-col gap-4 overflow-visible rounded-t-3xl bg-background pb-4 md:pb-6", children: [
3685
+ /* @__PURE__ */ jsxs15(ThreadPrimitive.ViewportFooter, { className: "aui-thread-viewport-footer sticky bottom-0 mx-auto mt-auto flex w-full max-w-(--thread-max-width) flex-col gap-4 overflow-visible bg-transparent pb-4 md:pb-6", children: [
3564
3686
  /* @__PURE__ */ jsx27(ScrollToBottomSlot, {}),
3565
3687
  /* @__PURE__ */ jsx27(ComposerSlot, { placeholder: composerPlaceholder })
3566
3688
  ] })
@@ -3646,7 +3768,7 @@ var ThreadWelcome = ({
3646
3768
  ] });
3647
3769
  };
3648
3770
  var MessageError = () => {
3649
- return /* @__PURE__ */ jsx27(MessagePrimitive2.Error, { children: /* @__PURE__ */ jsx27(ErrorPrimitive.Root, { className: "aui-message-error-root mt-2 rounded-md border border-destructive bg-destructive/10 p-3 text-destructive text-sm dark:bg-destructive/5 dark:text-red-200", children: /* @__PURE__ */ jsx27(ErrorPrimitive.Message, { className: "aui-message-error-message line-clamp-2" }) }) });
3771
+ return /* @__PURE__ */ jsx27(MessagePrimitive2.Error, { children: /* @__PURE__ */ jsx27(ErrorPrimitive.Root, { className: "aui-message-error-root mt-2 rounded-md border border-destructive bg-destructive/10 p-3 text-destructive text-sm", children: /* @__PURE__ */ jsx27(ErrorPrimitive.Message, { className: "aui-message-error-message line-clamp-2" }) }) });
3650
3772
  };
3651
3773
  var AssistantMessage = () => {
3652
3774
  return /* @__PURE__ */ jsxs15(
@@ -3679,8 +3801,7 @@ var ASSISTANT_ACTION_ICON_CLASS = cn(
3679
3801
  // The v2 fill span sits inside `group/tbv2 > span:first-child`. We mute it
3680
3802
  // here so action-bar buttons read as subtle icons rather than full pills.
3681
3803
  "[&>span:first-child]:bg-transparent",
3682
- "[&>span:first-child]:group-hover/tbv2:bg-neutral-100/50",
3683
- "dark:[&>span:first-child]:group-hover/tbv2:bg-white/8"
3804
+ "[&>span:first-child]:group-hover/tbv2:bg-muted/70"
3684
3805
  );
3685
3806
  var AssistantActionBar = () => {
3686
3807
  return /* @__PURE__ */ jsxs15(
@@ -3729,8 +3850,8 @@ var AssistantActionBar = () => {
3729
3850
  {
3730
3851
  side: "bottom",
3731
3852
  align: "start",
3732
- className: "aui-action-bar-more-content z-50 min-w-36 overflow-hidden rounded-lg border border-neutral-200 bg-white p-1 text-foreground shadow-md dark:border-white/10 dark:bg-zinc-900",
3733
- children: /* @__PURE__ */ jsx27(ActionBarPrimitive.ExportMarkdown, { asChild: true, children: /* @__PURE__ */ jsxs15(ActionBarMorePrimitive.Item, { className: "aui-action-bar-more-item flex cursor-pointer select-none items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none hover:bg-neutral-100 focus:bg-neutral-100 dark:hover:bg-zinc-800 dark:focus:bg-zinc-800", children: [
3853
+ className: "aui-action-bar-more-content z-50 min-w-36 overflow-hidden rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-card-elevated",
3854
+ children: /* @__PURE__ */ jsx27(ActionBarPrimitive.ExportMarkdown, { asChild: true, children: /* @__PURE__ */ jsxs15(ActionBarMorePrimitive.Item, { className: "aui-action-bar-more-item flex cursor-pointer select-none items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none hover:bg-muted focus:bg-muted", children: [
3734
3855
  /* @__PURE__ */ jsx27(DownloadIcon, { className: "size-4 shrink-0" }),
3735
3856
  "Export as Markdown"
3736
3857
  ] }) })
@@ -3755,7 +3876,7 @@ var UserMessage = () => {
3755
3876
  /* @__PURE__ */ jsxs15(
3756
3877
  motion4.div,
3757
3878
  {
3758
- className: "aui-user-message-content relative inline-block max-w-[80%] rounded-2xl bg-neutral-200 px-4 py-2.5 text-foreground dark:bg-neutral-700",
3879
+ className: "aui-user-message-content relative inline-block max-w-[80%] rounded-2xl bg-bubble-user px-4 py-2.5 text-bubble-user-foreground",
3759
3880
  initial: { opacity: 0, y: 8, scale: 0.99 },
3760
3881
  animate: { opacity: 1, y: 0, scale: 1 },
3761
3882
  transition: { duration: 0.65, ease: luxuryEase },
@@ -3831,208 +3952,6 @@ function TimbalChat({
3831
3952
  );
3832
3953
  }
3833
3954
 
3834
- // src/artifacts/agent-instructions.ts
3835
- var ARTIFACT_AGENT_INSTRUCTIONS = `
3836
- ## Rich artifacts (Timbal chat UI)
3837
-
3838
- When you need charts, tables, choice widgets, or interactive controls, return a **JSON artifact object** instead of plain prose. The chat UI renders these automatically.
3839
-
3840
- ### Delivery channels (either works)
3841
-
3842
- 1. **Tool result (preferred)** \u2014 return a single JSON object (or a JSON string) from a tool. The object must include a string \`type\` field.
3843
- 2. **Inline markdown fence** \u2014 embed the same JSON inside a fenced block:
3844
-
3845
- \`\`\`timbal-artifact
3846
- {"type":"chart","data":[{"month":"Jan","sales":120}]}
3847
- \`\`\`
3848
-
3849
- The alias \`\`\`timbal\`\`\` is also accepted.
3850
-
3851
- ### Built-in artifact types
3852
-
3853
- | \`type\` | Use for |
3854
- |---|---|
3855
- | \`chart\` | Bar, line, area, or pie charts. Fields: \`data\`, optional \`chartType\`, \`xKey\`, \`dataKey\`, \`title\`, \`unit\`. |
3856
- | \`table\` | Tabular data. Fields: \`rows\`, optional \`columns\`, \`title\`. |
3857
- | \`question\` | In-thread multiple choice. Fields: \`options: [{ id, label, description? }]\`, optional \`prompt\`, \`multi\`. User replies are sent back as a normal user message. |
3858
- | \`html\` | Custom HTML/CSS/JS in an iframe. Fields: \`content\` (HTML document or fragment), optional \`title\`, \`height\`, \`sandboxed\` (default \`true\`; set \`false\` for unrestricted scripts/CDN). |
3859
- | \`json\` | Fallback structured view. Fields: \`data\`, optional \`title\`. |
3860
- | \`ui\` | **Interactive UI** composed from a fixed node palette (hover, click, drag). See below. |
3861
-
3862
- ### When to use \`type: "html"\`
3863
-
3864
- Use \`html\` when the user wants a **rich visual or interactive page** that does not fit the \`ui\` palette \u2014 e.g. styled layouts, SVG graphics, CSS animations, canvas, small games, calculators, or multi-section mockups. Pass a full HTML document or a body fragment in \`content\`.
3865
-
3866
- - Inline \`<style>\`, \`<script>\`, SVG, and canvas are supported.
3867
- - Default \`sandboxed: true\` runs in an isolated iframe with scripts enabled.
3868
- - Set \`sandboxed: false\` only for trusted content that needs external CDN scripts/styles or full DOM freedom.
3869
- - Prefer \`ui\` when controls should send chat messages or host events; prefer \`html\` for self-contained mini-apps and visual demos.
3870
-
3871
- ### When to use \`type: "ui"\`
3872
-
3873
- Use a \`ui\` artifact when the user should **hover, click, drag, or adjust controls** in-thread and those actions should integrate with the chat runtime (messages, \`onArtifactEvent\`). For standalone visual/interactive HTML, use \`html\` instead.
3874
-
3875
- Each \`ui\` artifact has:
3876
-
3877
- - \`initialState\` \u2014 optional object seeding local state (per widget instance).
3878
- - \`root\` \u2014 a single node tree (see node kinds below).
3879
- - optional \`title\` \u2014 card heading.
3880
-
3881
- **Bindings:** anywhere a primitive is accepted, you may use \`{ "$bind": "dotted.path" }\` to read from \`initialState\` / local state (e.g. \`{ "$bind": "qty" }\`).
3882
-
3883
- **Actions:** nodes may attach \`onClick\`, \`onChange\`, or \`onDragEnd\` with one action or an array:
3884
-
3885
- | Action | Shape | Effect |
3886
- |---|---|---|
3887
- | User message | \`{ "kind": "message", "text": "..." }\` or \`{ "kind": "message", "text": { "$bind": "path" } }\` | Sends text as the next user message. |
3888
- | Set state | \`{ "kind": "set", "path": "foo", "value": 1 }\` | Writes local widget state. |
3889
- | Toggle boolean | \`{ "kind": "toggle", "path": "enabled" }\` | Flips a boolean at \`path\`. |
3890
- | Host event | \`{ "kind": "emit", "name": "event-name", "payload": { ... } }\` | Bubbles to the host app (\`onArtifactEvent\` on \`<Thread>\`). |
3891
-
3892
- ### \`ui\` node palette (\`root.kind\`)
3893
-
3894
- | \`kind\` | Purpose | Key fields |
3895
- |---|---|---|
3896
- | \`box\` | Layout container | \`children\`, \`direction\` (\`row\`/\`col\`), \`gap\`, \`padding\`, \`align\`, \`justify\`, \`wrap\` |
3897
- | \`text\` | Body text | \`value\`, optional \`muted\`, \`size\`, \`weight\` |
3898
- | \`heading\` | Heading | \`value\`, optional \`level\` (1\u20134) |
3899
- | \`badge\` | Pill label | \`value\`, optional \`tone\` (\`default\`, \`primary\`, \`success\`, \`warn\`, \`danger\`) |
3900
- | \`button\` | Clickable button | \`label\`, optional \`variant\`, \`size\`, \`disabled\`, \`onClick\` |
3901
- | \`toggle\` | Boolean switch | \`binding\` (state path), optional \`label\`, \`onChange\` |
3902
- | \`slider\` | Numeric range | \`binding\`, optional \`min\`, \`max\`, \`step\`, \`label\`, \`showValue\`, \`onChange\` |
3903
- | \`tooltip\` | Hover tooltip | \`content\`, \`child\` (single node), optional \`side\` |
3904
- | \`draggable\` | Drag gesture | \`child\`, optional \`axis\` (\`x\`/\`y\`/\`both\`), \`snapBack\`, \`onDragEnd\` |
3905
- | \`custom\` | Host-registered widget | \`name\`, optional \`props\`, \`children\` \u2014 only if the app registered that name |
3906
-
3907
- ### Example \`ui\` artifact
3908
-
3909
- \`\`\`json
3910
- {
3911
- "type": "ui",
3912
- "title": "Configure plan",
3913
- "initialState": { "qty": 1, "premium": false },
3914
- "root": {
3915
- "kind": "box",
3916
- "direction": "col",
3917
- "gap": 3,
3918
- "children": [
3919
- { "kind": "heading", "value": "Choose quantity", "level": 3 },
3920
- {
3921
- "kind": "tooltip",
3922
- "content": "Drag to adjust quantity",
3923
- "child": {
3924
- "kind": "slider",
3925
- "binding": "qty",
3926
- "min": 1,
3927
- "max": 50,
3928
- "label": "Quantity",
3929
- "onChange": { "kind": "emit", "name": "qty-changed" }
3930
- }
3931
- },
3932
- { "kind": "toggle", "binding": "premium", "label": "Premium support" },
3933
- {
3934
- "kind": "button",
3935
- "label": "Confirm",
3936
- "onClick": { "kind": "message", "text": { "$bind": "qty" } }
3937
- }
3938
- ]
3939
- }
3940
- }
3941
- \`\`\`
3942
-
3943
- ### Rules
3944
-
3945
- - Always set \`type\` to a built-in value above unless the app documented a custom type.
3946
- - Prefer \`ui\` over \`html\` when actions must bubble to the host chat (\`message\`, \`emit\`).
3947
- - Prefer \`question\` for simple A/B/C choices; use \`ui\` when you need sliders, toggles, drag, or multi-control layouts.
3948
- - Keep \`data\` arrays reasonably small (charts/tables).
3949
-
3950
- ### After calling an artifact tool (critical)
3951
-
3952
- When you call a tool that returns an artifact (\`make_chart\`, \`ask_question\`, \`show_table\`, \`show_html\`, \`make_ui_demo\`, etc.):
3953
-
3954
- 1. **Do not** paste, quote, paraphrase as JSON, or fence the tool return value in your assistant message. The chat UI already renders it from the tool result.
3955
- 2. **Do not** emit a matching \`\`\`timbal-artifact\`\`\` block for the same payload \u2014 pick **one** channel (tool result only).
3956
- 3. Your follow-up text should be **empty**, or at most **one short sentence** (e.g. "Pick an option above." / "Try the controls."). Never include \`type\`, \`options\`, \`data\`, or dict/JSON syntax.
3957
- 4. Treat the widget as visible to the user; refer to it as "above" / "the chart" / "the choices" \u2014 never reproduce its contents.
3958
- `.trim();
3959
-
3960
- // src/index.ts
3961
- import {
3962
- ThreadPrimitive as ThreadPrimitive2,
3963
- MessagePrimitive as MessagePrimitive3,
3964
- MessagePartPrimitive as MessagePartPrimitive2,
3965
- ComposerPrimitive as ComposerPrimitive4,
3966
- ActionBarPrimitive as ActionBarPrimitive2,
3967
- ActionBarMorePrimitive as ActionBarMorePrimitive2,
3968
- ErrorPrimitive as ErrorPrimitive2,
3969
- AuiIf as AuiIf3,
3970
- AssistantRuntimeProvider as AssistantRuntimeProvider2,
3971
- useThread as useThread2,
3972
- useThreadRuntime as useThreadRuntime4,
3973
- useMessageRuntime,
3974
- useComposerRuntime as useComposerRuntime2,
3975
- useAuiState as useAuiState3
3976
- } from "@assistant-ui/react";
3977
-
3978
- // src/hooks/use-workforces.ts
3979
- import { useEffect as useEffect5, useMemo as useMemo7, useRef as useRef2, useState as useState8 } from "react";
3980
- function useWorkforces(options = {}) {
3981
- const { baseUrl = "/api", fetch: fetchFn, pickInitial } = options;
3982
- const [workforces, setWorkforces] = useState8([]);
3983
- const [selectedId, setSelectedId] = useState8("");
3984
- const [isLoading, setIsLoading] = useState8(true);
3985
- const [error, setError] = useState8(null);
3986
- const fetchFnRef = useRef2(fetchFn ?? authFetch);
3987
- useEffect5(() => {
3988
- fetchFnRef.current = fetchFn ?? authFetch;
3989
- }, [fetchFn]);
3990
- const pickInitialRef = useRef2(pickInitial);
3991
- useEffect5(() => {
3992
- pickInitialRef.current = pickInitial;
3993
- }, [pickInitial]);
3994
- const load = useMemo7(() => {
3995
- return async () => {
3996
- setIsLoading(true);
3997
- setError(null);
3998
- try {
3999
- const res = await fetchFnRef.current(`${baseUrl}/workforce`);
4000
- if (!res.ok) throw new Error(`Failed to load workforces (${res.status})`);
4001
- const data = await res.json();
4002
- setWorkforces(data);
4003
- setSelectedId((current) => {
4004
- if (current && data.some((w) => idOf(w) === current)) return current;
4005
- const initial = pickInitialRef.current?.(data) ?? data.find((w) => w.type === "agent") ?? data[0];
4006
- return initial ? idOf(initial) : "";
4007
- });
4008
- } catch (err) {
4009
- setError(err instanceof Error ? err : new Error(String(err)));
4010
- } finally {
4011
- setIsLoading(false);
4012
- }
4013
- };
4014
- }, [baseUrl]);
4015
- useEffect5(() => {
4016
- load();
4017
- }, [load]);
4018
- const selected = useMemo7(
4019
- () => workforces.find((w) => idOf(w) === selectedId),
4020
- [workforces, selectedId]
4021
- );
4022
- return {
4023
- workforces,
4024
- selectedId,
4025
- setSelectedId,
4026
- selected,
4027
- isLoading,
4028
- error,
4029
- refresh: load
4030
- };
4031
- }
4032
- function idOf(item) {
4033
- return item.id ?? item.uid ?? item.name ?? "";
4034
- }
4035
-
4036
3955
  // src/components/workforce-selector.tsx
4037
3956
  import { ChevronDownIcon } from "lucide-react";
4038
3957
  import { jsx as jsx29, jsxs as jsxs16 } from "react/jsx-runtime";
@@ -4067,7 +3986,7 @@ var WorkforceSelector = ({
4067
3986
  children: [
4068
3987
  !value && /* @__PURE__ */ jsx29("option", { value: "", children: placeholder }),
4069
3988
  workforces.map((w) => {
4070
- const id = idOf2(w);
3989
+ const id = idOf(w);
4071
3990
  return /* @__PURE__ */ jsx29("option", { value: id, children: w.name ?? id }, id);
4072
3991
  })
4073
3992
  ]
@@ -4084,43 +4003,146 @@ var WorkforceSelector = ({
4084
4003
  }
4085
4004
  );
4086
4005
  };
4087
- function idOf2(item) {
4006
+ function idOf(item) {
4088
4007
  return item.id ?? item.uid ?? item.name ?? "";
4089
4008
  }
4090
4009
 
4091
- // src/components/chat-shell.tsx
4092
- import { jsx as jsx30, jsxs as jsxs17 } from "react/jsx-runtime";
4093
- var TimbalChatShell = ({
4094
- workforceId,
4095
- brand,
4096
- headerActions,
4097
- hideWorkforceSelector,
4098
- className,
4099
- headerClassName,
4100
- baseUrl,
4101
- fetch: fetch2,
4102
- ...chatProps
4103
- }) => {
4104
- const { workforces, selectedId, setSelectedId } = useWorkforces({
4105
- baseUrl,
4106
- fetch: fetch2
4107
- });
4108
- const effectiveId = workforceId ?? selectedId;
4109
- const showSelector = !hideWorkforceSelector && !workforceId && workforces.length > 0;
4110
- return /* @__PURE__ */ jsxs17(
4111
- "div",
4112
- {
4113
- className: cn(
4114
- "aui-chat-shell relative flex h-dvh flex-col overflow-hidden bg-background",
4115
- className
4116
- ),
4117
- style: studioChromeShellStyle,
4118
- children: [
4119
- /* @__PURE__ */ jsx30(
4120
- "div",
4121
- {
4122
- className: cn(
4123
- "pointer-events-none absolute inset-0 z-0",
4010
+ // src/hooks/use-workforces.ts
4011
+ import { useEffect as useEffect6, useMemo as useMemo7, useRef as useRef2, useState as useState8 } from "react";
4012
+ function useWorkforces(options = {}) {
4013
+ const { baseUrl = "/api", fetch: fetchFn, pickInitial, enabled = true } = options;
4014
+ const [workforces, setWorkforces] = useState8([]);
4015
+ const [selectedId, setSelectedId] = useState8("");
4016
+ const [isLoading, setIsLoading] = useState8(enabled);
4017
+ const [error, setError] = useState8(null);
4018
+ const fetchFnRef = useRef2(fetchFn ?? authFetch);
4019
+ useEffect6(() => {
4020
+ fetchFnRef.current = fetchFn ?? authFetch;
4021
+ }, [fetchFn]);
4022
+ const pickInitialRef = useRef2(pickInitial);
4023
+ useEffect6(() => {
4024
+ pickInitialRef.current = pickInitial;
4025
+ }, [pickInitial]);
4026
+ const load = useMemo7(() => {
4027
+ return async () => {
4028
+ if (!enabled) {
4029
+ setIsLoading(false);
4030
+ return;
4031
+ }
4032
+ setIsLoading(true);
4033
+ setError(null);
4034
+ try {
4035
+ const res = await fetchFnRef.current(`${baseUrl}/workforce`);
4036
+ if (!res.ok) throw new Error(`Failed to load workforces (${res.status})`);
4037
+ const data = await res.json();
4038
+ setWorkforces(data);
4039
+ setSelectedId((current) => {
4040
+ if (current && data.some((w) => idOf2(w) === current)) return current;
4041
+ const initial = pickInitialRef.current?.(data) ?? data.find((w) => w.type === "agent") ?? data[0];
4042
+ return initial ? idOf2(initial) : "";
4043
+ });
4044
+ } catch (err) {
4045
+ setError(err instanceof Error ? err : new Error(String(err)));
4046
+ } finally {
4047
+ setIsLoading(false);
4048
+ }
4049
+ };
4050
+ }, [baseUrl, enabled]);
4051
+ useEffect6(() => {
4052
+ load();
4053
+ }, [load]);
4054
+ const selected = useMemo7(
4055
+ () => workforces.find((w) => idOf2(w) === selectedId),
4056
+ [workforces, selectedId]
4057
+ );
4058
+ return {
4059
+ workforces,
4060
+ selectedId,
4061
+ setSelectedId,
4062
+ selected,
4063
+ isLoading,
4064
+ error,
4065
+ refresh: load
4066
+ };
4067
+ }
4068
+ function idOf2(item) {
4069
+ return item.id ?? item.uid ?? item.name ?? "";
4070
+ }
4071
+
4072
+ // src/design/tokens.ts
4073
+ var SIDEBAR_WIDTH_PX = 224;
4074
+ var SIDEBAR_WIDTH_COLLAPSED_PX = 52;
4075
+ var SIDEBAR_MOBILE_PX = 272;
4076
+ var SIDEBAR_GAP_PX = 12;
4077
+ var SIDEBAR_CONTENT_GAP_PX = 8;
4078
+ var TOPBAR_GAP_PX = 8;
4079
+ var TOPBAR_HEIGHT_PX = 48;
4080
+ var PILL_HEIGHT_PX = 40;
4081
+ var SIDEBAR_INSET_PX_EXPANDED = SIDEBAR_GAP_PX + SIDEBAR_WIDTH_PX + SIDEBAR_CONTENT_GAP_PX;
4082
+ var SIDEBAR_INSET_PX_COLLAPSED = SIDEBAR_GAP_PX + SIDEBAR_WIDTH_COLLAPSED_PX + SIDEBAR_CONTENT_GAP_PX;
4083
+ var px = (n) => `${n / 16}rem`;
4084
+ var SIDEBAR_WIDTH = px(SIDEBAR_WIDTH_PX);
4085
+ var SIDEBAR_WIDTH_COLLAPSED = px(SIDEBAR_WIDTH_COLLAPSED_PX);
4086
+ var SIDEBAR_GAP = px(SIDEBAR_GAP_PX);
4087
+ var SIDEBAR_CONTENT_GAP = px(SIDEBAR_CONTENT_GAP_PX);
4088
+ var TOPBAR_GAP = px(TOPBAR_GAP_PX);
4089
+ var TOPBAR_HEIGHT = px(TOPBAR_HEIGHT_PX);
4090
+ var PILL_HEIGHT = px(PILL_HEIGHT_PX);
4091
+ var SIDEBAR_INSET_EXPANDED = px(SIDEBAR_INSET_PX_EXPANDED);
4092
+ var SIDEBAR_INSET_COLLAPSED = px(SIDEBAR_INSET_PX_COLLAPSED);
4093
+ var studioChromeShellStyle = {
4094
+ "--studio-topbar-gap": TOPBAR_GAP,
4095
+ "--studio-topbar-height": TOPBAR_HEIGHT,
4096
+ "--studio-chrome-pill-height": PILL_HEIGHT,
4097
+ "--studio-inset-top": `calc(${TOPBAR_GAP} + ${TOPBAR_HEIGHT})`,
4098
+ "--studio-sidebar-gap": SIDEBAR_GAP,
4099
+ "--studio-sidebar-width": SIDEBAR_WIDTH,
4100
+ "--studio-sidebar-width-collapsed": SIDEBAR_WIDTH_COLLAPSED,
4101
+ "--studio-sidebar-content-gap": SIDEBAR_CONTENT_GAP,
4102
+ "--studio-inset-left": SIDEBAR_INSET_EXPANDED,
4103
+ "--studio-inset-left-collapsed": SIDEBAR_INSET_COLLAPSED
4104
+ };
4105
+ var STORAGE_KEYS = {
4106
+ sidebarCollapsed: "timbal-studio-sidebar-collapsed"
4107
+ };
4108
+ var DOM_IDS = {
4109
+ sidebarRuntimeAnchor: "timbal-studio-sidebar-runtime-anchor",
4110
+ topbarBrandAnchor: "timbal-studio-topbar-brand-anchor"
4111
+ };
4112
+
4113
+ // src/components/chat-shell.tsx
4114
+ import { jsx as jsx30, jsxs as jsxs17 } from "react/jsx-runtime";
4115
+ var TimbalChatShell = ({
4116
+ workforceId,
4117
+ brand,
4118
+ headerActions,
4119
+ hideWorkforceSelector,
4120
+ className,
4121
+ headerClassName,
4122
+ baseUrl,
4123
+ fetch: fetch2,
4124
+ ...chatProps
4125
+ }) => {
4126
+ const { workforces, selectedId, setSelectedId } = useWorkforces({
4127
+ baseUrl,
4128
+ fetch: fetch2
4129
+ });
4130
+ const effectiveId = workforceId ?? selectedId;
4131
+ const showSelector = !hideWorkforceSelector && !workforceId && workforces.length > 0;
4132
+ return /* @__PURE__ */ jsxs17(
4133
+ "div",
4134
+ {
4135
+ className: cn(
4136
+ "aui-chat-shell relative flex h-dvh flex-col overflow-hidden bg-background",
4137
+ className
4138
+ ),
4139
+ style: studioChromeShellStyle,
4140
+ children: [
4141
+ /* @__PURE__ */ jsx30(
4142
+ "div",
4143
+ {
4144
+ className: cn(
4145
+ "pointer-events-none absolute inset-0 z-0",
4124
4146
  studioPlaygroundGradientClass
4125
4147
  ),
4126
4148
  "aria-hidden": true
@@ -4166,15 +4188,250 @@ var TimbalChatShell = ({
4166
4188
  );
4167
4189
  };
4168
4190
 
4169
- // src/auth/provider.tsx
4191
+ // src/components/studio/studio-shell.tsx
4170
4192
  import {
4171
- createContext as createContext4,
4172
- useCallback as useCallback4,
4173
- useContext as useContext4,
4174
- useEffect as useEffect6,
4175
- useState as useState9
4193
+ useCallback as useCallback8,
4194
+ useEffect as useEffect10,
4195
+ useMemo as useMemo9,
4196
+ useState as useState13
4176
4197
  } from "react";
4198
+ import { Menu } from "lucide-react";
4199
+ import { motion as motion10, useReducedMotion as useReducedMotion6 } from "motion/react";
4200
+
4201
+ // src/design/sidebar-motion.ts
4202
+ var STUDIO_SIDEBAR_EASE_ENTER = [0, 0, 0.2, 1];
4203
+ var STUDIO_SIDEBAR_EASE_EXIT = [0.4, 0, 1, 1];
4204
+ var STUDIO_SIDEBAR_EASE = [0.16, 1, 0.3, 1];
4205
+ var STUDIO_SIDEBAR_ENTRIES_OUT_S = 0.1;
4206
+ var STUDIO_SIDEBAR_WIDTH_S = 0.17;
4207
+ var STUDIO_SIDEBAR_ENTRY_ITEM_IN_S = 0.18;
4208
+ var STUDIO_SIDEBAR_STAGGER_S = 0.03;
4209
+ var STUDIO_SIDEBAR_EXPAND_REVEAL_FRAC = 0.5;
4210
+ var STUDIO_SIDEBAR_CONTENT_NUDGE_PX = 6;
4211
+ var studioSidebarEntriesContainerVariants = {
4212
+ hidden: {
4213
+ opacity: 0,
4214
+ transition: {
4215
+ duration: STUDIO_SIDEBAR_ENTRIES_OUT_S,
4216
+ ease: STUDIO_SIDEBAR_EASE_EXIT,
4217
+ staggerChildren: 0
4218
+ }
4219
+ },
4220
+ visible: {
4221
+ opacity: 1,
4222
+ transition: {
4223
+ duration: 0.06,
4224
+ ease: STUDIO_SIDEBAR_EASE_ENTER,
4225
+ staggerChildren: STUDIO_SIDEBAR_STAGGER_S,
4226
+ delayChildren: 0.02
4227
+ }
4228
+ }
4229
+ };
4230
+ var studioSidebarEntryItemVariants = {
4231
+ hidden: {
4232
+ opacity: 0,
4233
+ x: -STUDIO_SIDEBAR_CONTENT_NUDGE_PX,
4234
+ scale: 0.99
4235
+ },
4236
+ visible: {
4237
+ opacity: 1,
4238
+ x: 0,
4239
+ scale: 1,
4240
+ transition: {
4241
+ duration: STUDIO_SIDEBAR_ENTRY_ITEM_IN_S,
4242
+ ease: STUDIO_SIDEBAR_EASE_ENTER
4243
+ }
4244
+ }
4245
+ };
4246
+ function studioSidebarEntriesTransition(visible, reduced) {
4247
+ if (reduced) return { duration: 0.01 };
4248
+ return visible ? { duration: 0.06, ease: STUDIO_SIDEBAR_EASE_ENTER } : { duration: STUDIO_SIDEBAR_ENTRIES_OUT_S, ease: STUDIO_SIDEBAR_EASE_EXIT };
4249
+ }
4250
+ function studioSidebarWidthTransition(reduced, direction = "collapse") {
4251
+ if (reduced) return { duration: 0.01 };
4252
+ return {
4253
+ duration: direction === "expand" ? STUDIO_SIDEBAR_WIDTH_S : STUDIO_SIDEBAR_WIDTH_S * 0.94,
4254
+ ease: direction === "expand" ? STUDIO_SIDEBAR_EASE_ENTER : STUDIO_SIDEBAR_EASE_EXIT
4255
+ };
4256
+ }
4257
+ function studioSidebarDrawerTransition(reduced) {
4258
+ if (reduced) return { duration: 0.01 };
4259
+ return {
4260
+ duration: 0.22,
4261
+ ease: STUDIO_SIDEBAR_EASE
4262
+ };
4263
+ }
4264
+ function studioSidebarBackdropTransition(reduced) {
4265
+ if (reduced) return { duration: 0.01 };
4266
+ return { duration: 0.16, ease: STUDIO_SIDEBAR_EASE_EXIT };
4267
+ }
4268
+
4269
+ // src/hooks/use-sidebar-collapse-phase.ts
4270
+ import { useCallback as useCallback4, useEffect as useEffect7, useRef as useRef3, useState as useState9 } from "react";
4271
+ var WIDTH_OVERLAP_FRAC = 0.7;
4272
+ function useSidebarCollapsePhase(collapsed, reducedMotion) {
4273
+ const [widthCollapsed, setWidthCollapsed] = useState9(collapsed);
4274
+ const [entriesVisible, setEntriesVisible] = useState9(true);
4275
+ const collapsedTarget = useRef3(collapsed);
4276
+ const isFirstRender = useRef3(true);
4277
+ const widthTimerRef = useRef3(null);
4278
+ const revealTimerRef = useRef3(null);
4279
+ useEffect7(() => {
4280
+ collapsedTarget.current = collapsed;
4281
+ }, [collapsed]);
4282
+ const clearWidthTimer = () => {
4283
+ if (widthTimerRef.current !== null) {
4284
+ clearTimeout(widthTimerRef.current);
4285
+ widthTimerRef.current = null;
4286
+ }
4287
+ };
4288
+ const clearRevealTimer = () => {
4289
+ if (revealTimerRef.current !== null) {
4290
+ clearTimeout(revealTimerRef.current);
4291
+ revealTimerRef.current = null;
4292
+ }
4293
+ };
4294
+ const applyWidthTarget = useCallback4(() => {
4295
+ const willExpand = !collapsedTarget.current;
4296
+ setWidthCollapsed(collapsedTarget.current);
4297
+ clearRevealTimer();
4298
+ if (willExpand && !reducedMotion) {
4299
+ revealTimerRef.current = setTimeout(
4300
+ () => setEntriesVisible(true),
4301
+ STUDIO_SIDEBAR_WIDTH_S * 1e3 * STUDIO_SIDEBAR_EXPAND_REVEAL_FRAC
4302
+ );
4303
+ }
4304
+ }, [reducedMotion]);
4305
+ useEffect7(() => {
4306
+ clearWidthTimer();
4307
+ clearRevealTimer();
4308
+ if (reducedMotion) {
4309
+ setWidthCollapsed(collapsed);
4310
+ setEntriesVisible(true);
4311
+ return;
4312
+ }
4313
+ if (isFirstRender.current) {
4314
+ isFirstRender.current = false;
4315
+ setWidthCollapsed(collapsed);
4316
+ setEntriesVisible(true);
4317
+ return;
4318
+ }
4319
+ setEntriesVisible(false);
4320
+ widthTimerRef.current = setTimeout(
4321
+ applyWidthTarget,
4322
+ STUDIO_SIDEBAR_ENTRIES_OUT_S * 1e3 * WIDTH_OVERLAP_FRAC
4323
+ );
4324
+ return () => {
4325
+ clearWidthTimer();
4326
+ clearRevealTimer();
4327
+ };
4328
+ }, [collapsed, reducedMotion, applyWidthTarget]);
4329
+ const onEntriesBlurOutComplete = useCallback4(() => {
4330
+ applyWidthTarget();
4331
+ }, [applyWidthTarget]);
4332
+ const onPanelWidthComplete = useCallback4(() => {
4333
+ clearRevealTimer();
4334
+ setEntriesVisible(true);
4335
+ }, []);
4336
+ const isCollapsedRail = widthCollapsed;
4337
+ return {
4338
+ widthCollapsed,
4339
+ isCollapsedRail,
4340
+ entriesVisible,
4341
+ onEntriesBlurOutComplete,
4342
+ onPanelWidthComplete
4343
+ };
4344
+ }
4345
+
4346
+ // src/components/studio/sidebar-backdrop.tsx
4347
+ import { AnimatePresence as AnimatePresence2, motion as motion5, useReducedMotion as useReducedMotion2 } from "motion/react";
4177
4348
  import { jsx as jsx31 } from "react/jsx-runtime";
4349
+ var StudioSidebarBackdrop = ({
4350
+ open,
4351
+ onClose
4352
+ }) => {
4353
+ const reducedMotion = useReducedMotion2();
4354
+ return /* @__PURE__ */ jsx31(AnimatePresence2, { children: open ? /* @__PURE__ */ jsx31(
4355
+ motion5.button,
4356
+ {
4357
+ type: "button",
4358
+ className: "fixed inset-0 z-40 bg-foreground/30 backdrop-blur-[2px] md:hidden",
4359
+ "aria-label": "Close menu",
4360
+ initial: { opacity: 0 },
4361
+ animate: { opacity: 1 },
4362
+ exit: { opacity: 0 },
4363
+ transition: studioSidebarBackdropTransition(!!reducedMotion),
4364
+ onClick: onClose
4365
+ }
4366
+ ) : null });
4367
+ };
4368
+
4369
+ // src/components/studio/sidebar-context.tsx
4370
+ import { createContext as createContext4, useContext as useContext4 } from "react";
4371
+ var StudioSidebarContext = createContext4({
4372
+ collapsed: false,
4373
+ isMobile: false,
4374
+ isCollapsedRail: false,
4375
+ iconOnlyLayout: false
4376
+ });
4377
+ function useStudioSidebarLayout() {
4378
+ return useContext4(StudioSidebarContext);
4379
+ }
4380
+
4381
+ // src/components/studio/sidebar.tsx
4382
+ import {
4383
+ useCallback as useCallback6,
4384
+ useEffect as useEffect9,
4385
+ useMemo as useMemo8,
4386
+ useState as useState11
4387
+ } from "react";
4388
+ import { motion as motion8, useReducedMotion as useReducedMotion5 } from "motion/react";
4389
+
4390
+ // src/components/studio/sidebar-entries.tsx
4391
+ import { motion as motion6, useReducedMotion as useReducedMotion3 } from "motion/react";
4392
+ import { jsx as jsx32 } from "react/jsx-runtime";
4393
+ var StudioSidebarEntries = ({
4394
+ visible,
4395
+ onBlurOutComplete,
4396
+ children,
4397
+ className
4398
+ }) => {
4399
+ const reducedMotion = useReducedMotion3();
4400
+ if (reducedMotion) {
4401
+ return visible ? /* @__PURE__ */ jsx32("div", { className: cn("flex min-h-0 flex-1 flex-col", className), children }) : null;
4402
+ }
4403
+ return /* @__PURE__ */ jsx32(
4404
+ motion6.div,
4405
+ {
4406
+ className: cn("flex min-h-0 flex-1 flex-col", className),
4407
+ initial: false,
4408
+ variants: studioSidebarEntriesContainerVariants,
4409
+ animate: visible ? "visible" : "hidden",
4410
+ transition: studioSidebarEntriesTransition(visible, false),
4411
+ onAnimationComplete: (definition) => {
4412
+ if (definition === "hidden") {
4413
+ onBlurOutComplete();
4414
+ }
4415
+ },
4416
+ style: { pointerEvents: visible ? "auto" : "none" },
4417
+ "aria-hidden": !visible,
4418
+ children
4419
+ }
4420
+ );
4421
+ };
4422
+
4423
+ // src/components/studio/sidebar-footer.tsx
4424
+ import { LogOut } from "lucide-react";
4425
+
4426
+ // src/auth/provider.tsx
4427
+ import {
4428
+ createContext as createContext5,
4429
+ useCallback as useCallback5,
4430
+ useContext as useContext5,
4431
+ useEffect as useEffect8,
4432
+ useState as useState10
4433
+ } from "react";
4434
+ import { jsx as jsx33 } from "react/jsx-runtime";
4178
4435
  function isInsideIframe() {
4179
4436
  try {
4180
4437
  return typeof window !== "undefined" && window.self !== window.top;
@@ -4182,22 +4439,26 @@ function isInsideIframe() {
4182
4439
  return true;
4183
4440
  }
4184
4441
  }
4185
- var SessionContext = createContext4(void 0);
4442
+ var SessionContext = createContext5(void 0);
4186
4443
  var useSession = () => {
4187
- const context = useContext4(SessionContext);
4444
+ const context = useContext5(SessionContext);
4188
4445
  if (context === void 0) {
4189
4446
  throw new Error("useSession must be used within a SessionProvider");
4190
4447
  }
4191
4448
  return context;
4192
4449
  };
4450
+ var useOptionalSession = () => {
4451
+ const context = useContext5(SessionContext);
4452
+ return context ?? null;
4453
+ };
4193
4454
  var SessionProvider = ({
4194
4455
  children,
4195
4456
  enabled = true
4196
4457
  }) => {
4197
- const [user, setUser] = useState9(null);
4198
- const [loading, setLoading] = useState9(enabled);
4199
- const [embedded] = useState9(isInsideIframe);
4200
- useEffect6(() => {
4458
+ const [user, setUser] = useState10(null);
4459
+ const [loading, setLoading] = useState10(enabled);
4460
+ const [embedded] = useState10(isInsideIframe);
4461
+ useEffect8(() => {
4201
4462
  if (!enabled) {
4202
4463
  setLoading(false);
4203
4464
  return;
@@ -4258,7 +4519,7 @@ var SessionProvider = ({
4258
4519
  messageCleanup?.();
4259
4520
  };
4260
4521
  }, [enabled, embedded]);
4261
- const logout = useCallback4(() => {
4522
+ const logout = useCallback5(() => {
4262
4523
  clearTokens();
4263
4524
  setUser(null);
4264
4525
  const returnTo = encodeURIComponent(
@@ -4268,7 +4529,7 @@ var SessionProvider = ({
4268
4529
  () => window.location.href = `/api/auth/login?return_to=${returnTo}`
4269
4530
  );
4270
4531
  }, []);
4271
- return /* @__PURE__ */ jsx31(
4532
+ return /* @__PURE__ */ jsx33(
4272
4533
  SessionContext.Provider,
4273
4534
  {
4274
4535
  value: {
@@ -4283,49 +4544,1185 @@ var SessionProvider = ({
4283
4544
  );
4284
4545
  };
4285
4546
 
4286
- // src/auth/guard.tsx
4287
- import { Loader2 } from "lucide-react";
4288
- import { jsx as jsx32 } from "react/jsx-runtime";
4289
- var AuthGuard = ({
4547
+ // src/components/studio/sidebar-layout.ts
4548
+ function studioSidebarIconOnlyLayout(isMobile, isCollapsedRail) {
4549
+ if (isMobile) return false;
4550
+ return isCollapsedRail;
4551
+ }
4552
+ var studioSidebarCollapsedRailInsetClass = "box-border w-full px-1.5";
4553
+ var studioSidebarCollapsedRailChipRowClass = "flex w-full justify-center";
4554
+ function studioSidebarNavItemClasses(iconOnly, isActive) {
4555
+ if (iconOnly) {
4556
+ return cn(
4557
+ studioSidebarNavItemClass,
4558
+ studioSidebarNavItemLayout(true),
4559
+ isActive ? studioSidebarCollapsedRailItemActiveClass : studioSidebarCollapsedRailItemIdleClass
4560
+ );
4561
+ }
4562
+ return cn(
4563
+ studioSidebarNavItemClass,
4564
+ studioSidebarNavItemLayout(false),
4565
+ isActive ? studioSidebarNavItemActiveClass : studioSidebarNavItemIdleClass
4566
+ );
4567
+ }
4568
+
4569
+ // src/components/studio/sidebar-entry-motion.tsx
4570
+ import { motion as motion7, useReducedMotion as useReducedMotion4 } from "motion/react";
4571
+ import { jsx as jsx34 } from "react/jsx-runtime";
4572
+ var StudioSidebarEntryMotion = ({
4290
4573
  children,
4291
- requireAuth = false,
4292
- enabled = true
4574
+ className
4293
4575
  }) => {
4294
- const { isAuthenticated, loading, isEmbedded } = useSession();
4295
- if (!enabled) {
4296
- return children;
4576
+ const reducedMotion = useReducedMotion4();
4577
+ if (reducedMotion) {
4578
+ return /* @__PURE__ */ jsx34("div", { className, children });
4297
4579
  }
4298
- if (loading) {
4299
- return /* @__PURE__ */ jsx32("div", { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsx32(Loader2, { className: "w-8 h-8 animate-spin" }) });
4580
+ return /* @__PURE__ */ jsx34(motion7.div, { variants: studioSidebarEntryItemVariants, className: cn(className), children });
4581
+ };
4582
+
4583
+ // src/components/studio/sidebar-tooltip.tsx
4584
+ import { Fragment as Fragment3, jsx as jsx35, jsxs as jsxs18 } from "react/jsx-runtime";
4585
+ var StudioSidebarTooltip = ({
4586
+ label,
4587
+ enabled,
4588
+ children
4589
+ }) => {
4590
+ if (!enabled) return /* @__PURE__ */ jsx35(Fragment3, { children });
4591
+ return /* @__PURE__ */ jsxs18(Tooltip, { children: [
4592
+ /* @__PURE__ */ jsx35(TooltipTrigger, { asChild: true, children }),
4593
+ /* @__PURE__ */ jsx35(TooltipContent, { side: "right", className: "text-xs", children: label })
4594
+ ] });
4595
+ };
4596
+
4597
+ // src/components/studio/sidebar-footer.tsx
4598
+ import { jsx as jsx36, jsxs as jsxs19 } from "react/jsx-runtime";
4599
+ function userInitials(name, email) {
4600
+ const fromName = name.trim().split(/\s+/).map((part) => part.charAt(0)).join("").slice(0, 2).toUpperCase();
4601
+ if (fromName) return fromName;
4602
+ return email.charAt(0).toUpperCase() || "?";
4603
+ }
4604
+ var StudioSidebarFooter = ({
4605
+ iconOnlyLayout,
4606
+ showTooltips,
4607
+ onSignOut,
4608
+ emptyCaption = null
4609
+ }) => {
4610
+ const session = useOptionalSession();
4611
+ const user = session?.user ?? null;
4612
+ const handleSignOut = () => {
4613
+ session?.logout();
4614
+ onSignOut?.();
4615
+ };
4616
+ return /* @__PURE__ */ jsx36(StudioSidebarEntryMotion, { children: /* @__PURE__ */ jsx36(
4617
+ "footer",
4618
+ {
4619
+ className: cn(
4620
+ "mt-auto w-full shrink-0 py-2.5",
4621
+ iconOnlyLayout ? studioSidebarCollapsedRailInsetClass : "px-2.5"
4622
+ ),
4623
+ children: user ? /* @__PURE__ */ jsxs19("div", { className: "flex flex-col gap-2", children: [
4624
+ iconOnlyLayout ? /* @__PURE__ */ jsx36("div", { className: studioSidebarCollapsedRailChipRowClass, children: /* @__PURE__ */ jsxs19(Avatar, { size: "sm", className: "size-8", children: [
4625
+ user.user_photo_url ? /* @__PURE__ */ jsx36(AvatarImage, { src: user.user_photo_url, alt: user.user_name }) : null,
4626
+ /* @__PURE__ */ jsx36(AvatarFallback, { className: "text-[10px]", children: userInitials(user.user_name, user.user_email) })
4627
+ ] }) }) : /* @__PURE__ */ jsxs19("div", { className: "flex min-w-0 items-center gap-2.5", children: [
4628
+ /* @__PURE__ */ jsxs19(Avatar, { size: "sm", children: [
4629
+ user.user_photo_url ? /* @__PURE__ */ jsx36(AvatarImage, { src: user.user_photo_url, alt: user.user_name }) : null,
4630
+ /* @__PURE__ */ jsx36(AvatarFallback, { children: userInitials(user.user_name, user.user_email) })
4631
+ ] }),
4632
+ /* @__PURE__ */ jsxs19("div", { className: "min-w-0 flex-1", children: [
4633
+ /* @__PURE__ */ jsx36("p", { className: "truncate text-sm font-medium text-foreground", children: user.user_name }),
4634
+ /* @__PURE__ */ jsx36("p", { className: "truncate text-xs text-muted-foreground", children: user.user_email })
4635
+ ] })
4636
+ ] }),
4637
+ /* @__PURE__ */ jsx36(
4638
+ "div",
4639
+ {
4640
+ className: iconOnlyLayout ? studioSidebarCollapsedRailChipRowClass : void 0,
4641
+ children: /* @__PURE__ */ jsx36(StudioSidebarTooltip, { label: "Sign out", enabled: showTooltips, children: /* @__PURE__ */ jsxs19(
4642
+ "button",
4643
+ {
4644
+ type: "button",
4645
+ onClick: handleSignOut,
4646
+ className: cn(
4647
+ studioSidebarNavItemClasses(iconOnlyLayout, false),
4648
+ iconOnlyLayout && "inline-flex"
4649
+ ),
4650
+ "aria-label": "Sign out",
4651
+ children: [
4652
+ /* @__PURE__ */ jsx36(LogOut, { className: "size-3.5 shrink-0" }),
4653
+ !iconOnlyLayout ? "Sign out" : null
4654
+ ]
4655
+ }
4656
+ ) })
4657
+ }
4658
+ )
4659
+ ] }) : !iconOnlyLayout && emptyCaption ? /* @__PURE__ */ jsx36("p", { className: "px-1 text-xs text-muted-foreground", children: emptyCaption }) : null
4660
+ }
4661
+ ) });
4662
+ };
4663
+
4664
+ // src/components/studio/sidebar-header.tsx
4665
+ import { ChevronLeft, ChevronRight, X } from "lucide-react";
4666
+ import { jsx as jsx37, jsxs as jsxs20 } from "react/jsx-runtime";
4667
+ var sidebarHeaderClass = "flex h-12 shrink-0 items-center px-2";
4668
+ var toggleButtonClass = cn(
4669
+ "flex shrink-0 items-center justify-center rounded-lg text-muted-foreground transition-colors",
4670
+ "hover:bg-muted hover:text-foreground",
4671
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15"
4672
+ );
4673
+ var SidebarToggleButton = ({ ariaLabel, expanded, onClick, children }) => /* @__PURE__ */ jsx37(
4674
+ "button",
4675
+ {
4676
+ type: "button",
4677
+ onClick,
4678
+ className: cn(toggleButtonClass, "size-7"),
4679
+ "aria-label": ariaLabel,
4680
+ "aria-expanded": expanded,
4681
+ children
4300
4682
  }
4301
- if (requireAuth && !isAuthenticated && !isEmbedded) {
4302
- const returnTo = encodeURIComponent(
4303
- window.location.pathname + window.location.search
4683
+ );
4684
+ var CollapsedBrandToggle = ({
4685
+ onExpand,
4686
+ brand
4687
+ }) => /* @__PURE__ */ jsx37("div", { className: studioSidebarCollapsedRailChipRowClass, children: /* @__PURE__ */ jsx37(StudioSidebarTooltip, { label: "Expand sidebar", enabled: true, children: /* @__PURE__ */ jsxs20(
4688
+ "button",
4689
+ {
4690
+ type: "button",
4691
+ onClick: onExpand,
4692
+ "aria-label": "Expand sidebar",
4693
+ "aria-expanded": false,
4694
+ className: cn(
4695
+ toggleButtonClass,
4696
+ "group relative inline-flex size-8 items-center justify-center overflow-hidden rounded-lg"
4697
+ ),
4698
+ children: [
4699
+ /* @__PURE__ */ jsx37(
4700
+ "span",
4701
+ {
4702
+ "aria-hidden": true,
4703
+ className: cn(
4704
+ "pointer-events-none flex items-center justify-center",
4705
+ "transition-[opacity,transform] duration-200 ease-out",
4706
+ "group-hover:scale-90 group-hover:opacity-0"
4707
+ ),
4708
+ children: brand
4709
+ }
4710
+ ),
4711
+ /* @__PURE__ */ jsx37(
4712
+ ChevronRight,
4713
+ {
4714
+ "aria-hidden": true,
4715
+ className: cn(
4716
+ "pointer-events-none absolute inset-0 m-auto size-4",
4717
+ "opacity-0 transition-[opacity,transform] duration-200 ease-out",
4718
+ "group-hover:opacity-100"
4719
+ )
4720
+ }
4721
+ )
4722
+ ]
4723
+ }
4724
+ ) }) });
4725
+ var StudioSidebarHeader = ({
4726
+ isCollapsedRail,
4727
+ isMobile,
4728
+ mobileOpen,
4729
+ onToggle,
4730
+ brand
4731
+ }) => {
4732
+ if (isMobile) {
4733
+ return /* @__PURE__ */ jsxs20("header", { className: cn(sidebarHeaderClass, "justify-between gap-2 pr-2"), children: [
4734
+ brand,
4735
+ /* @__PURE__ */ jsx37(
4736
+ SidebarToggleButton,
4737
+ {
4738
+ ariaLabel: "Close menu",
4739
+ expanded: mobileOpen,
4740
+ onClick: onToggle,
4741
+ children: /* @__PURE__ */ jsx37(X, { className: "size-3.5" })
4742
+ }
4743
+ )
4744
+ ] });
4745
+ }
4746
+ if (isCollapsedRail) {
4747
+ return /* @__PURE__ */ jsx37(
4748
+ "header",
4749
+ {
4750
+ className: cn(
4751
+ "flex h-12 shrink-0 items-center",
4752
+ studioSidebarCollapsedRailInsetClass
4753
+ ),
4754
+ children: /* @__PURE__ */ jsx37(CollapsedBrandToggle, { onExpand: onToggle, brand })
4755
+ }
4304
4756
  );
4305
- window.location.href = `/api/auth/login?return_to=${returnTo}`;
4306
- return null;
4307
4757
  }
4308
- return children;
4758
+ return /* @__PURE__ */ jsxs20("header", { className: cn(sidebarHeaderClass, "justify-between gap-1 pr-2"), children: [
4759
+ brand,
4760
+ /* @__PURE__ */ jsx37(
4761
+ SidebarToggleButton,
4762
+ {
4763
+ ariaLabel: "Collapse sidebar",
4764
+ expanded: true,
4765
+ onClick: onToggle,
4766
+ children: /* @__PURE__ */ jsx37(ChevronLeft, { className: "size-4" })
4767
+ }
4768
+ )
4769
+ ] });
4309
4770
  };
4310
- export {
4311
- ARTIFACT_AGENT_INSTRUCTIONS,
4312
- ARTIFACT_FENCE_LANGUAGES,
4313
- ActionBarMorePrimitive2 as ActionBarMorePrimitive,
4314
- ActionBarPrimitive2 as ActionBarPrimitive,
4315
- ArtifactCard,
4316
- ArtifactRegistryProvider,
4317
- ArtifactView,
4318
- AssistantRuntimeProvider2 as AssistantRuntimeProvider,
4319
- AuiIf3 as AuiIf,
4320
- AuthGuard,
4321
- Avatar,
4771
+
4772
+ // src/components/studio/sidebar-workforce.ts
4773
+ function workforceItemId(w) {
4774
+ return w.id ?? w.uid ?? w.name ?? "";
4775
+ }
4776
+ function workforceItemLabel(w) {
4777
+ return w.name ?? workforceItemId(w);
4778
+ }
4779
+ function workforceItemInitial(w) {
4780
+ const label = workforceItemLabel(w);
4781
+ return label.charAt(0).toUpperCase() || "?";
4782
+ }
4783
+
4784
+ // src/components/studio/sidebar-nav.tsx
4785
+ import { jsx as jsx38 } from "react/jsx-runtime";
4786
+ var StudioSidebarNav = ({
4787
+ workforces,
4788
+ selectedId,
4789
+ onSelect,
4790
+ iconOnlyLayout,
4791
+ showTooltips
4792
+ }) => {
4793
+ if (workforces.length === 0) return null;
4794
+ return /* @__PURE__ */ jsx38(
4795
+ "nav",
4796
+ {
4797
+ className: cn(
4798
+ "flex min-h-0 flex-1 flex-col overflow-y-auto py-1",
4799
+ iconOnlyLayout ? cn(studioSidebarCollapsedRailInsetClass, "gap-1") : "gap-0.5 px-2"
4800
+ ),
4801
+ "aria-label": "Agents",
4802
+ children: workforces.map((w) => {
4803
+ const id = workforceItemId(w);
4804
+ const isActive = id === selectedId;
4805
+ const label = workforceItemLabel(w);
4806
+ return /* @__PURE__ */ jsx38(
4807
+ StudioSidebarEntryMotion,
4808
+ {
4809
+ className: iconOnlyLayout ? studioSidebarCollapsedRailChipRowClass : void 0,
4810
+ children: /* @__PURE__ */ jsx38(StudioSidebarTooltip, { label, enabled: showTooltips, children: /* @__PURE__ */ jsx38(
4811
+ "button",
4812
+ {
4813
+ type: "button",
4814
+ onClick: () => onSelect(id),
4815
+ "aria-pressed": isActive,
4816
+ "aria-label": label,
4817
+ className: cn(
4818
+ studioSidebarNavItemClasses(iconOnlyLayout, isActive),
4819
+ iconOnlyLayout && "inline-flex"
4820
+ ),
4821
+ children: iconOnlyLayout ? /* @__PURE__ */ jsx38("span", { className: "text-xs font-semibold leading-none", children: workforceItemInitial(w) }) : /* @__PURE__ */ jsx38("span", { className: "min-w-0 truncate", children: label })
4822
+ }
4823
+ ) })
4824
+ },
4825
+ id
4826
+ );
4827
+ })
4828
+ }
4829
+ );
4830
+ };
4831
+
4832
+ // src/components/studio/timbal-mark.tsx
4833
+ import { LiquidMetal } from "@paper-design/shaders-react";
4834
+ import { jsx as jsx39 } from "react/jsx-runtime";
4835
+ var DEFAULT_SIZE = 64;
4836
+ var TRANSPARENT_BACK = "#00000000";
4837
+ var TIMBAL_SYMBOL_DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAACWCAYAAAA8AXHiAAAH6ElEQVR4Aeyci7EcNRBFFxIBIgEygUiASCATyASIBOa4rFrZntmPPq1W93218o5nZyTd26dbmnX5fX3TjxyY4IDAmmCqurzdBJYomOKAwJpiqzoVWGJgigMCa4qt6lRgZWHAWKfAMjY8y3ACK0ukjXUKLGPDswwnsLJE2linwDI2PMtwAitLpI11Cixjw+/DxT4SWLHju0ydwHrP+p/euzzE1T8cKr492lsvgfWaXZj733Hp90fL9Pr1EPvn0QTWYcLoF8bS6Pdf/kjQAAnNv7RqVcW6do4q9ffxMe/HW5oXy323boF1zkvzEnDe3RZnS5X6fcRsBdbdRY6oTl1LAJ1s2NDdXaVq3QLr7kapUph8Pxv/iESiDVUqsG63sgQ0b1Rve/6QQEOrVG1DdrCGbFRrQzc5Zh9FlSKppkw5M1gYi8FTjHXaaalSJNTUKWYEC3P5spP3qeY667zsIadVqVpvNrCoUrTag+jHgIRm0z2ka7AGRpzqNG2jOnCeo7uiSi3RnQEszCVjydzRgfPaH1rRbFqlajMig0WVWmpubbThMRvzJVWq1hgVrFKlgKvWG/2YRHLxpBsNrOVLwCJySSBXT7qRwHKxBCwAiypFWzD09ZARwCpVysUScG318E+oUsv3Uleq3gPrqpd1512bO9GWsockqSYO0971zmBR/mnt6ve7k0RC87KvEV61bEewMNftEvCq8Q3XlSqF/obbbW/ZDSz2UWSs2yVgQvjQimb3VarWvgtYZCnm8uRXzz/6MXq3rM47gLXVEjCI9FKlqNCDurTtxjNYxdytloAB4aM6r65S3TK8grXtEtAZEZZ7Wmc362/3BlapUtsuAY0hDVGlau2ewApnbm30g+OyhySpHly210dewKL80/Zyr2+2JBKaQ+4hV4OFudtvVBv4KlUK/Q23+79lJVjso8jYUEvAk5CjFc0hq1StfQVYZCnm8uRXzyX6MXqbq/Nu5liDFX4JOAGgVCkq9MnHMU9ZgVXMDb8EfIYJ1TlNlaq1W4BFlcpoLss9rfY7zfFMsFSl0mD0pdBZYKXaqFa2so+iSpFU1el8hzPAwlgMzuQmeyl0k1ARdf/zrqiRYGGuq/+C9K4Zb11/v5g9JFCh/342+dEosDCWlslOljs0Z3vSfSnGvWCRpRmf+KhSGXW/BBUX9YCFuWQsmUtfGdo3h0g0q0odRjx6tYAFSFnNZXNOlX7kqT47HGgB67jtJnNv+nnkQCtYj/rUZ3LgFggsRdOTAwLLUzQCzUVgBQqmJykCy1M0As1FYAUKpicpAstTNALNRWAFCqYnKTPB8qRTczF2QGAZG55lOIGVJdLGOgWWseFZhhNYWSJtrFNgGRueZTiBlSXSM3We9C2wTkzRqX4HBFa/h+rhxAGBdWKKTvU7ILD6PVQPJw4IrBNTdKrfAYHV76F6OHFAYJ2Ysv+p9QoE1voYhJyBwAoZ1vWiBNb6GIScgcAKGdb1ogTW+hiEnIHAChnW9aIElk0M0o0isNKF3EawwLLxOd0oAitdyN8W/Ndxx9LfmnyMH/aFuX+EVXct7Lfjox+P9vZLFeu5ZcXcf59fGuYKKhRA8Xtmm0QJrGvbqFJd5l537foTKvN3xwzRf7y1vdKC9cSuUqW6zH0yhsePSaSfR0xMYH3qYvcS8Gl32/yNBPrqmC3vx1v/S2DdPRyyBNy72+aIKkUbOuEWsPg970MnsbizUqWGLAGLtbwzPNWpey91NWALWFd97Xh+qrmODSl7SJJqyjQzg0X5p00x1mmnJBKam79GeFVXRrAwd9oS8KrxC64rVQr904f3A9Z0qR8GYB9Fxk5bAj6M4usPtKJ5epWqZWcBiyzFXJ78av3Rj9G7pDpnAMt0CXBCaqlSVOglU4oMVjHXdAlYEsVPB6U6L6lS9TSigkWVWm5ubbTRMcs9zWi462GigaUqdR1r008igbVso2oasS8HYx9FlSKpvvx00ZkHYC2aUduwGIvBbXfveRd7KXSTUO4U7A4W5g79V3l3ETqfEHtIoEL/+RWLz+4MFsbSFltoOjzLHZrdP+nuCBZZmvGJjyVvG927gVWWADLXtFQsHAytVKmt9pC7gEWVwlz3S8BgANG9TZWqte8AVqlSmFzPPfoxiUSbrnPGAJ7BKkuAqtSMyE/u0ytYW21UB8aIfRRViqQa2K19Vx7BwlgMtndj3Ygs8+gmodbNYuDInsDCXH3ZOTC4K7vyAhbZSlvphfXYLHdoDrmHXA0WVWrLx+lOCnnSDa17JViYS8aSuZ1x2uZ2tKL5cZXaRs71RFeARZVKYe5ntrMxD12lar3WYJUqBVz1PKIfk0ipnnStwEqzBHyWISRQxifdmwVYqZaACiyqFK06ledwJlilSqVaAg50qFJp9lKH3tPXLLCymlv2kCTVqeFZTs4Ai/JPC+ThUykkEprDf43w1ImPF4wEC3MzLgGlSqH/o616GwUW+ygyNtMSgFY0q0qd5FEvWGQp5vLkd9J92FPozVidXw5oD1hZlwASiQr9sskZL2wBK+sSgO6UX3a2JEYrWCyBLePtfA/L387zN517C1imE7wYTKedOyCwnAdo1+kJrF0j53zeAst5gHadnsDaNXLO5y2wnAdo1+kJrF0j53zew8ByrlPTM3ZAYBkbnmU4gZUl0sY6BZax4VmGE1hZIm2sU2AZG55lOIGVJdLDdL7W0f8AAAD//x3VUCQAAAAGSURBVAMArsj7LTb9pqMAAAAASUVORK5CYII=";
4838
+ function TimbalMark({
4839
+ className,
4840
+ size = DEFAULT_SIZE,
4841
+ src = TIMBAL_SYMBOL_DATA_URI
4842
+ }) {
4843
+ return /* @__PURE__ */ jsx39(
4844
+ "div",
4845
+ {
4846
+ className: cn("relative shrink-0 bg-transparent", className),
4847
+ style: { width: size, height: size },
4848
+ role: "img",
4849
+ "aria-label": "Timbal",
4850
+ children: /* @__PURE__ */ jsx39(
4851
+ LiquidMetal,
4852
+ {
4853
+ width: size,
4854
+ height: size,
4855
+ image: src,
4856
+ colorBack: TRANSPARENT_BACK,
4857
+ colorTint: "#ffffff",
4858
+ shape: "none",
4859
+ repetition: 2,
4860
+ softness: 0.1,
4861
+ shiftRed: 0.3,
4862
+ shiftBlue: 0.3,
4863
+ distortion: 0.07,
4864
+ contour: 0.4,
4865
+ angle: 70,
4866
+ speed: 1,
4867
+ scale: 0.6,
4868
+ fit: "contain",
4869
+ className: "size-full bg-transparent",
4870
+ style: { background: "transparent" },
4871
+ webGlContextAttributes: {
4872
+ alpha: true,
4873
+ premultipliedAlpha: false
4874
+ }
4875
+ }
4876
+ )
4877
+ }
4878
+ );
4879
+ }
4880
+
4881
+ // src/components/studio/sidebar.tsx
4882
+ import { jsx as jsx40, jsxs as jsxs21 } from "react/jsx-runtime";
4883
+ var DEFAULT_BREAKPOINT_PX = 768;
4884
+ function readPersistedCollapsed(key) {
4885
+ if (!key || typeof window === "undefined") return false;
4886
+ try {
4887
+ return window.localStorage.getItem(key) === "1";
4888
+ } catch {
4889
+ return false;
4890
+ }
4891
+ }
4892
+ function writePersistedCollapsed(key, collapsed) {
4893
+ if (!key || typeof window === "undefined") return;
4894
+ try {
4895
+ window.localStorage.setItem(key, collapsed ? "1" : "0");
4896
+ } catch {
4897
+ }
4898
+ }
4899
+ var StudioSidebarPanel = ({
4900
+ workforces,
4901
+ selectedId,
4902
+ onSelect,
4903
+ collapsed,
4904
+ onCollapsedChange,
4905
+ isMobile,
4906
+ mobileOpen,
4907
+ onMobileOpenChange,
4908
+ widthCollapsed,
4909
+ entriesVisible,
4910
+ onEntriesBlurOutComplete,
4911
+ onPanelWidthComplete,
4912
+ brand,
4913
+ emptyCaption = null
4914
+ }) => {
4915
+ const reducedMotion = useReducedMotion5();
4916
+ const isCollapsedRail = widthCollapsed && !isMobile;
4917
+ const iconOnlyLayout = studioSidebarIconOnlyLayout(isMobile, isCollapsedRail);
4918
+ const isDrawerOpen = isMobile && mobileOpen;
4919
+ const widthDirection = widthCollapsed ? "collapse" : "expand";
4920
+ const widthTransition = studioSidebarWidthTransition(
4921
+ !!reducedMotion,
4922
+ widthDirection
4923
+ );
4924
+ const handleToggle = () => {
4925
+ if (isMobile) {
4926
+ onMobileOpenChange(false);
4927
+ return;
4928
+ }
4929
+ onCollapsedChange(!collapsed);
4930
+ };
4931
+ const panelWidthPx = isMobile ? SIDEBAR_MOBILE_PX : widthCollapsed ? SIDEBAR_WIDTH_COLLAPSED_PX : SIDEBAR_WIDTH_PX;
4932
+ const brandNode = brand ?? /* @__PURE__ */ jsx40(TimbalMark, { size: 32 });
4933
+ const panel = /* @__PURE__ */ jsxs21(
4934
+ motion8.div,
4935
+ {
4936
+ "data-sidebar-collapsed": isCollapsedRail ? "" : void 0,
4937
+ className: cn(
4938
+ "flex h-full flex-col overflow-hidden",
4939
+ studioSidebarPanelClass,
4940
+ isMobile ? "rounded-none rounded-r-2xl" : "rounded-2xl"
4941
+ ),
4942
+ initial: false,
4943
+ animate: { width: panelWidthPx },
4944
+ transition: widthTransition,
4945
+ style: { willChange: entriesVisible ? void 0 : "width" },
4946
+ onAnimationComplete: isMobile || entriesVisible ? void 0 : () => onPanelWidthComplete(),
4947
+ children: [
4948
+ /* @__PURE__ */ jsx40(
4949
+ StudioSidebarHeader,
4950
+ {
4951
+ isCollapsedRail,
4952
+ isMobile,
4953
+ mobileOpen,
4954
+ onToggle: handleToggle,
4955
+ brand: brandNode
4956
+ }
4957
+ ),
4958
+ /* @__PURE__ */ jsxs21(
4959
+ StudioSidebarEntries,
4960
+ {
4961
+ visible: entriesVisible,
4962
+ onBlurOutComplete: onEntriesBlurOutComplete,
4963
+ children: [
4964
+ /* @__PURE__ */ jsx40(
4965
+ "div",
4966
+ {
4967
+ id: DOM_IDS.sidebarRuntimeAnchor,
4968
+ className: cn(
4969
+ "min-h-0 shrink-0 empty:hidden",
4970
+ iconOnlyLayout ? "px-1.5 pt-1.5" : "px-2 pt-1.5"
4971
+ )
4972
+ }
4973
+ ),
4974
+ /* @__PURE__ */ jsx40(
4975
+ StudioSidebarNav,
4976
+ {
4977
+ workforces,
4978
+ selectedId,
4979
+ onSelect,
4980
+ iconOnlyLayout,
4981
+ showTooltips: isCollapsedRail
4982
+ }
4983
+ ),
4984
+ workforces.length === 0 ? /* @__PURE__ */ jsx40("div", { className: "min-h-0 flex-1" }) : null,
4985
+ /* @__PURE__ */ jsx40(
4986
+ StudioSidebarFooter,
4987
+ {
4988
+ iconOnlyLayout,
4989
+ showTooltips: isCollapsedRail,
4990
+ onSignOut: isMobile ? () => onMobileOpenChange(false) : void 0,
4991
+ emptyCaption
4992
+ }
4993
+ )
4994
+ ]
4995
+ }
4996
+ )
4997
+ ]
4998
+ }
4999
+ );
5000
+ if (isMobile) {
5001
+ return /* @__PURE__ */ jsx40(
5002
+ motion8.aside,
5003
+ {
5004
+ className: "fixed inset-y-0 left-0 z-[60] flex",
5005
+ "aria-label": "Studio navigation",
5006
+ "aria-hidden": !mobileOpen,
5007
+ initial: false,
5008
+ animate: {
5009
+ x: isDrawerOpen ? 0 : -(SIDEBAR_MOBILE_PX + 32)
5010
+ },
5011
+ transition: studioSidebarDrawerTransition(!!reducedMotion),
5012
+ style: { pointerEvents: isDrawerOpen ? "auto" : "none" },
5013
+ children: panel
5014
+ }
5015
+ );
5016
+ }
5017
+ return /* @__PURE__ */ jsx40(
5018
+ "aside",
5019
+ {
5020
+ className: "absolute inset-y-0 left-0 z-[60] flex py-[var(--studio-sidebar-gap)] pl-[var(--studio-sidebar-gap)]",
5021
+ "aria-label": "Studio navigation",
5022
+ children: panel
5023
+ }
5024
+ );
5025
+ };
5026
+ var StudioSidebar = ({
5027
+ workforces: workforcesProp,
5028
+ selectedId: selectedIdProp,
5029
+ onSelect,
5030
+ defaultCollapsed = false,
5031
+ persistKey = STORAGE_KEYS.sidebarCollapsed,
5032
+ mobileBreakpointPx = DEFAULT_BREAKPOINT_PX,
5033
+ brand,
5034
+ emptyCaption,
5035
+ mobileOpen: mobileOpenProp,
5036
+ onMobileOpenChange: onMobileOpenChangeProp
5037
+ }) => {
5038
+ const reducedMotion = useReducedMotion5();
5039
+ const fetched = useWorkforces({ enabled: workforcesProp === void 0 });
5040
+ const workforces = workforcesProp ?? fetched.workforces;
5041
+ const [internalSelected, setInternalSelected] = useState11(
5042
+ selectedIdProp ?? ""
5043
+ );
5044
+ useEffect9(() => {
5045
+ if (selectedIdProp !== void 0) return;
5046
+ if (internalSelected) return;
5047
+ const first = workforces[0]?.id ?? workforces[0]?.uid ?? workforces[0]?.name;
5048
+ if (first) setInternalSelected(first);
5049
+ }, [workforces, selectedIdProp, internalSelected]);
5050
+ const selectedId = selectedIdProp ?? internalSelected ?? workforces[0]?.id ?? workforces[0]?.uid ?? workforces[0]?.name ?? "";
5051
+ const handleSelect = useCallback6(
5052
+ (id) => {
5053
+ if (selectedIdProp === void 0) setInternalSelected(id);
5054
+ onSelect?.(id);
5055
+ },
5056
+ [selectedIdProp, onSelect]
5057
+ );
5058
+ const [collapsed, setCollapsed] = useState11(() => {
5059
+ const persisted = readPersistedCollapsed(persistKey);
5060
+ return persisted || defaultCollapsed;
5061
+ });
5062
+ const handleCollapsedChange = useCallback6(
5063
+ (next) => {
5064
+ setCollapsed(next);
5065
+ writePersistedCollapsed(persistKey, next);
5066
+ },
5067
+ [persistKey]
5068
+ );
5069
+ const [isMobile, setIsMobile] = useState11(() => {
5070
+ if (typeof window === "undefined") return false;
5071
+ return window.innerWidth < mobileBreakpointPx;
5072
+ });
5073
+ useEffect9(() => {
5074
+ if (typeof window === "undefined") return;
5075
+ const onResize = () => setIsMobile(window.innerWidth < mobileBreakpointPx);
5076
+ onResize();
5077
+ window.addEventListener("resize", onResize);
5078
+ return () => window.removeEventListener("resize", onResize);
5079
+ }, [mobileBreakpointPx]);
5080
+ const [internalMobileOpen, setInternalMobileOpen] = useState11(false);
5081
+ const mobileOpen = mobileOpenProp ?? internalMobileOpen;
5082
+ const setMobileOpen = useCallback6(
5083
+ (next) => {
5084
+ if (mobileOpenProp === void 0) setInternalMobileOpen(next);
5085
+ onMobileOpenChangeProp?.(next);
5086
+ },
5087
+ [mobileOpenProp, onMobileOpenChangeProp]
5088
+ );
5089
+ const effectiveCollapsed = isMobile ? false : collapsed;
5090
+ const {
5091
+ widthCollapsed,
5092
+ entriesVisible: phaseEntriesVisible,
5093
+ onEntriesBlurOutComplete,
5094
+ onPanelWidthComplete
5095
+ } = useSidebarCollapsePhase(effectiveCollapsed, !!reducedMotion);
5096
+ const entriesVisible = isMobile || phaseEntriesVisible;
5097
+ const isCollapsedRail = widthCollapsed && !isMobile;
5098
+ const iconOnlyLayout = studioSidebarIconOnlyLayout(isMobile, isCollapsedRail);
5099
+ const contextValue = useMemo8(
5100
+ () => ({
5101
+ collapsed: effectiveCollapsed,
5102
+ isMobile,
5103
+ isCollapsedRail,
5104
+ iconOnlyLayout
5105
+ }),
5106
+ [effectiveCollapsed, isMobile, isCollapsedRail, iconOnlyLayout]
5107
+ );
5108
+ return /* @__PURE__ */ jsx40(StudioSidebarContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx40(
5109
+ StudioSidebarPanel,
5110
+ {
5111
+ workforces,
5112
+ selectedId,
5113
+ onSelect: handleSelect,
5114
+ collapsed: effectiveCollapsed,
5115
+ onCollapsedChange: handleCollapsedChange,
5116
+ isMobile,
5117
+ mobileOpen,
5118
+ onMobileOpenChange: setMobileOpen,
5119
+ widthCollapsed,
5120
+ entriesVisible,
5121
+ onEntriesBlurOutComplete,
5122
+ onPanelWidthComplete,
5123
+ brand,
5124
+ emptyCaption
5125
+ }
5126
+ ) });
5127
+ };
5128
+
5129
+ // src/components/studio/sidebar-runtime-portal.tsx
5130
+ import { useCallback as useCallback7, useLayoutEffect, useState as useState12 } from "react";
5131
+ import { createPortal } from "react-dom";
5132
+ import { MessageSquarePlus } from "lucide-react";
5133
+ import { useThread as useThread2 } from "@assistant-ui/react";
5134
+ import { jsx as jsx41, jsxs as jsxs22 } from "react/jsx-runtime";
5135
+ var StudioSidebarRuntimePortal = ({
5136
+ label = "New chat"
5137
+ }) => {
5138
+ const { iconOnlyLayout } = useStudioSidebarLayout();
5139
+ const hasMessages = useThread2((s) => s.messages.length > 0);
5140
+ const { clear } = useTimbalRuntime();
5141
+ const [anchor, setAnchor] = useState12(null);
5142
+ const startNewChat = useCallback7(() => {
5143
+ clear();
5144
+ }, [clear]);
5145
+ useLayoutEffect(() => {
5146
+ setAnchor(document.getElementById(DOM_IDS.sidebarRuntimeAnchor));
5147
+ }, []);
5148
+ if (!anchor || !hasMessages) return null;
5149
+ const button = /* @__PURE__ */ jsxs22(
5150
+ "button",
5151
+ {
5152
+ type: "button",
5153
+ onClick: startNewChat,
5154
+ "aria-label": label,
5155
+ className: studioSidebarNavItemClasses(iconOnlyLayout, false),
5156
+ children: [
5157
+ /* @__PURE__ */ jsx41(MessageSquarePlus, { className: "size-3.5 shrink-0" }),
5158
+ !iconOnlyLayout ? /* @__PURE__ */ jsx41("span", { className: "min-w-0 truncate", children: label }) : null
5159
+ ]
5160
+ }
5161
+ );
5162
+ return createPortal(
5163
+ iconOnlyLayout ? /* @__PURE__ */ jsxs22(Tooltip, { children: [
5164
+ /* @__PURE__ */ jsx41(TooltipTrigger, { asChild: true, children: button }),
5165
+ /* @__PURE__ */ jsx41(TooltipContent, { side: "right", className: "text-xs", children: label })
5166
+ ] }) : button,
5167
+ anchor
5168
+ );
5169
+ };
5170
+
5171
+ // src/components/studio/welcome.tsx
5172
+ import { motion as motion9 } from "motion/react";
5173
+ import { useThread as useThread3 } from "@assistant-ui/react";
5174
+ import { jsx as jsx42, jsxs as jsxs23 } from "react/jsx-runtime";
5175
+ var luxuryEase2 = [0.16, 1, 0.3, 1];
5176
+ var welcomeStagger2 = {
5177
+ initial: {},
5178
+ animate: {
5179
+ transition: { staggerChildren: 0.16, delayChildren: 0.18 }
5180
+ }
5181
+ };
5182
+ var welcomeItem2 = {
5183
+ initial: { opacity: 0, y: 14 },
5184
+ animate: {
5185
+ opacity: 1,
5186
+ y: 0,
5187
+ transition: { duration: 0.9, ease: luxuryEase2 }
5188
+ }
5189
+ };
5190
+ var welcomeIcon2 = {
5191
+ initial: { opacity: 0, y: 10, scale: 0.96 },
5192
+ animate: {
5193
+ opacity: 1,
5194
+ y: 0,
5195
+ scale: 1,
5196
+ transition: { duration: 1.1, ease: luxuryEase2 }
5197
+ }
5198
+ };
5199
+ var StudioWelcome = ({ config, icon }) => {
5200
+ const isEmpty = useThread3((s) => s.messages.length === 0);
5201
+ if (!isEmpty) return null;
5202
+ const iconNode = icon ?? /* @__PURE__ */ jsx42(
5203
+ TimbalMark,
5204
+ {
5205
+ size: 112,
5206
+ className: "max-md:scale-[0.58] max-md:origin-center"
5207
+ }
5208
+ );
5209
+ return /* @__PURE__ */ jsx42("div", { className: "aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col", children: /* @__PURE__ */ jsx42("div", { className: "aui-thread-welcome-center flex w-full grow flex-col items-center justify-center", children: /* @__PURE__ */ jsxs23(
5210
+ motion9.div,
5211
+ {
5212
+ className: "aui-thread-welcome-message flex flex-col items-center justify-center px-2 text-center sm:px-4",
5213
+ variants: welcomeStagger2,
5214
+ initial: "initial",
5215
+ animate: "animate",
5216
+ children: [
5217
+ /* @__PURE__ */ jsx42(motion9.div, { variants: welcomeIcon2, className: "mb-4 md:mb-5", children: iconNode }),
5218
+ /* @__PURE__ */ jsx42(
5219
+ motion9.h1,
5220
+ {
5221
+ variants: welcomeItem2,
5222
+ className: "aui-thread-welcome-message-inner text-xl font-semibold sm:text-2xl",
5223
+ children: config?.heading ?? "How can I help you today?"
5224
+ }
5225
+ ),
5226
+ /* @__PURE__ */ jsx42(
5227
+ motion9.p,
5228
+ {
5229
+ variants: welcomeItem2,
5230
+ className: "aui-thread-welcome-message-inner mt-2 text-muted-foreground",
5231
+ children: config?.subheading ?? "Send a message to start a conversation."
5232
+ }
5233
+ )
5234
+ ]
5235
+ }
5236
+ ) }) });
5237
+ };
5238
+
5239
+ // src/components/studio/studio-shell.tsx
5240
+ import { Fragment as Fragment4, jsx as jsx43, jsxs as jsxs24 } from "react/jsx-runtime";
5241
+ import { createElement } from "react";
5242
+ var DEFAULT_BREAKPOINT_PX2 = 768;
5243
+ function readPersistedCollapsed2(key) {
5244
+ if (!key || typeof window === "undefined") return false;
5245
+ try {
5246
+ return window.localStorage.getItem(key) === "1";
5247
+ } catch {
5248
+ return false;
5249
+ }
5250
+ }
5251
+ function writePersistedCollapsed2(key, collapsed) {
5252
+ if (!key || typeof window === "undefined") return;
5253
+ try {
5254
+ window.localStorage.setItem(key, collapsed ? "1" : "0");
5255
+ } catch {
5256
+ }
5257
+ }
5258
+ function makeComposerWithPortal(BaseComposer) {
5259
+ const Resolved = BaseComposer ?? Composer;
5260
+ return function StudioComposerWithSidebar(props) {
5261
+ return /* @__PURE__ */ jsxs24(Fragment4, { children: [
5262
+ /* @__PURE__ */ jsx43(StudioSidebarRuntimePortal, {}),
5263
+ /* @__PURE__ */ jsx43(Resolved, { ...props })
5264
+ ] });
5265
+ };
5266
+ }
5267
+ var TimbalStudioShell = ({
5268
+ workforceId,
5269
+ workforces: workforcesProp,
5270
+ workforcesFetch,
5271
+ workforcesBaseUrl,
5272
+ brand,
5273
+ headerActions,
5274
+ headerStart,
5275
+ defaultCollapsed = false,
5276
+ persistKey = STORAGE_KEYS.sidebarCollapsed,
5277
+ mobileBreakpointPx = DEFAULT_BREAKPOINT_PX2,
5278
+ sidebarEmptyCaption = null,
5279
+ welcome,
5280
+ components,
5281
+ ...chatProps
5282
+ }) => {
5283
+ const reducedMotion = useReducedMotion6();
5284
+ const shouldFetchWorkforces = !workforceId && workforcesProp === void 0;
5285
+ const fetched = useWorkforces({
5286
+ enabled: shouldFetchWorkforces,
5287
+ fetch: workforcesFetch,
5288
+ baseUrl: workforcesBaseUrl
5289
+ });
5290
+ const workforces = workforcesProp ?? fetched.workforces;
5291
+ const [internalSelected, setInternalSelected] = useState13(
5292
+ workforceId ?? ""
5293
+ );
5294
+ useEffect10(() => {
5295
+ if (workforceId) return;
5296
+ if (internalSelected) return;
5297
+ const first = workforces[0]?.id ?? workforces[0]?.uid ?? workforces[0]?.name;
5298
+ if (first) setInternalSelected(first);
5299
+ }, [workforces, workforceId, internalSelected]);
5300
+ const activeWorkforceId = workforceId ?? internalSelected ?? fetched.selectedId ?? "";
5301
+ const [collapsed, setCollapsed] = useState13(() => {
5302
+ const persisted = readPersistedCollapsed2(persistKey);
5303
+ return persisted || defaultCollapsed;
5304
+ });
5305
+ const [isMobile, setIsMobile] = useState13(() => {
5306
+ if (typeof window === "undefined") return false;
5307
+ return window.innerWidth < mobileBreakpointPx;
5308
+ });
5309
+ const [mobileSidebarOpen, setMobileSidebarOpen] = useState13(false);
5310
+ useEffect10(() => {
5311
+ if (typeof window === "undefined") return;
5312
+ const onResize = () => setIsMobile(window.innerWidth < mobileBreakpointPx);
5313
+ onResize();
5314
+ window.addEventListener("resize", onResize);
5315
+ return () => window.removeEventListener("resize", onResize);
5316
+ }, [mobileBreakpointPx]);
5317
+ useEffect10(() => {
5318
+ if (!isMobile) setMobileSidebarOpen(false);
5319
+ }, [isMobile]);
5320
+ useEffect10(() => {
5321
+ if (!mobileSidebarOpen) return;
5322
+ const onKeyDown = (e) => {
5323
+ if (e.key === "Escape") setMobileSidebarOpen(false);
5324
+ };
5325
+ window.addEventListener("keydown", onKeyDown);
5326
+ return () => window.removeEventListener("keydown", onKeyDown);
5327
+ }, [mobileSidebarOpen]);
5328
+ const effectiveCollapsed = isMobile ? false : collapsed;
5329
+ const {
5330
+ widthCollapsed,
5331
+ entriesVisible: phaseEntriesVisible,
5332
+ onEntriesBlurOutComplete,
5333
+ onPanelWidthComplete
5334
+ } = useSidebarCollapsePhase(effectiveCollapsed, !!reducedMotion);
5335
+ const entriesVisible = isMobile || phaseEntriesVisible;
5336
+ const isCollapsedRail = widthCollapsed && !isMobile;
5337
+ const iconOnlyLayout = studioSidebarIconOnlyLayout(isMobile, isCollapsedRail);
5338
+ const layoutDirection = widthCollapsed ? "collapse" : "expand";
5339
+ const layoutTransition = studioSidebarWidthTransition(
5340
+ !!reducedMotion,
5341
+ layoutDirection
5342
+ );
5343
+ const desktopInsetPx = widthCollapsed ? SIDEBAR_INSET_PX_COLLAPSED : SIDEBAR_INSET_PX_EXPANDED;
5344
+ const onCollapsedChange = useCallback8(
5345
+ (next) => {
5346
+ setCollapsed(next);
5347
+ writePersistedCollapsed2(persistKey, next);
5348
+ },
5349
+ [persistKey]
5350
+ );
5351
+ const handleSelectWorkforce = useCallback8(
5352
+ (id) => {
5353
+ if (!workforceId) setInternalSelected(id);
5354
+ if (isMobile) setMobileSidebarOpen(false);
5355
+ },
5356
+ [workforceId, isMobile]
5357
+ );
5358
+ const sidebarContext = useMemo9(
5359
+ () => ({
5360
+ collapsed: effectiveCollapsed,
5361
+ isMobile,
5362
+ isCollapsedRail,
5363
+ iconOnlyLayout
5364
+ }),
5365
+ [effectiveCollapsed, isMobile, isCollapsedRail, iconOnlyLayout]
5366
+ );
5367
+ const resolvedComponents = useMemo9(() => {
5368
+ const next = { Welcome: StudioWelcome, ...components };
5369
+ next.Composer = makeComposerWithPortal(components?.Composer);
5370
+ return next;
5371
+ }, [components]);
5372
+ return /* @__PURE__ */ jsx43(StudioSidebarContext.Provider, { value: sidebarContext, children: /* @__PURE__ */ jsxs24(
5373
+ "div",
5374
+ {
5375
+ className: cn(
5376
+ "relative h-dvh overflow-hidden bg-background",
5377
+ isMobile && mobileSidebarOpen && "max-md:overflow-hidden"
5378
+ ),
5379
+ style: studioChromeShellStyle,
5380
+ children: [
5381
+ /* @__PURE__ */ jsx43(
5382
+ "div",
5383
+ {
5384
+ className: "pointer-events-none absolute inset-0 z-0 bg-background",
5385
+ "aria-hidden": true
5386
+ }
5387
+ ),
5388
+ /* @__PURE__ */ jsx43(
5389
+ "div",
5390
+ {
5391
+ className: cn(
5392
+ "pointer-events-none absolute inset-0 z-0",
5393
+ studioPlaygroundGradientClass
5394
+ ),
5395
+ "aria-hidden": true
5396
+ }
5397
+ ),
5398
+ /* @__PURE__ */ jsx43(
5399
+ StudioSidebarBackdrop,
5400
+ {
5401
+ open: isMobile && mobileSidebarOpen,
5402
+ onClose: () => setMobileSidebarOpen(false)
5403
+ }
5404
+ ),
5405
+ /* @__PURE__ */ jsx43(
5406
+ StudioSidebarPanel,
5407
+ {
5408
+ workforces,
5409
+ selectedId: activeWorkforceId,
5410
+ onSelect: handleSelectWorkforce,
5411
+ collapsed: effectiveCollapsed,
5412
+ onCollapsedChange,
5413
+ isMobile,
5414
+ mobileOpen: mobileSidebarOpen,
5415
+ onMobileOpenChange: setMobileSidebarOpen,
5416
+ widthCollapsed,
5417
+ entriesVisible,
5418
+ onEntriesBlurOutComplete,
5419
+ onPanelWidthComplete,
5420
+ brand,
5421
+ emptyCaption: sidebarEmptyCaption
5422
+ }
5423
+ ),
5424
+ /* @__PURE__ */ jsxs24(
5425
+ motion10.header,
5426
+ {
5427
+ className: cn(
5428
+ "absolute top-0 right-0 z-40 flex items-start justify-between gap-2",
5429
+ "px-3 pt-[var(--studio-topbar-gap)] md:px-4",
5430
+ "left-0"
5431
+ ),
5432
+ initial: false,
5433
+ animate: { left: isMobile ? 0 : desktopInsetPx },
5434
+ transition: layoutTransition,
5435
+ children: [
5436
+ /* @__PURE__ */ jsxs24(
5437
+ "div",
5438
+ {
5439
+ className: cn(
5440
+ "flex min-w-0 flex-1 items-center gap-2",
5441
+ studioTopbarPillHeightClass
5442
+ ),
5443
+ children: [
5444
+ isMobile && !mobileSidebarOpen ? /* @__PURE__ */ jsx43(
5445
+ TimbalV2Button,
5446
+ {
5447
+ variant: "secondary",
5448
+ size: "sm",
5449
+ isIconOnly: true,
5450
+ className: studioTopbarIconPillClass,
5451
+ onClick: () => setMobileSidebarOpen(true),
5452
+ "aria-label": "Open menu",
5453
+ "aria-expanded": false,
5454
+ children: /* @__PURE__ */ jsx43(Menu, { className: "size-4" })
5455
+ }
5456
+ ) : null,
5457
+ headerStart
5458
+ ]
5459
+ }
5460
+ ),
5461
+ headerActions ? /* @__PURE__ */ jsx43("div", { className: "flex shrink-0 items-center gap-1", children: headerActions }) : null
5462
+ ]
5463
+ }
5464
+ ),
5465
+ /* @__PURE__ */ jsx43(
5466
+ motion10.main,
5467
+ {
5468
+ className: cn(
5469
+ "relative z-10 flex h-full min-w-0 flex-col",
5470
+ "pt-[var(--studio-inset-top)]",
5471
+ "px-3 md:px-0"
5472
+ ),
5473
+ initial: false,
5474
+ animate: { paddingLeft: isMobile ? 12 : desktopInsetPx },
5475
+ transition: layoutTransition,
5476
+ children: activeWorkforceId ? /* @__PURE__ */ createElement(
5477
+ TimbalChat,
5478
+ {
5479
+ ...chatProps,
5480
+ workforceId: activeWorkforceId,
5481
+ key: activeWorkforceId,
5482
+ welcome,
5483
+ components: resolvedComponents,
5484
+ className: cn("min-h-0 flex-1 bg-transparent", chatProps.className)
5485
+ }
5486
+ ) : null
5487
+ }
5488
+ )
5489
+ ]
5490
+ }
5491
+ ) });
5492
+ };
5493
+
5494
+ // src/components/studio/mode-toggle.tsx
5495
+ import { useCallback as useCallback9, useEffect as useEffect11, useState as useState14 } from "react";
5496
+ import { Moon, Sun } from "lucide-react";
5497
+ import { jsx as jsx44, jsxs as jsxs25 } from "react/jsx-runtime";
5498
+ var ModeToggle = ({
5499
+ theme,
5500
+ setTheme,
5501
+ className,
5502
+ label = "Toggle theme"
5503
+ }) => {
5504
+ const isControlled = theme !== void 0;
5505
+ const [internalIsDark, setInternalIsDark] = useState14(false);
5506
+ useEffect11(() => {
5507
+ if (isControlled) return;
5508
+ if (typeof document === "undefined") return;
5509
+ setInternalIsDark(document.documentElement.classList.contains("dark"));
5510
+ }, [isControlled]);
5511
+ const isDark = isControlled ? theme === "dark" : internalIsDark;
5512
+ const onClick = useCallback9(() => {
5513
+ const next = isDark ? "light" : "dark";
5514
+ if (setTheme) {
5515
+ setTheme(next);
5516
+ return;
5517
+ }
5518
+ if (typeof document === "undefined") return;
5519
+ document.documentElement.classList.toggle("dark", next === "dark");
5520
+ setInternalIsDark(next === "dark");
5521
+ }, [isDark, setTheme]);
5522
+ return /* @__PURE__ */ jsxs25(
5523
+ TimbalV2Button,
5524
+ {
5525
+ variant: "secondary",
5526
+ size: "sm",
5527
+ isIconOnly: true,
5528
+ onClick,
5529
+ className: cn(
5530
+ studioTopbarPillHeightClass,
5531
+ studioTopbarIconPillClass,
5532
+ "relative",
5533
+ className
5534
+ ),
5535
+ "aria-label": label,
5536
+ title: label,
5537
+ children: [
5538
+ /* @__PURE__ */ jsx44(Sun, { className: "size-3.5 scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" }),
5539
+ /* @__PURE__ */ jsx44(Moon, { className: "absolute size-3.5 scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" }),
5540
+ /* @__PURE__ */ jsx44("span", { className: "sr-only", children: label })
5541
+ ]
5542
+ }
5543
+ );
5544
+ };
5545
+
5546
+ // src/artifacts/agent-instructions.ts
5547
+ var ARTIFACT_AGENT_INSTRUCTIONS = `
5548
+ ## Rich artifacts (Timbal chat UI)
5549
+
5550
+ When you need charts, tables, choice widgets, or interactive controls, return a **JSON artifact object** instead of plain prose. The chat UI renders these automatically.
5551
+
5552
+ ### Delivery channels (either works)
5553
+
5554
+ 1. **Tool result (preferred)** \u2014 return a single JSON object (or a JSON string) from a tool. The object must include a string \`type\` field.
5555
+ 2. **Inline markdown fence** \u2014 embed the same JSON inside a fenced block:
5556
+
5557
+ \`\`\`timbal-artifact
5558
+ {"type":"chart","data":[{"month":"Jan","sales":120}]}
5559
+ \`\`\`
5560
+
5561
+ The alias \`\`\`timbal\`\`\` is also accepted.
5562
+
5563
+ ### Built-in artifact types
5564
+
5565
+ | \`type\` | Use for |
5566
+ |---|---|
5567
+ | \`chart\` | Bar, line, area, or pie charts. Fields: \`data\`, optional \`chartType\`, \`xKey\`, \`dataKey\`, \`title\`, \`unit\`. |
5568
+ | \`table\` | Tabular data. Fields: \`rows\`, optional \`columns\`, \`title\`. |
5569
+ | \`question\` | In-thread multiple choice. Fields: \`options: [{ id, label, description? }]\`, optional \`prompt\`, \`multi\`. User replies are sent back as a normal user message. |
5570
+ | \`html\` | Custom HTML/CSS/JS in an iframe. Fields: \`content\` (HTML document or fragment), optional \`title\`, \`height\`, \`sandboxed\` (default \`true\`; set \`false\` for unrestricted scripts/CDN). |
5571
+ | \`json\` | Fallback structured view. Fields: \`data\`, optional \`title\`. |
5572
+ | \`ui\` | **Interactive UI** composed from a fixed node palette (hover, click, drag). See below. |
5573
+
5574
+ ### When to use \`type: "html"\`
5575
+
5576
+ Use \`html\` when the user wants a **rich visual or interactive page** that does not fit the \`ui\` palette \u2014 e.g. styled layouts, SVG graphics, CSS animations, canvas, small games, calculators, or multi-section mockups. Pass a full HTML document or a body fragment in \`content\`.
5577
+
5578
+ - Inline \`<style>\`, \`<script>\`, SVG, and canvas are supported.
5579
+ - Default \`sandboxed: true\` runs in an isolated iframe with scripts enabled.
5580
+ - Set \`sandboxed: false\` only for trusted content that needs external CDN scripts/styles or full DOM freedom.
5581
+ - Prefer \`ui\` when controls should send chat messages or host events; prefer \`html\` for self-contained mini-apps and visual demos.
5582
+
5583
+ ### When to use \`type: "ui"\`
5584
+
5585
+ Use a \`ui\` artifact when the user should **hover, click, drag, or adjust controls** in-thread and those actions should integrate with the chat runtime (messages, \`onArtifactEvent\`). For standalone visual/interactive HTML, use \`html\` instead.
5586
+
5587
+ Each \`ui\` artifact has:
5588
+
5589
+ - \`initialState\` \u2014 optional object seeding local state (per widget instance).
5590
+ - \`root\` \u2014 a single node tree (see node kinds below).
5591
+ - optional \`title\` \u2014 card heading.
5592
+
5593
+ **Bindings:** anywhere a primitive is accepted, you may use \`{ "$bind": "dotted.path" }\` to read from \`initialState\` / local state (e.g. \`{ "$bind": "qty" }\`).
5594
+
5595
+ **Actions:** nodes may attach \`onClick\`, \`onChange\`, or \`onDragEnd\` with one action or an array:
5596
+
5597
+ | Action | Shape | Effect |
5598
+ |---|---|---|
5599
+ | User message | \`{ "kind": "message", "text": "..." }\` or \`{ "kind": "message", "text": { "$bind": "path" } }\` | Sends text as the next user message. |
5600
+ | Set state | \`{ "kind": "set", "path": "foo", "value": 1 }\` | Writes local widget state. |
5601
+ | Toggle boolean | \`{ "kind": "toggle", "path": "enabled" }\` | Flips a boolean at \`path\`. |
5602
+ | Host event | \`{ "kind": "emit", "name": "event-name", "payload": { ... } }\` | Bubbles to the host app (\`onArtifactEvent\` on \`<Thread>\`). |
5603
+
5604
+ ### \`ui\` node palette (\`root.kind\`)
5605
+
5606
+ | \`kind\` | Purpose | Key fields |
5607
+ |---|---|---|
5608
+ | \`box\` | Layout container | \`children\`, \`direction\` (\`row\`/\`col\`), \`gap\`, \`padding\`, \`align\`, \`justify\`, \`wrap\` |
5609
+ | \`text\` | Body text | \`value\`, optional \`muted\`, \`size\`, \`weight\` |
5610
+ | \`heading\` | Heading | \`value\`, optional \`level\` (1\u20134) |
5611
+ | \`badge\` | Pill label | \`value\`, optional \`tone\` (\`default\`, \`primary\`, \`success\`, \`warn\`, \`danger\`) |
5612
+ | \`button\` | Clickable button | \`label\`, optional \`variant\`, \`size\`, \`disabled\`, \`onClick\` |
5613
+ | \`toggle\` | Boolean switch | \`binding\` (state path), optional \`label\`, \`onChange\` |
5614
+ | \`slider\` | Numeric range | \`binding\`, optional \`min\`, \`max\`, \`step\`, \`label\`, \`showValue\`, \`onChange\` |
5615
+ | \`tooltip\` | Hover tooltip | \`content\`, \`child\` (single node), optional \`side\` |
5616
+ | \`draggable\` | Drag gesture | \`child\`, optional \`axis\` (\`x\`/\`y\`/\`both\`), \`snapBack\`, \`onDragEnd\` |
5617
+ | \`custom\` | Host-registered widget | \`name\`, optional \`props\`, \`children\` \u2014 only if the app registered that name |
5618
+
5619
+ ### Example \`ui\` artifact
5620
+
5621
+ \`\`\`json
5622
+ {
5623
+ "type": "ui",
5624
+ "title": "Configure plan",
5625
+ "initialState": { "qty": 1, "premium": false },
5626
+ "root": {
5627
+ "kind": "box",
5628
+ "direction": "col",
5629
+ "gap": 3,
5630
+ "children": [
5631
+ { "kind": "heading", "value": "Choose quantity", "level": 3 },
5632
+ {
5633
+ "kind": "tooltip",
5634
+ "content": "Drag to adjust quantity",
5635
+ "child": {
5636
+ "kind": "slider",
5637
+ "binding": "qty",
5638
+ "min": 1,
5639
+ "max": 50,
5640
+ "label": "Quantity",
5641
+ "onChange": { "kind": "emit", "name": "qty-changed" }
5642
+ }
5643
+ },
5644
+ { "kind": "toggle", "binding": "premium", "label": "Premium support" },
5645
+ {
5646
+ "kind": "button",
5647
+ "label": "Confirm",
5648
+ "onClick": { "kind": "message", "text": { "$bind": "qty" } }
5649
+ }
5650
+ ]
5651
+ }
5652
+ }
5653
+ \`\`\`
5654
+
5655
+ ### Rules
5656
+
5657
+ - Always set \`type\` to a built-in value above unless the app documented a custom type.
5658
+ - Prefer \`ui\` over \`html\` when actions must bubble to the host chat (\`message\`, \`emit\`).
5659
+ - Prefer \`question\` for simple A/B/C choices; use \`ui\` when you need sliders, toggles, drag, or multi-control layouts.
5660
+ - Keep \`data\` arrays reasonably small (charts/tables).
5661
+
5662
+ ### After calling an artifact tool (critical)
5663
+
5664
+ When you call a tool that returns an artifact (\`make_chart\`, \`ask_question\`, \`show_table\`, \`show_html\`, \`make_ui_demo\`, etc.):
5665
+
5666
+ 1. **Do not** paste, quote, paraphrase as JSON, or fence the tool return value in your assistant message. The chat UI already renders it from the tool result.
5667
+ 2. **Do not** emit a matching \`\`\`timbal-artifact\`\`\` block for the same payload \u2014 pick **one** channel (tool result only).
5668
+ 3. Your follow-up text should be **empty**, or at most **one short sentence** (e.g. "Pick an option above." / "Try the controls."). Never include \`type\`, \`options\`, \`data\`, or dict/JSON syntax.
5669
+ 4. Treat the widget as visible to the user; refer to it as "above" / "the chart" / "the choices" \u2014 never reproduce its contents.
5670
+ `.trim();
5671
+
5672
+ // src/auth/guard.tsx
5673
+ import { Loader2 } from "lucide-react";
5674
+ import { jsx as jsx45 } from "react/jsx-runtime";
5675
+ var AuthGuard = ({
5676
+ children,
5677
+ requireAuth = false,
5678
+ enabled = true
5679
+ }) => {
5680
+ const { isAuthenticated, loading, isEmbedded } = useSession();
5681
+ if (!enabled) {
5682
+ return children;
5683
+ }
5684
+ if (loading) {
5685
+ return /* @__PURE__ */ jsx45("div", { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsx45(Loader2, { className: "w-8 h-8 animate-spin" }) });
5686
+ }
5687
+ if (requireAuth && !isAuthenticated && !isEmbedded) {
5688
+ const returnTo = encodeURIComponent(
5689
+ window.location.pathname + window.location.search
5690
+ );
5691
+ window.location.href = `/api/auth/login?return_to=${returnTo}`;
5692
+ return null;
5693
+ }
5694
+ return children;
5695
+ };
5696
+
5697
+ // src/index.ts
5698
+ import {
5699
+ ThreadPrimitive as ThreadPrimitive2,
5700
+ MessagePrimitive as MessagePrimitive3,
5701
+ ComposerPrimitive as ComposerPrimitive4,
5702
+ ActionBarPrimitive as ActionBarPrimitive2,
5703
+ AuiIf as AuiIf3,
5704
+ AssistantRuntimeProvider as AssistantRuntimeProvider2,
5705
+ useThread as useThread4,
5706
+ useThreadRuntime as useThreadRuntime4,
5707
+ useMessageRuntime,
5708
+ useComposerRuntime as useComposerRuntime2
5709
+ } from "@assistant-ui/react";
5710
+ export {
5711
+ ARTIFACT_AGENT_INSTRUCTIONS,
5712
+ ARTIFACT_FENCE_LANGUAGES,
5713
+ ActionBarPrimitive2 as ActionBarPrimitive,
5714
+ ArtifactCard,
5715
+ ArtifactRegistryProvider,
5716
+ ArtifactView,
5717
+ AssistantRuntimeProvider2 as AssistantRuntimeProvider,
5718
+ AuiIf3 as AuiIf,
5719
+ AuthGuard,
5720
+ Avatar,
4322
5721
  AvatarFallback,
4323
5722
  AvatarImage,
4324
5723
  Button,
4325
5724
  ChartArtifactView,
4326
5725
  Composer,
4327
- ComposerAddAttachment,
4328
- ComposerAttachments,
4329
5726
  ComposerPrimitive4 as ComposerPrimitive,
4330
5727
  DEFAULT_UPLOAD_ACCEPT,
4331
5728
  Dialog,
@@ -4335,44 +5732,27 @@ export {
4335
5732
  DialogPortal,
4336
5733
  DialogTitle,
4337
5734
  DialogTrigger,
4338
- ErrorPrimitive2 as ErrorPrimitive,
4339
5735
  HtmlArtifactView,
4340
5736
  JsonArtifactView,
4341
5737
  MarkdownText,
4342
- MessagePartPrimitive2 as MessagePartPrimitive,
4343
5738
  MessagePrimitive3 as MessagePrimitive,
5739
+ ModeToggle,
4344
5740
  QuestionArtifactView,
4345
- STUDIO_INSET_LEFT,
4346
- STUDIO_PILL_HEIGHT,
4347
- STUDIO_SIDEBAR_GAP,
4348
- STUDIO_SIDEBAR_WIDTH,
4349
- STUDIO_TOPBAR_GAP,
4350
- STUDIO_TOPBAR_HEIGHT,
4351
5741
  SessionProvider,
4352
5742
  Shimmer,
5743
+ StudioSidebar,
5744
+ StudioWelcome,
4353
5745
  Suggestions,
4354
- syntax_highlighter_default as SyntaxHighlighter,
4355
- TIMBAL_V2_BORDER,
4356
- TIMBAL_V2_FILL,
4357
- TIMBAL_V2_LABEL,
4358
- TIMBAL_V2_PILL_SURFACE,
4359
- TIMBAL_V2_SECONDARY_CHROME,
4360
- TIMBAL_V2_SHADOW,
4361
- TIMBAL_V2_SIZE_HEIGHT,
4362
- TIMBAL_V2_SIZE_ICON,
4363
- TIMBAL_V2_SIZE_LABEL_PX,
4364
5746
  TableArtifactView,
4365
5747
  Thread,
4366
5748
  ThreadPrimitive2 as ThreadPrimitive,
4367
5749
  TimbalChat,
4368
5750
  TimbalChatShell,
5751
+ TimbalMark,
4369
5752
  TimbalRuntimeProvider,
4370
- TimbalV2Button,
5753
+ TimbalStudioShell,
4371
5754
  ToolArtifactFallback,
4372
- ToolBodyPresence,
4373
5755
  ToolFallback,
4374
- ToolMotion,
4375
- ToolPresence,
4376
5756
  Tooltip,
4377
5757
  TooltipContent,
4378
5758
  TooltipIconButton,
@@ -4382,10 +5762,8 @@ export {
4382
5762
  UiCustomNodeRegistryProvider,
4383
5763
  UiEventProvider,
4384
5764
  UiNodeView,
4385
- UserMessageAttachments,
4386
5765
  WorkforceSelector,
4387
5766
  authFetch,
4388
- buttonVariants,
4389
5767
  clearTokens,
4390
5768
  cn,
4391
5769
  createDefaultAttachmentAdapter,
@@ -4399,7 +5777,6 @@ export {
4399
5777
  isArtifact,
4400
5778
  isArtifactFenceLanguage,
4401
5779
  isUiBinding,
4402
- luxuryEase,
4403
5780
  parseArtifactFromToolResult,
4404
5781
  parseSSELine2 as parseSSELine,
4405
5782
  refreshAccessToken,
@@ -4409,38 +5786,13 @@ export {
4409
5786
  setPath,
4410
5787
  setRefreshToken,
4411
5788
  splitMarkdownByArtifacts,
4412
- studioArtifactShellClass,
4413
- studioChromeShellStyle,
4414
- studioComposeInputShellClass,
4415
- studioComposerIoWellClass,
4416
- studioIntegrationBorder,
4417
- studioIntegrationCardClass,
4418
- studioIntegrationIconTileClass,
4419
- studioIntegrationSurfaceSolid,
4420
- studioListRowButtonClass,
4421
- studioPillSurfaceClass,
4422
- studioPlaygroundGradientClass,
4423
- studioQuestionOptionClass,
4424
- studioQuestionOptionSelectedClass,
4425
- studioSecondaryChromeClass,
4426
- studioTimelineActionClass,
4427
- studioTimelineBodyPadClass,
4428
- studioTimelineChevronClass,
4429
- studioTimelineDetailClass,
4430
- studioTimelineRowButtonClass,
4431
- studioTimelineShimmerActionClass,
4432
- studioTimelineTextClass,
4433
- studioToolCardShellClass,
4434
- studioTopbarIconPillClass,
4435
- studioTopbarPillHeightClass,
4436
- toolPresenceTransition,
4437
5789
  useArtifactRegistry,
4438
- useAuiState3 as useAuiState,
4439
5790
  useComposerRuntime2 as useComposerRuntime,
4440
5791
  useMessageRuntime,
5792
+ useOptionalSession,
4441
5793
  useResolvedSuggestions,
4442
5794
  useSession,
4443
- useThread2 as useThread,
5795
+ useThread4 as useThread,
4444
5796
  useThreadRuntime4 as useThreadRuntime,
4445
5797
  useTimbalRuntime,
4446
5798
  useTimbalStream,