@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.cjs CHANGED
@@ -32,13 +32,12 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  ARTIFACT_AGENT_INSTRUCTIONS: () => ARTIFACT_AGENT_INSTRUCTIONS,
34
34
  ARTIFACT_FENCE_LANGUAGES: () => ARTIFACT_FENCE_LANGUAGES,
35
- ActionBarMorePrimitive: () => import_react29.ActionBarMorePrimitive,
36
- ActionBarPrimitive: () => import_react29.ActionBarPrimitive,
35
+ ActionBarPrimitive: () => import_react45.ActionBarPrimitive,
37
36
  ArtifactCard: () => ArtifactCard,
38
37
  ArtifactRegistryProvider: () => ArtifactRegistryProvider,
39
38
  ArtifactView: () => ArtifactView,
40
- AssistantRuntimeProvider: () => import_react29.AssistantRuntimeProvider,
41
- AuiIf: () => import_react29.AuiIf,
39
+ AssistantRuntimeProvider: () => import_react45.AssistantRuntimeProvider,
40
+ AuiIf: () => import_react45.AuiIf,
42
41
  AuthGuard: () => AuthGuard,
43
42
  Avatar: () => Avatar,
44
43
  AvatarFallback: () => AvatarFallback,
@@ -46,9 +45,7 @@ __export(index_exports, {
46
45
  Button: () => Button,
47
46
  ChartArtifactView: () => ChartArtifactView,
48
47
  Composer: () => Composer,
49
- ComposerAddAttachment: () => ComposerAddAttachment,
50
- ComposerAttachments: () => ComposerAttachments,
51
- ComposerPrimitive: () => import_react29.ComposerPrimitive,
48
+ ComposerPrimitive: () => import_react45.ComposerPrimitive,
52
49
  DEFAULT_UPLOAD_ACCEPT: () => DEFAULT_UPLOAD_ACCEPT,
53
50
  Dialog: () => Dialog,
54
51
  DialogClose: () => DialogClose,
@@ -57,44 +54,27 @@ __export(index_exports, {
57
54
  DialogPortal: () => DialogPortal,
58
55
  DialogTitle: () => DialogTitle,
59
56
  DialogTrigger: () => DialogTrigger,
60
- ErrorPrimitive: () => import_react29.ErrorPrimitive,
61
57
  HtmlArtifactView: () => HtmlArtifactView,
62
58
  JsonArtifactView: () => JsonArtifactView,
63
59
  MarkdownText: () => MarkdownText,
64
- MessagePartPrimitive: () => import_react29.MessagePartPrimitive,
65
- MessagePrimitive: () => import_react29.MessagePrimitive,
60
+ MessagePrimitive: () => import_react45.MessagePrimitive,
61
+ ModeToggle: () => ModeToggle,
66
62
  QuestionArtifactView: () => QuestionArtifactView,
67
- STUDIO_INSET_LEFT: () => STUDIO_INSET_LEFT,
68
- STUDIO_PILL_HEIGHT: () => STUDIO_PILL_HEIGHT,
69
- STUDIO_SIDEBAR_GAP: () => STUDIO_SIDEBAR_GAP,
70
- STUDIO_SIDEBAR_WIDTH: () => STUDIO_SIDEBAR_WIDTH,
71
- STUDIO_TOPBAR_GAP: () => STUDIO_TOPBAR_GAP,
72
- STUDIO_TOPBAR_HEIGHT: () => STUDIO_TOPBAR_HEIGHT,
73
63
  SessionProvider: () => SessionProvider,
74
64
  Shimmer: () => Shimmer,
65
+ StudioSidebar: () => StudioSidebar,
66
+ StudioWelcome: () => StudioWelcome,
75
67
  Suggestions: () => Suggestions,
76
- SyntaxHighlighter: () => syntax_highlighter_default,
77
- TIMBAL_V2_BORDER: () => TIMBAL_V2_BORDER,
78
- TIMBAL_V2_FILL: () => TIMBAL_V2_FILL,
79
- TIMBAL_V2_LABEL: () => TIMBAL_V2_LABEL,
80
- TIMBAL_V2_PILL_SURFACE: () => TIMBAL_V2_PILL_SURFACE,
81
- TIMBAL_V2_SECONDARY_CHROME: () => TIMBAL_V2_SECONDARY_CHROME,
82
- TIMBAL_V2_SHADOW: () => TIMBAL_V2_SHADOW,
83
- TIMBAL_V2_SIZE_HEIGHT: () => TIMBAL_V2_SIZE_HEIGHT,
84
- TIMBAL_V2_SIZE_ICON: () => TIMBAL_V2_SIZE_ICON,
85
- TIMBAL_V2_SIZE_LABEL_PX: () => TIMBAL_V2_SIZE_LABEL_PX,
86
68
  TableArtifactView: () => TableArtifactView,
87
69
  Thread: () => Thread,
88
- ThreadPrimitive: () => import_react29.ThreadPrimitive,
70
+ ThreadPrimitive: () => import_react45.ThreadPrimitive,
89
71
  TimbalChat: () => TimbalChat,
90
72
  TimbalChatShell: () => TimbalChatShell,
73
+ TimbalMark: () => TimbalMark,
91
74
  TimbalRuntimeProvider: () => TimbalRuntimeProvider,
92
- TimbalV2Button: () => TimbalV2Button,
75
+ TimbalStudioShell: () => TimbalStudioShell,
93
76
  ToolArtifactFallback: () => ToolArtifactFallback,
94
- ToolBodyPresence: () => ToolBodyPresence,
95
77
  ToolFallback: () => ToolFallback,
96
- ToolMotion: () => ToolMotion,
97
- ToolPresence: () => ToolPresence,
98
78
  Tooltip: () => Tooltip,
99
79
  TooltipContent: () => TooltipContent,
100
80
  TooltipIconButton: () => TooltipIconButton,
@@ -104,10 +84,8 @@ __export(index_exports, {
104
84
  UiCustomNodeRegistryProvider: () => UiCustomNodeRegistryProvider,
105
85
  UiEventProvider: () => UiEventProvider,
106
86
  UiNodeView: () => UiNodeView,
107
- UserMessageAttachments: () => UserMessageAttachments,
108
87
  WorkforceSelector: () => WorkforceSelector,
109
88
  authFetch: () => authFetch,
110
- buttonVariants: () => buttonVariants,
111
89
  clearTokens: () => clearTokens,
112
90
  cn: () => cn,
113
91
  createDefaultAttachmentAdapter: () => createDefaultAttachmentAdapter,
@@ -121,7 +99,6 @@ __export(index_exports, {
121
99
  isArtifact: () => isArtifact,
122
100
  isArtifactFenceLanguage: () => isArtifactFenceLanguage,
123
101
  isUiBinding: () => isUiBinding,
124
- luxuryEase: () => luxuryEase,
125
102
  parseArtifactFromToolResult: () => parseArtifactFromToolResult,
126
103
  parseSSELine: () => import_timbal_sdk2.parseSSELine,
127
104
  refreshAccessToken: () => refreshAccessToken,
@@ -131,39 +108,14 @@ __export(index_exports, {
131
108
  setPath: () => setPath,
132
109
  setRefreshToken: () => setRefreshToken,
133
110
  splitMarkdownByArtifacts: () => splitMarkdownByArtifacts,
134
- studioArtifactShellClass: () => studioArtifactShellClass,
135
- studioChromeShellStyle: () => studioChromeShellStyle,
136
- studioComposeInputShellClass: () => studioComposeInputShellClass,
137
- studioComposerIoWellClass: () => studioComposerIoWellClass,
138
- studioIntegrationBorder: () => studioIntegrationBorder,
139
- studioIntegrationCardClass: () => studioIntegrationCardClass,
140
- studioIntegrationIconTileClass: () => studioIntegrationIconTileClass,
141
- studioIntegrationSurfaceSolid: () => studioIntegrationSurfaceSolid,
142
- studioListRowButtonClass: () => studioListRowButtonClass,
143
- studioPillSurfaceClass: () => studioPillSurfaceClass,
144
- studioPlaygroundGradientClass: () => studioPlaygroundGradientClass,
145
- studioQuestionOptionClass: () => studioQuestionOptionClass,
146
- studioQuestionOptionSelectedClass: () => studioQuestionOptionSelectedClass,
147
- studioSecondaryChromeClass: () => studioSecondaryChromeClass,
148
- studioTimelineActionClass: () => studioTimelineActionClass,
149
- studioTimelineBodyPadClass: () => studioTimelineBodyPadClass,
150
- studioTimelineChevronClass: () => studioTimelineChevronClass,
151
- studioTimelineDetailClass: () => studioTimelineDetailClass,
152
- studioTimelineRowButtonClass: () => studioTimelineRowButtonClass,
153
- studioTimelineShimmerActionClass: () => studioTimelineShimmerActionClass,
154
- studioTimelineTextClass: () => studioTimelineTextClass,
155
- studioToolCardShellClass: () => studioToolCardShellClass,
156
- studioTopbarIconPillClass: () => studioTopbarIconPillClass,
157
- studioTopbarPillHeightClass: () => studioTopbarPillHeightClass,
158
- toolPresenceTransition: () => toolPresenceTransition,
159
111
  useArtifactRegistry: () => useArtifactRegistry,
160
- useAuiState: () => import_react29.useAuiState,
161
- useComposerRuntime: () => import_react29.useComposerRuntime,
162
- useMessageRuntime: () => import_react29.useMessageRuntime,
112
+ useComposerRuntime: () => import_react45.useComposerRuntime,
113
+ useMessageRuntime: () => import_react45.useMessageRuntime,
114
+ useOptionalSession: () => useOptionalSession,
163
115
  useResolvedSuggestions: () => useResolvedSuggestions,
164
116
  useSession: () => useSession,
165
- useThread: () => import_react29.useThread,
166
- useThreadRuntime: () => import_react29.useThreadRuntime,
117
+ useThread: () => import_react45.useThread,
118
+ useThreadRuntime: () => import_react45.useThreadRuntime,
167
119
  useTimbalRuntime: () => useTimbalRuntime,
168
120
  useTimbalStream: () => useTimbalStream,
169
121
  useToolRunning: () => useToolRunning,
@@ -1034,9 +986,10 @@ function findParentIdFromAuiParent(messages, auiParentId) {
1034
986
  var import_timbal_sdk2 = require("@timbal-ai/timbal-sdk");
1035
987
 
1036
988
  // src/components/thread.tsx
1037
- var import_react25 = require("@assistant-ui/react");
989
+ var import_react25 = require("react");
990
+ var import_react26 = require("@assistant-ui/react");
1038
991
  var import_lucide_react8 = require("lucide-react");
1039
- var import_react26 = require("motion/react");
992
+ var import_react27 = require("motion/react");
1040
993
 
1041
994
  // src/components/attachment.tsx
1042
995
  var import_react4 = require("react");
@@ -1250,7 +1203,7 @@ var import_react3 = require("react");
1250
1203
  var React = __toESM(require("react"), 1);
1251
1204
  var import_radix_ui4 = require("radix-ui");
1252
1205
 
1253
- // src/ui/timbal-v2-button-tokens.ts
1206
+ // src/design/button-tokens.ts
1254
1207
  var TIMBAL_V2_SIZE_HEIGHT = {
1255
1208
  xs: "min-h-8 h-8",
1256
1209
  sm: "min-h-9 h-9",
@@ -1270,39 +1223,62 @@ var TIMBAL_V2_SIZE_LABEL_PX = {
1270
1223
  lg: "px-6"
1271
1224
  };
1272
1225
  var TIMBAL_V2_FILL = {
1273
- 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",
1274
- informative: "bg-blue-600 group-active/tbv2:[background-image:linear-gradient(to_top,rgba(0,0,0,0.08),transparent_55%)]",
1275
- 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",
1276
- 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]",
1277
- 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",
1226
+ primary: [
1227
+ "bg-gradient-to-b from-primary-fill-from to-primary-fill-to",
1228
+ "group-hover/tbv2:from-primary-fill-hover-from group-hover/tbv2:to-primary-fill-hover-to",
1229
+ "group-active/tbv2:from-primary-fill-active-from group-active/tbv2:to-primary-fill-active-to"
1230
+ ].join(" "),
1231
+ informative: [
1232
+ "bg-primary",
1233
+ "group-active/tbv2:[background-image:linear-gradient(to_top,rgba(0,0,0,0.08),transparent_55%)]"
1234
+ ].join(" "),
1235
+ destructive: [
1236
+ "bg-gradient-to-b from-elevated-from to-elevated-to",
1237
+ "group-hover/tbv2:from-destructive-fill-hover-from group-hover/tbv2:to-destructive-fill-hover-to",
1238
+ "group-active/tbv2:from-destructive-fill-active-from group-active/tbv2:to-destructive-fill-active-to"
1239
+ ].join(" "),
1240
+ secondary: [
1241
+ "bg-gradient-to-b from-elevated-from to-elevated-to",
1242
+ "group-hover/tbv2:from-secondary-fill-hover-from group-hover/tbv2:to-secondary-fill-hover-to",
1243
+ "group-active/tbv2:from-secondary-fill-active-from group-active/tbv2:to-secondary-fill-active-to"
1244
+ ].join(" "),
1245
+ ghost: [
1246
+ "bg-transparent",
1247
+ "group-hover/tbv2:bg-ghost-fill-hover",
1248
+ "group-active/tbv2:bg-ghost-fill-active"
1249
+ ].join(" "),
1278
1250
  link: "bg-transparent"
1279
1251
  };
1280
1252
  var TIMBAL_V2_LABEL = {
1281
- primary: "text-white dark:text-neutral-900",
1282
- informative: "text-white",
1283
- destructive: "text-destructive dark:text-red-400",
1253
+ primary: "text-primary-foreground",
1254
+ informative: "text-primary-foreground",
1255
+ destructive: "text-destructive",
1284
1256
  secondary: "text-foreground",
1285
1257
  ghost: "text-foreground",
1286
- 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"
1258
+ link: "text-foreground underline decoration-foreground/25 underline-offset-2 group-hover/tbv2:decoration-foreground/45"
1287
1259
  };
1288
1260
  var TIMBAL_V2_BORDER = {
1289
1261
  primary: "",
1290
- informative: "border border-white/15 dark:border-white/10",
1291
- destructive: "border border-destructive/45 dark:border-red-500/55",
1292
- secondary: "border border-neutral-200/80 dark:border-white/[0.08]",
1262
+ informative: "border border-foreground/15",
1263
+ destructive: "border border-destructive/45",
1264
+ secondary: "border border-border",
1293
1265
  ghost: "",
1294
1266
  link: ""
1295
1267
  };
1296
1268
  var TIMBAL_V2_SHADOW = {
1297
- primary: "shadow-sm shadow-black/15 dark:shadow-black/40",
1298
- informative: "shadow-sm shadow-blue-900/20 dark:shadow-black/40",
1299
- destructive: "shadow-[0_1px_2px_-0.5px_rgba(0,0,0,0.05)] dark:shadow-[0_1px_3px_rgba(0,0,0,0.22)]",
1300
- secondary: "shadow-[0_1px_2px_-0.5px_rgba(0,0,0,0.05)] dark:shadow-[0_1px_3px_rgba(0,0,0,0.22)]",
1269
+ primary: "shadow-card",
1270
+ informative: "shadow-card",
1271
+ destructive: "shadow-card",
1272
+ secondary: "shadow-card",
1301
1273
  ghost: "",
1302
1274
  link: ""
1303
1275
  };
1304
- 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)]";
1305
- 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]";
1276
+ var TIMBAL_V2_SECONDARY_CHROME = [
1277
+ "bg-gradient-to-b from-elevated-from to-elevated-to border border-border shadow-card",
1278
+ "transition-[background-color,box-shadow,border-color] duration-200 ease-in-out",
1279
+ "hover:from-secondary-fill-hover-from hover:to-secondary-fill-hover-to",
1280
+ "active:from-secondary-fill-active-from active:to-secondary-fill-active-to"
1281
+ ].join(" ");
1306
1282
 
1307
1283
  // src/ui/timbal-v2-button.tsx
1308
1284
  var import_jsx_runtime5 = require("react/jsx-runtime");
@@ -1334,7 +1310,7 @@ var TimbalV2Button = React.forwardRef(function TimbalV2Button2({
1334
1310
  "data-variant": variant,
1335
1311
  className: cn(
1336
1312
  "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",
1337
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-400/60 focus-visible:ring-offset-1 focus-visible:ring-offset-background",
1313
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/60 focus-visible:ring-offset-1 focus-visible:ring-offset-background",
1338
1314
  sizeClass,
1339
1315
  radiusClass,
1340
1316
  TIMBAL_V2_BORDER[variant],
@@ -1511,9 +1487,9 @@ var AttachmentRemove = () => {
1511
1487
  TooltipIconButton,
1512
1488
  {
1513
1489
  tooltip: "Remove file",
1514
- 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",
1490
+ 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",
1515
1491
  side: "top",
1516
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react2.XIcon, { className: "aui-attachment-remove-icon size-3 dark:stroke-[2.5px]" })
1492
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react2.XIcon, { className: "aui-attachment-remove-icon size-3" })
1517
1493
  }
1518
1494
  ) });
1519
1495
  };
@@ -1535,7 +1511,7 @@ var ComposerAddAttachment = () => {
1535
1511
  tooltip: "Add Attachment",
1536
1512
  side: "bottom",
1537
1513
  variant: "secondary",
1538
- className: "aui-composer-add-attachment shrink-0 text-neutral-500 dark:text-muted-foreground",
1514
+ className: "aui-composer-add-attachment shrink-0 text-muted-foreground",
1539
1515
  "aria-label": "Add Attachment",
1540
1516
  children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react2.PlusIcon, { className: "aui-attachment-add-icon size-4 stroke-[1.5]" })
1541
1517
  }
@@ -1885,30 +1861,19 @@ var import_react7 = require("react");
1885
1861
  var import_react8 = require("@assistant-ui/react");
1886
1862
  var import_lucide_react3 = require("lucide-react");
1887
1863
 
1888
- // src/ui/chrome.ts
1889
- var STUDIO_TOPBAR_GAP = "0.5rem";
1890
- var STUDIO_TOPBAR_HEIGHT = "3rem";
1891
- var STUDIO_PILL_HEIGHT = "2.5rem";
1892
- var STUDIO_SIDEBAR_GAP = "0.5rem";
1893
- var STUDIO_SIDEBAR_WIDTH = "3rem";
1894
- var STUDIO_INSET_LEFT = `calc(${STUDIO_SIDEBAR_GAP} + ${STUDIO_SIDEBAR_WIDTH})`;
1895
- var studioChromeShellStyle = {
1896
- "--studio-topbar-gap": STUDIO_TOPBAR_GAP,
1897
- "--studio-topbar-height": STUDIO_TOPBAR_HEIGHT,
1898
- "--studio-chrome-pill-height": STUDIO_PILL_HEIGHT,
1899
- "--studio-inset-top": `calc(${STUDIO_TOPBAR_GAP} + ${STUDIO_TOPBAR_HEIGHT})`,
1900
- "--studio-sidebar-gap": STUDIO_SIDEBAR_GAP,
1901
- "--studio-sidebar-width": STUDIO_SIDEBAR_WIDTH,
1902
- "--studio-inset-left": STUDIO_INSET_LEFT
1903
- };
1864
+ // src/design/classes.ts
1904
1865
  var studioTopbarPillHeightClass = "h-[var(--studio-chrome-pill-height)] min-h-[var(--studio-chrome-pill-height)]";
1905
1866
  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)]";
1906
- 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";
1907
- 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";
1908
- var studioPillSurfaceClass = TIMBAL_V2_PILL_SURFACE;
1867
+ var studioPlaygroundGradientClass = "bg-gradient-to-b from-playground-from via-playground-via to-playground-to";
1868
+ var studioComposeInputShellClass = cn(
1869
+ "flex w-full flex-col rounded-2xl bg-composer-bg shadow-card-elevated outline-none",
1870
+ "border border-composer-border",
1871
+ "transition-[box-shadow,border-color]",
1872
+ "focus-within:border-composer-border-focus focus-within:ring-2 focus-within:ring-foreground/5"
1873
+ );
1909
1874
  var studioSecondaryChromeClass = TIMBAL_V2_SECONDARY_CHROME;
1910
- 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)]";
1911
- var studioIntegrationBorder = "border border-neutral-200/80 dark:border-white/[0.08]";
1875
+ var studioIntegrationSurfaceSolid = "bg-gradient-to-b from-elevated-from to-elevated-to shadow-card";
1876
+ var studioIntegrationBorder = "border border-border";
1912
1877
  var studioIntegrationCardClass = cn(
1913
1878
  "rounded-xl",
1914
1879
  studioIntegrationSurfaceSolid,
@@ -1923,8 +1888,8 @@ var studioListRowButtonClass = cn(
1923
1888
  "flex w-full cursor-pointer items-center gap-3 rounded-xl px-3 py-2.5 text-left",
1924
1889
  studioIntegrationCardClass,
1925
1890
  "transition-[background-color,box-shadow,border-color] duration-200 ease-in-out",
1926
- "hover:border-neutral-300 dark:hover:border-white/15",
1927
- "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"
1891
+ "hover:border-foreground/20",
1892
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15 focus-visible:ring-offset-2 focus-visible:ring-offset-background"
1928
1893
  );
1929
1894
  var studioComposerIoWellClass = cn(
1930
1895
  "rounded-lg",
@@ -1935,6 +1900,43 @@ var studioToolCardShellClass = cn(
1935
1900
  studioIntegrationCardClass,
1936
1901
  "my-2 min-h-0 overflow-hidden"
1937
1902
  );
1903
+ var studioSidebarPanelClass = cn(
1904
+ "bg-sidebar text-sidebar-foreground",
1905
+ "border border-sidebar-border",
1906
+ "shadow-card-elevated"
1907
+ );
1908
+ var studioSidebarNavItemClass = cn(
1909
+ "flex items-center rounded-lg text-sm",
1910
+ "transition-[color,background-color,box-shadow,border-color] duration-200 ease-in-out",
1911
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15 focus-visible:ring-offset-2"
1912
+ );
1913
+ function studioSidebarNavItemLayout(iconOnly) {
1914
+ 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";
1915
+ }
1916
+ var studioSidebarNavItemSurfaceClass = cn(
1917
+ "bg-gradient-to-b from-elevated-from to-elevated-to text-foreground",
1918
+ "border border-border",
1919
+ "shadow-card"
1920
+ );
1921
+ var studioSidebarNavItemIdleClass = cn(
1922
+ "border border-transparent text-muted-foreground shadow-none",
1923
+ "hover:text-foreground",
1924
+ "hover:bg-gradient-to-b hover:from-elevated-from hover:to-elevated-to",
1925
+ "hover:border-border hover:shadow-card"
1926
+ );
1927
+ var studioSidebarCollapsedRailItemClass = cn(
1928
+ "border border-border shadow-card bg-sidebar-accent"
1929
+ );
1930
+ var studioSidebarCollapsedRailItemIdleClass = cn(
1931
+ studioSidebarCollapsedRailItemClass,
1932
+ "text-muted-foreground hover:text-foreground"
1933
+ );
1934
+ var studioSidebarCollapsedRailItemActiveClass = cn(
1935
+ studioSidebarCollapsedRailItemClass,
1936
+ studioSidebarNavItemSurfaceClass,
1937
+ "text-foreground"
1938
+ );
1939
+ var studioSidebarNavItemActiveClass = studioSidebarNavItemSurfaceClass;
1938
1940
  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";
1939
1941
  var studioTimelineTextClass = "text-xs font-normal leading-snug";
1940
1942
  var studioTimelineActionClass = cn(
@@ -1960,10 +1962,10 @@ var studioArtifactShellClass = cn(
1960
1962
  studioIntegrationCardClass,
1961
1963
  "my-2 w-full min-w-0 overflow-hidden"
1962
1964
  );
1963
- 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";
1965
+ 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";
1964
1966
  var studioQuestionOptionSelectedClass = cn(
1965
1967
  studioQuestionOptionClass,
1966
- "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"
1968
+ "border-border bg-accent ring-1 ring-foreground/10"
1967
1969
  );
1968
1970
 
1969
1971
  // src/artifacts/question-artifact.tsx
@@ -1977,7 +1979,7 @@ var OptionRadio = ({ selected }) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx
1977
1979
  {
1978
1980
  className: cn(
1979
1981
  "flex size-4 shrink-0 items-center justify-center rounded-full border-2 transition-colors",
1980
- selected ? "border-foreground bg-foreground text-background" : "border-neutral-300 bg-background dark:border-white/20"
1982
+ selected ? "border-foreground bg-foreground text-background" : "border-border bg-background"
1981
1983
  ),
1982
1984
  "aria-hidden": true,
1983
1985
  children: selected ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react3.CheckIcon, { className: "size-2.5 stroke-[3]" }) : null
@@ -2233,15 +2235,15 @@ var import_class_variance_authority = require("class-variance-authority");
2233
2235
  var import_radix_ui5 = require("radix-ui");
2234
2236
  var import_jsx_runtime15 = require("react/jsx-runtime");
2235
2237
  var buttonVariants = (0, import_class_variance_authority.cva)(
2236
- "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",
2238
+ "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",
2237
2239
  {
2238
2240
  variants: {
2239
2241
  variant: {
2240
2242
  default: "bg-primary text-primary-foreground hover:bg-primary/90",
2241
- destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
2242
- 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",
2243
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90 focus-visible:ring-destructive/30",
2244
+ outline: "border border-border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
2243
2245
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
2244
- ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
2246
+ ghost: "hover:bg-accent hover:text-accent-foreground",
2245
2247
  link: "text-primary underline-offset-4 hover:underline"
2246
2248
  },
2247
2249
  size: {
@@ -2873,7 +2875,7 @@ var CodeHeader = ({ language, code }) => {
2873
2875
  if (!code || isCopied) return;
2874
2876
  copyToClipboard(code);
2875
2877
  };
2876
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("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: [
2878
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("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: [
2877
2879
  /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "flex items-center gap-2 text-xs font-semibold tracking-wide text-muted-foreground/80 uppercase", children: [
2878
2880
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "inline-block h-2 w-2 rounded-full bg-primary/40" }),
2879
2881
  language
@@ -3080,7 +3082,7 @@ var defaultComponents = (0, import_react_markdown.unstable_memoizeMarkdownCompon
3080
3082
  "pre",
3081
3083
  {
3082
3084
  className: cn(
3083
- "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",
3085
+ "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",
3084
3086
  className
3085
3087
  ),
3086
3088
  ...props
@@ -3092,7 +3094,7 @@ var defaultComponents = (0, import_react_markdown.unstable_memoizeMarkdownCompon
3092
3094
  "code",
3093
3095
  {
3094
3096
  className: cn(
3095
- !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",
3097
+ !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",
3096
3098
  className
3097
3099
  ),
3098
3100
  ...props
@@ -3508,7 +3510,7 @@ var ComposerInput = ({
3508
3510
  import_react22.ComposerPrimitive.Input,
3509
3511
  {
3510
3512
  placeholder,
3511
- 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",
3513
+ 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",
3512
3514
  rows: 1,
3513
3515
  autoFocus,
3514
3516
  "aria-label": "Message input",
@@ -3590,9 +3592,9 @@ var SuggestionRow = ({ suggestion }) => {
3590
3592
  onClick,
3591
3593
  className: cn("aui-thread-suggestion", studioListRowButtonClass),
3592
3594
  children: [
3593
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "aui-thread-suggestion-icon shrink-0 text-neutral-500 dark:text-muted-foreground", children: suggestion.icon ?? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react7.ArrowUpIcon, { className: "size-4", strokeWidth: 1.75, "aria-hidden": true }) }),
3595
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "aui-thread-suggestion-icon shrink-0 text-muted-foreground", children: suggestion.icon ?? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react7.ArrowUpIcon, { className: "size-4", strokeWidth: 1.75, "aria-hidden": true }) }),
3594
3596
  /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("span", { className: "aui-thread-suggestion-text min-w-0 flex-1 text-left", children: [
3595
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "aui-thread-suggestion-text-1 block truncate text-sm font-normal text-foreground dark:text-foreground/95", children: suggestion.title }),
3597
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "aui-thread-suggestion-text-1 block truncate text-sm font-normal text-foreground", children: suggestion.title }),
3596
3598
  suggestion.description && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "aui-thread-suggestion-text-2 mt-0.5 block truncate text-xs text-muted-foreground", children: suggestion.description })
3597
3599
  ] })
3598
3600
  ]
@@ -3625,6 +3627,75 @@ function useResolvedSuggestions(source) {
3625
3627
  return (0, import_react23.useMemo)(() => resolved, [resolved]);
3626
3628
  }
3627
3629
 
3630
+ // src/design/theme-sanity.ts
3631
+ var scheduled = false;
3632
+ var warned = false;
3633
+ function isDev() {
3634
+ if (typeof process !== "undefined" && process.env?.NODE_ENV === "production") {
3635
+ return false;
3636
+ }
3637
+ return true;
3638
+ }
3639
+ function parseLuminance(color) {
3640
+ const value = color.trim();
3641
+ if (!value) return null;
3642
+ const oklch = value.match(/oklch\(\s*([0-9.]+)/i);
3643
+ if (oklch) {
3644
+ const lightness = Number.parseFloat(oklch[1]);
3645
+ if (Number.isFinite(lightness)) return lightness;
3646
+ }
3647
+ const rgb = value.match(/rgba?\(\s*([0-9.]+)[\s,]+([0-9.]+)[\s,]+([0-9.]+)/i);
3648
+ if (rgb) {
3649
+ const r = Number.parseFloat(rgb[1]) / 255;
3650
+ const g = Number.parseFloat(rgb[2]) / 255;
3651
+ const b = Number.parseFloat(rgb[3]) / 255;
3652
+ if ([r, g, b].every(Number.isFinite)) {
3653
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
3654
+ }
3655
+ }
3656
+ const hsl = value.match(/hsla?\(\s*[0-9.]+[\s,]+[0-9.]+%[\s,]+([0-9.]+)%/i);
3657
+ if (hsl) {
3658
+ const lightness = Number.parseFloat(hsl[1]) / 100;
3659
+ if (Number.isFinite(lightness)) return lightness;
3660
+ }
3661
+ return null;
3662
+ }
3663
+ function runCheck() {
3664
+ if (warned) return;
3665
+ if (typeof window === "undefined" || typeof document === "undefined") return;
3666
+ const root = document.documentElement;
3667
+ const styles = window.getComputedStyle(root);
3668
+ const background = styles.getPropertyValue("--background").trim();
3669
+ if (!background) {
3670
+ warned = true;
3671
+ console.warn(
3672
+ '[@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.'
3673
+ );
3674
+ return;
3675
+ }
3676
+ const luminance = parseLuminance(background);
3677
+ if (luminance === null) return;
3678
+ const hasDarkClass = root.classList.contains("dark");
3679
+ const looksDark = luminance < 0.5;
3680
+ if (hasDarkClass !== looksDark) {
3681
+ warned = true;
3682
+ console.warn(
3683
+ `[@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.`
3684
+ );
3685
+ }
3686
+ }
3687
+ function scheduleThemeSanityCheck() {
3688
+ if (scheduled) return;
3689
+ if (!isDev()) return;
3690
+ if (typeof window === "undefined") return;
3691
+ scheduled = true;
3692
+ if (typeof queueMicrotask === "function") {
3693
+ queueMicrotask(() => setTimeout(runCheck, 0));
3694
+ } else {
3695
+ setTimeout(runCheck, 0);
3696
+ }
3697
+ }
3698
+
3628
3699
  // src/components/thread.tsx
3629
3700
  var import_jsx_runtime27 = require("react/jsx-runtime");
3630
3701
  var Thread = ({
@@ -3644,6 +3715,9 @@ var Thread = ({
3644
3715
  const EditComposerSlot = components?.EditComposer ?? EditComposer;
3645
3716
  const ScrollToBottomSlot = components?.ScrollToBottom ?? ThreadScrollToBottom;
3646
3717
  const SuggestionsSlot = components?.Suggestions ?? Suggestions;
3718
+ (0, import_react25.useEffect)(() => {
3719
+ scheduleThemeSanityCheck();
3720
+ }, []);
3647
3721
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3648
3722
  ArtifactRegistryProvider,
3649
3723
  {
@@ -3651,7 +3725,7 @@ var Thread = ({
3651
3725
  override: artifacts?.override,
3652
3726
  children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(UiEventProvider, { onEvent: onArtifactEvent ?? (() => {
3653
3727
  }), children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3654
- import_react25.ThreadPrimitive.Root,
3728
+ import_react26.ThreadPrimitive.Root,
3655
3729
  {
3656
3730
  className: cn(
3657
3731
  "aui-root aui-thread-root @container flex h-full flex-col bg-background",
@@ -3659,7 +3733,7 @@ var Thread = ({
3659
3733
  ),
3660
3734
  style: { ["--thread-max-width"]: maxWidth },
3661
3735
  children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
3662
- import_react25.ThreadPrimitive.Viewport,
3736
+ import_react26.ThreadPrimitive.Viewport,
3663
3737
  {
3664
3738
  turnAnchor: "bottom",
3665
3739
  className: "aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll px-4 pt-4",
@@ -3673,7 +3747,7 @@ var Thread = ({
3673
3747
  }
3674
3748
  ),
3675
3749
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3676
- import_react25.ThreadPrimitive.Messages,
3750
+ import_react26.ThreadPrimitive.Messages,
3677
3751
  {
3678
3752
  components: {
3679
3753
  UserMessage: UserMessageSlot,
@@ -3682,7 +3756,7 @@ var Thread = ({
3682
3756
  }
3683
3757
  }
3684
3758
  ),
3685
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_react25.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: [
3759
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_react26.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: [
3686
3760
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ScrollToBottomSlot, {}),
3687
3761
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ComposerSlot, { placeholder: composerPlaceholder })
3688
3762
  ] })
@@ -3695,7 +3769,7 @@ var Thread = ({
3695
3769
  );
3696
3770
  };
3697
3771
  var ThreadScrollToBottom = () => {
3698
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react25.ThreadPrimitive.ScrollToBottom, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3772
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react26.ThreadPrimitive.ScrollToBottom, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3699
3773
  TooltipIconButton,
3700
3774
  {
3701
3775
  tooltip: "Scroll to bottom",
@@ -3733,20 +3807,20 @@ var ThreadWelcome = ({
3733
3807
  suggestions,
3734
3808
  Suggestions: SuggestionsSlot = Suggestions
3735
3809
  }) => {
3736
- const isEmpty = (0, import_react25.useThread)((s) => s.messages.length === 0);
3810
+ const isEmpty = (0, import_react26.useThread)((s) => s.messages.length === 0);
3737
3811
  if (!isEmpty) return null;
3738
3812
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col", children: [
3739
3813
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "aui-thread-welcome-center flex w-full grow flex-col items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
3740
- import_react26.motion.div,
3814
+ import_react27.motion.div,
3741
3815
  {
3742
3816
  className: "aui-thread-welcome-message flex flex-col items-center justify-center px-4 text-center",
3743
3817
  variants: welcomeStagger,
3744
3818
  initial: "initial",
3745
3819
  animate: "animate",
3746
3820
  children: [
3747
- config?.icon && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react26.motion.div, { variants: welcomeIcon, className: "mb-5", children: config.icon }),
3821
+ config?.icon && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react27.motion.div, { variants: welcomeIcon, className: "mb-5", children: config.icon }),
3748
3822
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3749
- import_react26.motion.h1,
3823
+ import_react27.motion.h1,
3750
3824
  {
3751
3825
  variants: welcomeItem,
3752
3826
  className: "aui-thread-welcome-message-inner font-semibold text-2xl",
@@ -3754,7 +3828,7 @@ var ThreadWelcome = ({
3754
3828
  }
3755
3829
  ),
3756
3830
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3757
- import_react26.motion.p,
3831
+ import_react27.motion.p,
3758
3832
  {
3759
3833
  variants: welcomeItem,
3760
3834
  className: "aui-thread-welcome-message-inner mt-2 text-muted-foreground",
@@ -3768,18 +3842,18 @@ var ThreadWelcome = ({
3768
3842
  ] });
3769
3843
  };
3770
3844
  var MessageError = () => {
3771
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react25.MessagePrimitive.Error, { children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react25.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__ */ (0, import_jsx_runtime27.jsx)(import_react25.ErrorPrimitive.Message, { className: "aui-message-error-message line-clamp-2" }) }) });
3845
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react26.MessagePrimitive.Error, { children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react26.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__ */ (0, import_jsx_runtime27.jsx)(import_react26.ErrorPrimitive.Message, { className: "aui-message-error-message line-clamp-2" }) }) });
3772
3846
  };
3773
3847
  var AssistantMessage = () => {
3774
3848
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
3775
- import_react25.MessagePrimitive.Root,
3849
+ import_react26.MessagePrimitive.Root,
3776
3850
  {
3777
3851
  className: "aui-assistant-message-root fade-in slide-in-from-bottom-1 relative mx-auto w-full max-w-(--thread-max-width) animate-in py-3 duration-150",
3778
3852
  "data-role": "assistant",
3779
3853
  children: [
3780
3854
  /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "aui-assistant-message-content wrap-break-word px-2 text-foreground leading-relaxed", children: [
3781
3855
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3782
- import_react25.MessagePrimitive.Parts,
3856
+ import_react26.MessagePrimitive.Parts,
3783
3857
  {
3784
3858
  components: {
3785
3859
  Text: MarkdownText,
@@ -3801,30 +3875,29 @@ var ASSISTANT_ACTION_ICON_CLASS = cn(
3801
3875
  // The v2 fill span sits inside `group/tbv2 > span:first-child`. We mute it
3802
3876
  // here so action-bar buttons read as subtle icons rather than full pills.
3803
3877
  "[&>span:first-child]:bg-transparent",
3804
- "[&>span:first-child]:group-hover/tbv2:bg-neutral-100/50",
3805
- "dark:[&>span:first-child]:group-hover/tbv2:bg-white/8"
3878
+ "[&>span:first-child]:group-hover/tbv2:bg-muted/70"
3806
3879
  );
3807
3880
  var AssistantActionBar = () => {
3808
3881
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
3809
- import_react25.ActionBarPrimitive.Root,
3882
+ import_react26.ActionBarPrimitive.Root,
3810
3883
  {
3811
3884
  hideWhenRunning: true,
3812
3885
  autohide: "not-last",
3813
3886
  className: "aui-assistant-action-bar-root flex items-center gap-0 bg-transparent px-0 py-0.5 text-muted-foreground/60",
3814
3887
  children: [
3815
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react25.ActionBarPrimitive.Copy, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
3888
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react26.ActionBarPrimitive.Copy, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
3816
3889
  TooltipIconButton,
3817
3890
  {
3818
3891
  tooltip: "Copy",
3819
3892
  variant: "ghost",
3820
3893
  className: ASSISTANT_ACTION_ICON_CLASS,
3821
3894
  children: [
3822
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react25.AuiIf, { condition: (s) => s.message.isCopied, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react8.CheckIcon, { className: "size-3" }) }),
3823
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react25.AuiIf, { condition: (s) => !s.message.isCopied, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react8.CopyIcon, { className: "size-3" }) })
3895
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react26.AuiIf, { condition: (s) => s.message.isCopied, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react8.CheckIcon, { className: "size-3" }) }),
3896
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react26.AuiIf, { condition: (s) => !s.message.isCopied, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react8.CopyIcon, { className: "size-3" }) })
3824
3897
  ]
3825
3898
  }
3826
3899
  ) }),
3827
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react25.ActionBarPrimitive.Reload, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3900
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react26.ActionBarPrimitive.Reload, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3828
3901
  TooltipIconButton,
3829
3902
  {
3830
3903
  tooltip: "Regenerate",
@@ -3833,8 +3906,8 @@ var AssistantActionBar = () => {
3833
3906
  children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react8.RefreshCwIcon, { className: "size-3" })
3834
3907
  }
3835
3908
  ) }),
3836
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_react25.ActionBarMorePrimitive.Root, { children: [
3837
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react25.ActionBarMorePrimitive.Trigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3909
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_react26.ActionBarMorePrimitive.Root, { children: [
3910
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react26.ActionBarMorePrimitive.Trigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3838
3911
  TooltipIconButton,
3839
3912
  {
3840
3913
  tooltip: "More",
@@ -3847,12 +3920,12 @@ var AssistantActionBar = () => {
3847
3920
  }
3848
3921
  ) }),
3849
3922
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3850
- import_react25.ActionBarMorePrimitive.Content,
3923
+ import_react26.ActionBarMorePrimitive.Content,
3851
3924
  {
3852
3925
  side: "bottom",
3853
3926
  align: "start",
3854
- 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",
3855
- children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react25.ActionBarPrimitive.ExportMarkdown, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_react25.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: [
3927
+ 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",
3928
+ children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react26.ActionBarPrimitive.ExportMarkdown, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_react26.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: [
3856
3929
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_lucide_react8.DownloadIcon, { className: "size-4 shrink-0" }),
3857
3930
  "Export as Markdown"
3858
3931
  ] }) })
@@ -3864,25 +3937,25 @@ var AssistantActionBar = () => {
3864
3937
  );
3865
3938
  };
3866
3939
  var UserMessageText = () => {
3867
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "whitespace-pre-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react25.MessagePartPrimitive.Text, { smooth: false }) });
3940
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "whitespace-pre-wrap", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react26.MessagePartPrimitive.Text, { smooth: false }) });
3868
3941
  };
3869
3942
  var UserMessage = () => {
3870
3943
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
3871
- import_react25.MessagePrimitive.Root,
3944
+ import_react26.MessagePrimitive.Root,
3872
3945
  {
3873
3946
  className: "aui-user-message-root mx-auto flex w-full max-w-(--thread-max-width) flex-col items-end gap-2 px-2 py-3",
3874
3947
  "data-role": "user",
3875
3948
  children: [
3876
3949
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(UserMessageAttachments, {}),
3877
3950
  /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
3878
- import_react26.motion.div,
3951
+ import_react27.motion.div,
3879
3952
  {
3880
- 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",
3953
+ 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",
3881
3954
  initial: { opacity: 0, y: 8, scale: 0.99 },
3882
3955
  animate: { opacity: 1, y: 0, scale: 1 },
3883
3956
  transition: { duration: 0.65, ease: luxuryEase },
3884
3957
  children: [
3885
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react25.MessagePrimitive.Parts, { components: { Text: UserMessageText } }),
3958
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react26.MessagePrimitive.Parts, { components: { Text: UserMessageText } }),
3886
3959
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "aui-user-action-bar-wrapper absolute top-1/2 left-0 -translate-x-full -translate-y-1/2 pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(UserActionBar, {}) })
3887
3960
  ]
3888
3961
  }
@@ -3893,12 +3966,12 @@ var UserMessage = () => {
3893
3966
  };
3894
3967
  var UserActionBar = () => {
3895
3968
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3896
- import_react25.ActionBarPrimitive.Root,
3969
+ import_react26.ActionBarPrimitive.Root,
3897
3970
  {
3898
3971
  hideWhenRunning: true,
3899
3972
  autohide: "not-last",
3900
3973
  className: "aui-user-action-bar-root flex flex-col items-end",
3901
- children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react25.ActionBarPrimitive.Edit, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3974
+ children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react26.ActionBarPrimitive.Edit, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3902
3975
  TooltipIconButton,
3903
3976
  {
3904
3977
  tooltip: "Edit",
@@ -3911,17 +3984,17 @@ var UserActionBar = () => {
3911
3984
  );
3912
3985
  };
3913
3986
  var EditComposer = () => {
3914
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react25.MessagePrimitive.Root, { className: "aui-edit-composer-wrapper mx-auto flex w-full max-w-(--thread-max-width) flex-col px-2 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_react25.ComposerPrimitive.Root, { className: "aui-edit-composer-root ml-auto flex w-full max-w-[85%] flex-col rounded-2xl bg-muted", children: [
3987
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react26.MessagePrimitive.Root, { className: "aui-edit-composer-wrapper mx-auto flex w-full max-w-(--thread-max-width) flex-col px-2 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_react26.ComposerPrimitive.Root, { className: "aui-edit-composer-root ml-auto flex w-full max-w-[85%] flex-col rounded-2xl bg-muted", children: [
3915
3988
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3916
- import_react25.ComposerPrimitive.Input,
3989
+ import_react26.ComposerPrimitive.Input,
3917
3990
  {
3918
3991
  className: "aui-edit-composer-input min-h-14 w-full resize-none bg-transparent p-4 text-foreground text-sm outline-none",
3919
3992
  autoFocus: true
3920
3993
  }
3921
3994
  ),
3922
3995
  /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "aui-edit-composer-footer mx-3 mb-3 flex items-center gap-2 self-end", children: [
3923
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react25.ComposerPrimitive.Cancel, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(TimbalV2Button, { variant: "ghost", size: "sm", children: "Cancel" }) }),
3924
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react25.ComposerPrimitive.Send, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(TimbalV2Button, { variant: "primary", size: "sm", children: "Update" }) })
3996
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react26.ComposerPrimitive.Cancel, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(TimbalV2Button, { variant: "ghost", size: "sm", children: "Cancel" }) }),
3997
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react26.ComposerPrimitive.Send, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(TimbalV2Button, { variant: "primary", size: "sm", children: "Update" }) })
3925
3998
  ] })
3926
3999
  ] }) });
3927
4000
  };
@@ -3953,193 +4026,6 @@ function TimbalChat({
3953
4026
  );
3954
4027
  }
3955
4028
 
3956
- // src/artifacts/agent-instructions.ts
3957
- var ARTIFACT_AGENT_INSTRUCTIONS = `
3958
- ## Rich artifacts (Timbal chat UI)
3959
-
3960
- 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.
3961
-
3962
- ### Delivery channels (either works)
3963
-
3964
- 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.
3965
- 2. **Inline markdown fence** \u2014 embed the same JSON inside a fenced block:
3966
-
3967
- \`\`\`timbal-artifact
3968
- {"type":"chart","data":[{"month":"Jan","sales":120}]}
3969
- \`\`\`
3970
-
3971
- The alias \`\`\`timbal\`\`\` is also accepted.
3972
-
3973
- ### Built-in artifact types
3974
-
3975
- | \`type\` | Use for |
3976
- |---|---|
3977
- | \`chart\` | Bar, line, area, or pie charts. Fields: \`data\`, optional \`chartType\`, \`xKey\`, \`dataKey\`, \`title\`, \`unit\`. |
3978
- | \`table\` | Tabular data. Fields: \`rows\`, optional \`columns\`, \`title\`. |
3979
- | \`question\` | In-thread multiple choice. Fields: \`options: [{ id, label, description? }]\`, optional \`prompt\`, \`multi\`. User replies are sent back as a normal user message. |
3980
- | \`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). |
3981
- | \`json\` | Fallback structured view. Fields: \`data\`, optional \`title\`. |
3982
- | \`ui\` | **Interactive UI** composed from a fixed node palette (hover, click, drag). See below. |
3983
-
3984
- ### When to use \`type: "html"\`
3985
-
3986
- 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\`.
3987
-
3988
- - Inline \`<style>\`, \`<script>\`, SVG, and canvas are supported.
3989
- - Default \`sandboxed: true\` runs in an isolated iframe with scripts enabled.
3990
- - Set \`sandboxed: false\` only for trusted content that needs external CDN scripts/styles or full DOM freedom.
3991
- - Prefer \`ui\` when controls should send chat messages or host events; prefer \`html\` for self-contained mini-apps and visual demos.
3992
-
3993
- ### When to use \`type: "ui"\`
3994
-
3995
- 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.
3996
-
3997
- Each \`ui\` artifact has:
3998
-
3999
- - \`initialState\` \u2014 optional object seeding local state (per widget instance).
4000
- - \`root\` \u2014 a single node tree (see node kinds below).
4001
- - optional \`title\` \u2014 card heading.
4002
-
4003
- **Bindings:** anywhere a primitive is accepted, you may use \`{ "$bind": "dotted.path" }\` to read from \`initialState\` / local state (e.g. \`{ "$bind": "qty" }\`).
4004
-
4005
- **Actions:** nodes may attach \`onClick\`, \`onChange\`, or \`onDragEnd\` with one action or an array:
4006
-
4007
- | Action | Shape | Effect |
4008
- |---|---|---|
4009
- | User message | \`{ "kind": "message", "text": "..." }\` or \`{ "kind": "message", "text": { "$bind": "path" } }\` | Sends text as the next user message. |
4010
- | Set state | \`{ "kind": "set", "path": "foo", "value": 1 }\` | Writes local widget state. |
4011
- | Toggle boolean | \`{ "kind": "toggle", "path": "enabled" }\` | Flips a boolean at \`path\`. |
4012
- | Host event | \`{ "kind": "emit", "name": "event-name", "payload": { ... } }\` | Bubbles to the host app (\`onArtifactEvent\` on \`<Thread>\`). |
4013
-
4014
- ### \`ui\` node palette (\`root.kind\`)
4015
-
4016
- | \`kind\` | Purpose | Key fields |
4017
- |---|---|---|
4018
- | \`box\` | Layout container | \`children\`, \`direction\` (\`row\`/\`col\`), \`gap\`, \`padding\`, \`align\`, \`justify\`, \`wrap\` |
4019
- | \`text\` | Body text | \`value\`, optional \`muted\`, \`size\`, \`weight\` |
4020
- | \`heading\` | Heading | \`value\`, optional \`level\` (1\u20134) |
4021
- | \`badge\` | Pill label | \`value\`, optional \`tone\` (\`default\`, \`primary\`, \`success\`, \`warn\`, \`danger\`) |
4022
- | \`button\` | Clickable button | \`label\`, optional \`variant\`, \`size\`, \`disabled\`, \`onClick\` |
4023
- | \`toggle\` | Boolean switch | \`binding\` (state path), optional \`label\`, \`onChange\` |
4024
- | \`slider\` | Numeric range | \`binding\`, optional \`min\`, \`max\`, \`step\`, \`label\`, \`showValue\`, \`onChange\` |
4025
- | \`tooltip\` | Hover tooltip | \`content\`, \`child\` (single node), optional \`side\` |
4026
- | \`draggable\` | Drag gesture | \`child\`, optional \`axis\` (\`x\`/\`y\`/\`both\`), \`snapBack\`, \`onDragEnd\` |
4027
- | \`custom\` | Host-registered widget | \`name\`, optional \`props\`, \`children\` \u2014 only if the app registered that name |
4028
-
4029
- ### Example \`ui\` artifact
4030
-
4031
- \`\`\`json
4032
- {
4033
- "type": "ui",
4034
- "title": "Configure plan",
4035
- "initialState": { "qty": 1, "premium": false },
4036
- "root": {
4037
- "kind": "box",
4038
- "direction": "col",
4039
- "gap": 3,
4040
- "children": [
4041
- { "kind": "heading", "value": "Choose quantity", "level": 3 },
4042
- {
4043
- "kind": "tooltip",
4044
- "content": "Drag to adjust quantity",
4045
- "child": {
4046
- "kind": "slider",
4047
- "binding": "qty",
4048
- "min": 1,
4049
- "max": 50,
4050
- "label": "Quantity",
4051
- "onChange": { "kind": "emit", "name": "qty-changed" }
4052
- }
4053
- },
4054
- { "kind": "toggle", "binding": "premium", "label": "Premium support" },
4055
- {
4056
- "kind": "button",
4057
- "label": "Confirm",
4058
- "onClick": { "kind": "message", "text": { "$bind": "qty" } }
4059
- }
4060
- ]
4061
- }
4062
- }
4063
- \`\`\`
4064
-
4065
- ### Rules
4066
-
4067
- - Always set \`type\` to a built-in value above unless the app documented a custom type.
4068
- - Prefer \`ui\` over \`html\` when actions must bubble to the host chat (\`message\`, \`emit\`).
4069
- - Prefer \`question\` for simple A/B/C choices; use \`ui\` when you need sliders, toggles, drag, or multi-control layouts.
4070
- - Keep \`data\` arrays reasonably small (charts/tables).
4071
-
4072
- ### After calling an artifact tool (critical)
4073
-
4074
- When you call a tool that returns an artifact (\`make_chart\`, \`ask_question\`, \`show_table\`, \`show_html\`, \`make_ui_demo\`, etc.):
4075
-
4076
- 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.
4077
- 2. **Do not** emit a matching \`\`\`timbal-artifact\`\`\` block for the same payload \u2014 pick **one** channel (tool result only).
4078
- 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.
4079
- 4. Treat the widget as visible to the user; refer to it as "above" / "the chart" / "the choices" \u2014 never reproduce its contents.
4080
- `.trim();
4081
-
4082
- // src/index.ts
4083
- var import_react29 = require("@assistant-ui/react");
4084
-
4085
- // src/hooks/use-workforces.ts
4086
- var import_react27 = require("react");
4087
- function useWorkforces(options = {}) {
4088
- const { baseUrl = "/api", fetch: fetchFn, pickInitial } = options;
4089
- const [workforces, setWorkforces] = (0, import_react27.useState)([]);
4090
- const [selectedId, setSelectedId] = (0, import_react27.useState)("");
4091
- const [isLoading, setIsLoading] = (0, import_react27.useState)(true);
4092
- const [error, setError] = (0, import_react27.useState)(null);
4093
- const fetchFnRef = (0, import_react27.useRef)(fetchFn ?? authFetch);
4094
- (0, import_react27.useEffect)(() => {
4095
- fetchFnRef.current = fetchFn ?? authFetch;
4096
- }, [fetchFn]);
4097
- const pickInitialRef = (0, import_react27.useRef)(pickInitial);
4098
- (0, import_react27.useEffect)(() => {
4099
- pickInitialRef.current = pickInitial;
4100
- }, [pickInitial]);
4101
- const load = (0, import_react27.useMemo)(() => {
4102
- return async () => {
4103
- setIsLoading(true);
4104
- setError(null);
4105
- try {
4106
- const res = await fetchFnRef.current(`${baseUrl}/workforce`);
4107
- if (!res.ok) throw new Error(`Failed to load workforces (${res.status})`);
4108
- const data = await res.json();
4109
- setWorkforces(data);
4110
- setSelectedId((current) => {
4111
- if (current && data.some((w) => idOf(w) === current)) return current;
4112
- const initial = pickInitialRef.current?.(data) ?? data.find((w) => w.type === "agent") ?? data[0];
4113
- return initial ? idOf(initial) : "";
4114
- });
4115
- } catch (err) {
4116
- setError(err instanceof Error ? err : new Error(String(err)));
4117
- } finally {
4118
- setIsLoading(false);
4119
- }
4120
- };
4121
- }, [baseUrl]);
4122
- (0, import_react27.useEffect)(() => {
4123
- load();
4124
- }, [load]);
4125
- const selected = (0, import_react27.useMemo)(
4126
- () => workforces.find((w) => idOf(w) === selectedId),
4127
- [workforces, selectedId]
4128
- );
4129
- return {
4130
- workforces,
4131
- selectedId,
4132
- setSelectedId,
4133
- selected,
4134
- isLoading,
4135
- error,
4136
- refresh: load
4137
- };
4138
- }
4139
- function idOf(item) {
4140
- return item.id ?? item.uid ?? item.name ?? "";
4141
- }
4142
-
4143
4029
  // src/components/workforce-selector.tsx
4144
4030
  var import_lucide_react9 = require("lucide-react");
4145
4031
  var import_jsx_runtime29 = require("react/jsx-runtime");
@@ -4174,7 +4060,7 @@ var WorkforceSelector = ({
4174
4060
  children: [
4175
4061
  !value && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("option", { value: "", children: placeholder }),
4176
4062
  workforces.map((w) => {
4177
- const id = idOf2(w);
4063
+ const id = idOf(w);
4178
4064
  return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("option", { value: id, children: w.name ?? id }, id);
4179
4065
  })
4180
4066
  ]
@@ -4191,10 +4077,113 @@ var WorkforceSelector = ({
4191
4077
  }
4192
4078
  );
4193
4079
  };
4080
+ function idOf(item) {
4081
+ return item.id ?? item.uid ?? item.name ?? "";
4082
+ }
4083
+
4084
+ // src/hooks/use-workforces.ts
4085
+ var import_react28 = require("react");
4086
+ function useWorkforces(options = {}) {
4087
+ const { baseUrl = "/api", fetch: fetchFn, pickInitial, enabled = true } = options;
4088
+ const [workforces, setWorkforces] = (0, import_react28.useState)([]);
4089
+ const [selectedId, setSelectedId] = (0, import_react28.useState)("");
4090
+ const [isLoading, setIsLoading] = (0, import_react28.useState)(enabled);
4091
+ const [error, setError] = (0, import_react28.useState)(null);
4092
+ const fetchFnRef = (0, import_react28.useRef)(fetchFn ?? authFetch);
4093
+ (0, import_react28.useEffect)(() => {
4094
+ fetchFnRef.current = fetchFn ?? authFetch;
4095
+ }, [fetchFn]);
4096
+ const pickInitialRef = (0, import_react28.useRef)(pickInitial);
4097
+ (0, import_react28.useEffect)(() => {
4098
+ pickInitialRef.current = pickInitial;
4099
+ }, [pickInitial]);
4100
+ const load = (0, import_react28.useMemo)(() => {
4101
+ return async () => {
4102
+ if (!enabled) {
4103
+ setIsLoading(false);
4104
+ return;
4105
+ }
4106
+ setIsLoading(true);
4107
+ setError(null);
4108
+ try {
4109
+ const res = await fetchFnRef.current(`${baseUrl}/workforce`);
4110
+ if (!res.ok) throw new Error(`Failed to load workforces (${res.status})`);
4111
+ const data = await res.json();
4112
+ setWorkforces(data);
4113
+ setSelectedId((current) => {
4114
+ if (current && data.some((w) => idOf2(w) === current)) return current;
4115
+ const initial = pickInitialRef.current?.(data) ?? data.find((w) => w.type === "agent") ?? data[0];
4116
+ return initial ? idOf2(initial) : "";
4117
+ });
4118
+ } catch (err) {
4119
+ setError(err instanceof Error ? err : new Error(String(err)));
4120
+ } finally {
4121
+ setIsLoading(false);
4122
+ }
4123
+ };
4124
+ }, [baseUrl, enabled]);
4125
+ (0, import_react28.useEffect)(() => {
4126
+ load();
4127
+ }, [load]);
4128
+ const selected = (0, import_react28.useMemo)(
4129
+ () => workforces.find((w) => idOf2(w) === selectedId),
4130
+ [workforces, selectedId]
4131
+ );
4132
+ return {
4133
+ workforces,
4134
+ selectedId,
4135
+ setSelectedId,
4136
+ selected,
4137
+ isLoading,
4138
+ error,
4139
+ refresh: load
4140
+ };
4141
+ }
4194
4142
  function idOf2(item) {
4195
4143
  return item.id ?? item.uid ?? item.name ?? "";
4196
4144
  }
4197
4145
 
4146
+ // src/design/tokens.ts
4147
+ var SIDEBAR_WIDTH_PX = 224;
4148
+ var SIDEBAR_WIDTH_COLLAPSED_PX = 52;
4149
+ var SIDEBAR_MOBILE_PX = 272;
4150
+ var SIDEBAR_GAP_PX = 12;
4151
+ var SIDEBAR_CONTENT_GAP_PX = 8;
4152
+ var TOPBAR_GAP_PX = 8;
4153
+ var TOPBAR_HEIGHT_PX = 48;
4154
+ var PILL_HEIGHT_PX = 40;
4155
+ var SIDEBAR_INSET_PX_EXPANDED = SIDEBAR_GAP_PX + SIDEBAR_WIDTH_PX + SIDEBAR_CONTENT_GAP_PX;
4156
+ var SIDEBAR_INSET_PX_COLLAPSED = SIDEBAR_GAP_PX + SIDEBAR_WIDTH_COLLAPSED_PX + SIDEBAR_CONTENT_GAP_PX;
4157
+ var px = (n) => `${n / 16}rem`;
4158
+ var SIDEBAR_WIDTH = px(SIDEBAR_WIDTH_PX);
4159
+ var SIDEBAR_WIDTH_COLLAPSED = px(SIDEBAR_WIDTH_COLLAPSED_PX);
4160
+ var SIDEBAR_GAP = px(SIDEBAR_GAP_PX);
4161
+ var SIDEBAR_CONTENT_GAP = px(SIDEBAR_CONTENT_GAP_PX);
4162
+ var TOPBAR_GAP = px(TOPBAR_GAP_PX);
4163
+ var TOPBAR_HEIGHT = px(TOPBAR_HEIGHT_PX);
4164
+ var PILL_HEIGHT = px(PILL_HEIGHT_PX);
4165
+ var SIDEBAR_INSET_EXPANDED = px(SIDEBAR_INSET_PX_EXPANDED);
4166
+ var SIDEBAR_INSET_COLLAPSED = px(SIDEBAR_INSET_PX_COLLAPSED);
4167
+ var studioChromeShellStyle = {
4168
+ "--studio-topbar-gap": TOPBAR_GAP,
4169
+ "--studio-topbar-height": TOPBAR_HEIGHT,
4170
+ "--studio-chrome-pill-height": PILL_HEIGHT,
4171
+ "--studio-inset-top": `calc(${TOPBAR_GAP} + ${TOPBAR_HEIGHT})`,
4172
+ "--studio-sidebar-gap": SIDEBAR_GAP,
4173
+ "--studio-sidebar-width": SIDEBAR_WIDTH,
4174
+ "--studio-sidebar-width-collapsed": SIDEBAR_WIDTH_COLLAPSED,
4175
+ "--studio-sidebar-content-gap": SIDEBAR_CONTENT_GAP,
4176
+ "--studio-inset-left": SIDEBAR_INSET_EXPANDED,
4177
+ "--studio-inset-left-collapsed": SIDEBAR_INSET_COLLAPSED
4178
+ };
4179
+ var STORAGE_KEYS = {
4180
+ sidebarCollapsed: "timbal-studio-sidebar-collapsed"
4181
+ };
4182
+ var DOM_IDS = {
4183
+ sidebarRuntimeAnchor: "timbal-studio-sidebar-runtime-anchor",
4184
+ topbarBrandAnchor: "timbal-studio-topbar-brand-anchor"
4185
+ };
4186
+
4198
4187
  // src/components/chat-shell.tsx
4199
4188
  var import_jsx_runtime30 = require("react/jsx-runtime");
4200
4189
  var TimbalChatShell = ({
@@ -4273,9 +4262,234 @@ var TimbalChatShell = ({
4273
4262
  );
4274
4263
  };
4275
4264
 
4276
- // src/auth/provider.tsx
4277
- var import_react28 = require("react");
4278
- var import_jsx_runtime31 = require("react/jsx-runtime");
4265
+ // src/components/studio/studio-shell.tsx
4266
+ var import_react41 = require("react");
4267
+ var import_lucide_react13 = require("lucide-react");
4268
+ var import_react42 = require("motion/react");
4269
+
4270
+ // src/design/sidebar-motion.ts
4271
+ var STUDIO_SIDEBAR_EASE_ENTER = [0, 0, 0.2, 1];
4272
+ var STUDIO_SIDEBAR_EASE_EXIT = [0.4, 0, 1, 1];
4273
+ var STUDIO_SIDEBAR_EASE = [0.16, 1, 0.3, 1];
4274
+ var STUDIO_SIDEBAR_ENTRIES_OUT_S = 0.1;
4275
+ var STUDIO_SIDEBAR_WIDTH_S = 0.17;
4276
+ var STUDIO_SIDEBAR_ENTRY_ITEM_IN_S = 0.18;
4277
+ var STUDIO_SIDEBAR_STAGGER_S = 0.03;
4278
+ var STUDIO_SIDEBAR_EXPAND_REVEAL_FRAC = 0.5;
4279
+ var STUDIO_SIDEBAR_CONTENT_NUDGE_PX = 6;
4280
+ var studioSidebarEntriesContainerVariants = {
4281
+ hidden: {
4282
+ opacity: 0,
4283
+ transition: {
4284
+ duration: STUDIO_SIDEBAR_ENTRIES_OUT_S,
4285
+ ease: STUDIO_SIDEBAR_EASE_EXIT,
4286
+ staggerChildren: 0
4287
+ }
4288
+ },
4289
+ visible: {
4290
+ opacity: 1,
4291
+ transition: {
4292
+ duration: 0.06,
4293
+ ease: STUDIO_SIDEBAR_EASE_ENTER,
4294
+ staggerChildren: STUDIO_SIDEBAR_STAGGER_S,
4295
+ delayChildren: 0.02
4296
+ }
4297
+ }
4298
+ };
4299
+ var studioSidebarEntryItemVariants = {
4300
+ hidden: {
4301
+ opacity: 0,
4302
+ x: -STUDIO_SIDEBAR_CONTENT_NUDGE_PX,
4303
+ scale: 0.99
4304
+ },
4305
+ visible: {
4306
+ opacity: 1,
4307
+ x: 0,
4308
+ scale: 1,
4309
+ transition: {
4310
+ duration: STUDIO_SIDEBAR_ENTRY_ITEM_IN_S,
4311
+ ease: STUDIO_SIDEBAR_EASE_ENTER
4312
+ }
4313
+ }
4314
+ };
4315
+ function studioSidebarEntriesTransition(visible, reduced) {
4316
+ if (reduced) return { duration: 0.01 };
4317
+ return visible ? { duration: 0.06, ease: STUDIO_SIDEBAR_EASE_ENTER } : { duration: STUDIO_SIDEBAR_ENTRIES_OUT_S, ease: STUDIO_SIDEBAR_EASE_EXIT };
4318
+ }
4319
+ function studioSidebarWidthTransition(reduced, direction = "collapse") {
4320
+ if (reduced) return { duration: 0.01 };
4321
+ return {
4322
+ duration: direction === "expand" ? STUDIO_SIDEBAR_WIDTH_S : STUDIO_SIDEBAR_WIDTH_S * 0.94,
4323
+ ease: direction === "expand" ? STUDIO_SIDEBAR_EASE_ENTER : STUDIO_SIDEBAR_EASE_EXIT
4324
+ };
4325
+ }
4326
+ function studioSidebarDrawerTransition(reduced) {
4327
+ if (reduced) return { duration: 0.01 };
4328
+ return {
4329
+ duration: 0.22,
4330
+ ease: STUDIO_SIDEBAR_EASE
4331
+ };
4332
+ }
4333
+ function studioSidebarBackdropTransition(reduced) {
4334
+ if (reduced) return { duration: 0.01 };
4335
+ return { duration: 0.16, ease: STUDIO_SIDEBAR_EASE_EXIT };
4336
+ }
4337
+
4338
+ // src/hooks/use-sidebar-collapse-phase.ts
4339
+ var import_react29 = require("react");
4340
+ var WIDTH_OVERLAP_FRAC = 0.7;
4341
+ function useSidebarCollapsePhase(collapsed, reducedMotion) {
4342
+ const [widthCollapsed, setWidthCollapsed] = (0, import_react29.useState)(collapsed);
4343
+ const [entriesVisible, setEntriesVisible] = (0, import_react29.useState)(true);
4344
+ const collapsedTarget = (0, import_react29.useRef)(collapsed);
4345
+ const isFirstRender = (0, import_react29.useRef)(true);
4346
+ const widthTimerRef = (0, import_react29.useRef)(null);
4347
+ const revealTimerRef = (0, import_react29.useRef)(null);
4348
+ (0, import_react29.useEffect)(() => {
4349
+ collapsedTarget.current = collapsed;
4350
+ }, [collapsed]);
4351
+ const clearWidthTimer = () => {
4352
+ if (widthTimerRef.current !== null) {
4353
+ clearTimeout(widthTimerRef.current);
4354
+ widthTimerRef.current = null;
4355
+ }
4356
+ };
4357
+ const clearRevealTimer = () => {
4358
+ if (revealTimerRef.current !== null) {
4359
+ clearTimeout(revealTimerRef.current);
4360
+ revealTimerRef.current = null;
4361
+ }
4362
+ };
4363
+ const applyWidthTarget = (0, import_react29.useCallback)(() => {
4364
+ const willExpand = !collapsedTarget.current;
4365
+ setWidthCollapsed(collapsedTarget.current);
4366
+ clearRevealTimer();
4367
+ if (willExpand && !reducedMotion) {
4368
+ revealTimerRef.current = setTimeout(
4369
+ () => setEntriesVisible(true),
4370
+ STUDIO_SIDEBAR_WIDTH_S * 1e3 * STUDIO_SIDEBAR_EXPAND_REVEAL_FRAC
4371
+ );
4372
+ }
4373
+ }, [reducedMotion]);
4374
+ (0, import_react29.useEffect)(() => {
4375
+ clearWidthTimer();
4376
+ clearRevealTimer();
4377
+ if (reducedMotion) {
4378
+ setWidthCollapsed(collapsed);
4379
+ setEntriesVisible(true);
4380
+ return;
4381
+ }
4382
+ if (isFirstRender.current) {
4383
+ isFirstRender.current = false;
4384
+ setWidthCollapsed(collapsed);
4385
+ setEntriesVisible(true);
4386
+ return;
4387
+ }
4388
+ setEntriesVisible(false);
4389
+ widthTimerRef.current = setTimeout(
4390
+ applyWidthTarget,
4391
+ STUDIO_SIDEBAR_ENTRIES_OUT_S * 1e3 * WIDTH_OVERLAP_FRAC
4392
+ );
4393
+ return () => {
4394
+ clearWidthTimer();
4395
+ clearRevealTimer();
4396
+ };
4397
+ }, [collapsed, reducedMotion, applyWidthTarget]);
4398
+ const onEntriesBlurOutComplete = (0, import_react29.useCallback)(() => {
4399
+ applyWidthTarget();
4400
+ }, [applyWidthTarget]);
4401
+ const onPanelWidthComplete = (0, import_react29.useCallback)(() => {
4402
+ clearRevealTimer();
4403
+ setEntriesVisible(true);
4404
+ }, []);
4405
+ const isCollapsedRail = widthCollapsed;
4406
+ return {
4407
+ widthCollapsed,
4408
+ isCollapsedRail,
4409
+ entriesVisible,
4410
+ onEntriesBlurOutComplete,
4411
+ onPanelWidthComplete
4412
+ };
4413
+ }
4414
+
4415
+ // src/components/studio/sidebar-backdrop.tsx
4416
+ var import_react30 = require("motion/react");
4417
+ var import_jsx_runtime31 = require("react/jsx-runtime");
4418
+ var StudioSidebarBackdrop = ({
4419
+ open,
4420
+ onClose
4421
+ }) => {
4422
+ const reducedMotion = (0, import_react30.useReducedMotion)();
4423
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react30.AnimatePresence, { children: open ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
4424
+ import_react30.motion.button,
4425
+ {
4426
+ type: "button",
4427
+ className: "fixed inset-0 z-40 bg-foreground/30 backdrop-blur-[2px] md:hidden",
4428
+ "aria-label": "Close menu",
4429
+ initial: { opacity: 0 },
4430
+ animate: { opacity: 1 },
4431
+ exit: { opacity: 0 },
4432
+ transition: studioSidebarBackdropTransition(!!reducedMotion),
4433
+ onClick: onClose
4434
+ }
4435
+ ) : null });
4436
+ };
4437
+
4438
+ // src/components/studio/sidebar-context.tsx
4439
+ var import_react31 = require("react");
4440
+ var StudioSidebarContext = (0, import_react31.createContext)({
4441
+ collapsed: false,
4442
+ isMobile: false,
4443
+ isCollapsedRail: false,
4444
+ iconOnlyLayout: false
4445
+ });
4446
+ function useStudioSidebarLayout() {
4447
+ return (0, import_react31.useContext)(StudioSidebarContext);
4448
+ }
4449
+
4450
+ // src/components/studio/sidebar.tsx
4451
+ var import_react35 = require("react");
4452
+ var import_react36 = require("motion/react");
4453
+
4454
+ // src/components/studio/sidebar-entries.tsx
4455
+ var import_react32 = require("motion/react");
4456
+ var import_jsx_runtime32 = require("react/jsx-runtime");
4457
+ var StudioSidebarEntries = ({
4458
+ visible,
4459
+ onBlurOutComplete,
4460
+ children,
4461
+ className
4462
+ }) => {
4463
+ const reducedMotion = (0, import_react32.useReducedMotion)();
4464
+ if (reducedMotion) {
4465
+ return visible ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: cn("flex min-h-0 flex-1 flex-col", className), children }) : null;
4466
+ }
4467
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
4468
+ import_react32.motion.div,
4469
+ {
4470
+ className: cn("flex min-h-0 flex-1 flex-col", className),
4471
+ initial: false,
4472
+ variants: studioSidebarEntriesContainerVariants,
4473
+ animate: visible ? "visible" : "hidden",
4474
+ transition: studioSidebarEntriesTransition(visible, false),
4475
+ onAnimationComplete: (definition) => {
4476
+ if (definition === "hidden") {
4477
+ onBlurOutComplete();
4478
+ }
4479
+ },
4480
+ style: { pointerEvents: visible ? "auto" : "none" },
4481
+ "aria-hidden": !visible,
4482
+ children
4483
+ }
4484
+ );
4485
+ };
4486
+
4487
+ // src/components/studio/sidebar-footer.tsx
4488
+ var import_lucide_react10 = require("lucide-react");
4489
+
4490
+ // src/auth/provider.tsx
4491
+ var import_react33 = require("react");
4492
+ var import_jsx_runtime33 = require("react/jsx-runtime");
4279
4493
  function isInsideIframe() {
4280
4494
  try {
4281
4495
  return typeof window !== "undefined" && window.self !== window.top;
@@ -4283,22 +4497,26 @@ function isInsideIframe() {
4283
4497
  return true;
4284
4498
  }
4285
4499
  }
4286
- var SessionContext = (0, import_react28.createContext)(void 0);
4500
+ var SessionContext = (0, import_react33.createContext)(void 0);
4287
4501
  var useSession = () => {
4288
- const context = (0, import_react28.useContext)(SessionContext);
4502
+ const context = (0, import_react33.useContext)(SessionContext);
4289
4503
  if (context === void 0) {
4290
4504
  throw new Error("useSession must be used within a SessionProvider");
4291
4505
  }
4292
4506
  return context;
4293
4507
  };
4508
+ var useOptionalSession = () => {
4509
+ const context = (0, import_react33.useContext)(SessionContext);
4510
+ return context ?? null;
4511
+ };
4294
4512
  var SessionProvider = ({
4295
4513
  children,
4296
4514
  enabled = true
4297
4515
  }) => {
4298
- const [user, setUser] = (0, import_react28.useState)(null);
4299
- const [loading, setLoading] = (0, import_react28.useState)(enabled);
4300
- const [embedded] = (0, import_react28.useState)(isInsideIframe);
4301
- (0, import_react28.useEffect)(() => {
4516
+ const [user, setUser] = (0, import_react33.useState)(null);
4517
+ const [loading, setLoading] = (0, import_react33.useState)(enabled);
4518
+ const [embedded] = (0, import_react33.useState)(isInsideIframe);
4519
+ (0, import_react33.useEffect)(() => {
4302
4520
  if (!enabled) {
4303
4521
  setLoading(false);
4304
4522
  return;
@@ -4359,7 +4577,7 @@ var SessionProvider = ({
4359
4577
  messageCleanup?.();
4360
4578
  };
4361
4579
  }, [enabled, embedded]);
4362
- const logout = (0, import_react28.useCallback)(() => {
4580
+ const logout = (0, import_react33.useCallback)(() => {
4363
4581
  clearTokens();
4364
4582
  setUser(null);
4365
4583
  const returnTo = encodeURIComponent(
@@ -4369,7 +4587,7 @@ var SessionProvider = ({
4369
4587
  () => window.location.href = `/api/auth/login?return_to=${returnTo}`
4370
4588
  );
4371
4589
  }, []);
4372
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
4590
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
4373
4591
  SessionContext.Provider,
4374
4592
  {
4375
4593
  value: {
@@ -4384,9 +4602,1134 @@ var SessionProvider = ({
4384
4602
  );
4385
4603
  };
4386
4604
 
4605
+ // src/components/studio/sidebar-layout.ts
4606
+ function studioSidebarIconOnlyLayout(isMobile, isCollapsedRail) {
4607
+ if (isMobile) return false;
4608
+ return isCollapsedRail;
4609
+ }
4610
+ var studioSidebarCollapsedRailInsetClass = "box-border w-full px-1.5";
4611
+ var studioSidebarCollapsedRailChipRowClass = "flex w-full justify-center";
4612
+ function studioSidebarNavItemClasses(iconOnly, isActive) {
4613
+ if (iconOnly) {
4614
+ return cn(
4615
+ studioSidebarNavItemClass,
4616
+ studioSidebarNavItemLayout(true),
4617
+ isActive ? studioSidebarCollapsedRailItemActiveClass : studioSidebarCollapsedRailItemIdleClass
4618
+ );
4619
+ }
4620
+ return cn(
4621
+ studioSidebarNavItemClass,
4622
+ studioSidebarNavItemLayout(false),
4623
+ isActive ? studioSidebarNavItemActiveClass : studioSidebarNavItemIdleClass
4624
+ );
4625
+ }
4626
+
4627
+ // src/components/studio/sidebar-entry-motion.tsx
4628
+ var import_react34 = require("motion/react");
4629
+ var import_jsx_runtime34 = require("react/jsx-runtime");
4630
+ var StudioSidebarEntryMotion = ({
4631
+ children,
4632
+ className
4633
+ }) => {
4634
+ const reducedMotion = (0, import_react34.useReducedMotion)();
4635
+ if (reducedMotion) {
4636
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className, children });
4637
+ }
4638
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_react34.motion.div, { variants: studioSidebarEntryItemVariants, className: cn(className), children });
4639
+ };
4640
+
4641
+ // src/components/studio/sidebar-tooltip.tsx
4642
+ var import_jsx_runtime35 = require("react/jsx-runtime");
4643
+ var StudioSidebarTooltip = ({
4644
+ label,
4645
+ enabled,
4646
+ children
4647
+ }) => {
4648
+ if (!enabled) return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_jsx_runtime35.Fragment, { children });
4649
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(Tooltip, { children: [
4650
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(TooltipTrigger, { asChild: true, children }),
4651
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(TooltipContent, { side: "right", className: "text-xs", children: label })
4652
+ ] });
4653
+ };
4654
+
4655
+ // src/components/studio/sidebar-footer.tsx
4656
+ var import_jsx_runtime36 = require("react/jsx-runtime");
4657
+ function userInitials(name, email) {
4658
+ const fromName = name.trim().split(/\s+/).map((part) => part.charAt(0)).join("").slice(0, 2).toUpperCase();
4659
+ if (fromName) return fromName;
4660
+ return email.charAt(0).toUpperCase() || "?";
4661
+ }
4662
+ var StudioSidebarFooter = ({
4663
+ iconOnlyLayout,
4664
+ showTooltips,
4665
+ onSignOut,
4666
+ emptyCaption = null
4667
+ }) => {
4668
+ const session = useOptionalSession();
4669
+ const user = session?.user ?? null;
4670
+ const handleSignOut = () => {
4671
+ session?.logout();
4672
+ onSignOut?.();
4673
+ };
4674
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(StudioSidebarEntryMotion, { children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
4675
+ "footer",
4676
+ {
4677
+ className: cn(
4678
+ "mt-auto w-full shrink-0 py-2.5",
4679
+ iconOnlyLayout ? studioSidebarCollapsedRailInsetClass : "px-2.5"
4680
+ ),
4681
+ children: user ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex flex-col gap-2", children: [
4682
+ iconOnlyLayout ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: studioSidebarCollapsedRailChipRowClass, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(Avatar, { size: "sm", className: "size-8", children: [
4683
+ user.user_photo_url ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(AvatarImage, { src: user.user_photo_url, alt: user.user_name }) : null,
4684
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(AvatarFallback, { className: "text-[10px]", children: userInitials(user.user_name, user.user_email) })
4685
+ ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex min-w-0 items-center gap-2.5", children: [
4686
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(Avatar, { size: "sm", children: [
4687
+ user.user_photo_url ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(AvatarImage, { src: user.user_photo_url, alt: user.user_name }) : null,
4688
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(AvatarFallback, { children: userInitials(user.user_name, user.user_email) })
4689
+ ] }),
4690
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "min-w-0 flex-1", children: [
4691
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "truncate text-sm font-medium text-foreground", children: user.user_name }),
4692
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "truncate text-xs text-muted-foreground", children: user.user_email })
4693
+ ] })
4694
+ ] }),
4695
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
4696
+ "div",
4697
+ {
4698
+ className: iconOnlyLayout ? studioSidebarCollapsedRailChipRowClass : void 0,
4699
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(StudioSidebarTooltip, { label: "Sign out", enabled: showTooltips, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
4700
+ "button",
4701
+ {
4702
+ type: "button",
4703
+ onClick: handleSignOut,
4704
+ className: cn(
4705
+ studioSidebarNavItemClasses(iconOnlyLayout, false),
4706
+ iconOnlyLayout && "inline-flex"
4707
+ ),
4708
+ "aria-label": "Sign out",
4709
+ children: [
4710
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_lucide_react10.LogOut, { className: "size-3.5 shrink-0" }),
4711
+ !iconOnlyLayout ? "Sign out" : null
4712
+ ]
4713
+ }
4714
+ ) })
4715
+ }
4716
+ )
4717
+ ] }) : !iconOnlyLayout && emptyCaption ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "px-1 text-xs text-muted-foreground", children: emptyCaption }) : null
4718
+ }
4719
+ ) });
4720
+ };
4721
+
4722
+ // src/components/studio/sidebar-header.tsx
4723
+ var import_lucide_react11 = require("lucide-react");
4724
+ var import_jsx_runtime37 = require("react/jsx-runtime");
4725
+ var sidebarHeaderClass = "flex h-12 shrink-0 items-center px-2";
4726
+ var toggleButtonClass = cn(
4727
+ "flex shrink-0 items-center justify-center rounded-lg text-muted-foreground transition-colors",
4728
+ "hover:bg-muted hover:text-foreground",
4729
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15"
4730
+ );
4731
+ var SidebarToggleButton = ({ ariaLabel, expanded, onClick, children }) => /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
4732
+ "button",
4733
+ {
4734
+ type: "button",
4735
+ onClick,
4736
+ className: cn(toggleButtonClass, "size-7"),
4737
+ "aria-label": ariaLabel,
4738
+ "aria-expanded": expanded,
4739
+ children
4740
+ }
4741
+ );
4742
+ var CollapsedBrandToggle = ({
4743
+ onExpand,
4744
+ brand
4745
+ }) => /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: studioSidebarCollapsedRailChipRowClass, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(StudioSidebarTooltip, { label: "Expand sidebar", enabled: true, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
4746
+ "button",
4747
+ {
4748
+ type: "button",
4749
+ onClick: onExpand,
4750
+ "aria-label": "Expand sidebar",
4751
+ "aria-expanded": false,
4752
+ className: cn(
4753
+ toggleButtonClass,
4754
+ "group relative inline-flex size-8 items-center justify-center overflow-hidden rounded-lg"
4755
+ ),
4756
+ children: [
4757
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
4758
+ "span",
4759
+ {
4760
+ "aria-hidden": true,
4761
+ className: cn(
4762
+ "pointer-events-none flex items-center justify-center",
4763
+ "transition-[opacity,transform] duration-200 ease-out",
4764
+ "group-hover:scale-90 group-hover:opacity-0"
4765
+ ),
4766
+ children: brand
4767
+ }
4768
+ ),
4769
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
4770
+ import_lucide_react11.ChevronRight,
4771
+ {
4772
+ "aria-hidden": true,
4773
+ className: cn(
4774
+ "pointer-events-none absolute inset-0 m-auto size-4",
4775
+ "opacity-0 transition-[opacity,transform] duration-200 ease-out",
4776
+ "group-hover:opacity-100"
4777
+ )
4778
+ }
4779
+ )
4780
+ ]
4781
+ }
4782
+ ) }) });
4783
+ var StudioSidebarHeader = ({
4784
+ isCollapsedRail,
4785
+ isMobile,
4786
+ mobileOpen,
4787
+ onToggle,
4788
+ brand
4789
+ }) => {
4790
+ if (isMobile) {
4791
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("header", { className: cn(sidebarHeaderClass, "justify-between gap-2 pr-2"), children: [
4792
+ brand,
4793
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
4794
+ SidebarToggleButton,
4795
+ {
4796
+ ariaLabel: "Close menu",
4797
+ expanded: mobileOpen,
4798
+ onClick: onToggle,
4799
+ children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react11.X, { className: "size-3.5" })
4800
+ }
4801
+ )
4802
+ ] });
4803
+ }
4804
+ if (isCollapsedRail) {
4805
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
4806
+ "header",
4807
+ {
4808
+ className: cn(
4809
+ "flex h-12 shrink-0 items-center",
4810
+ studioSidebarCollapsedRailInsetClass
4811
+ ),
4812
+ children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(CollapsedBrandToggle, { onExpand: onToggle, brand })
4813
+ }
4814
+ );
4815
+ }
4816
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("header", { className: cn(sidebarHeaderClass, "justify-between gap-1 pr-2"), children: [
4817
+ brand,
4818
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
4819
+ SidebarToggleButton,
4820
+ {
4821
+ ariaLabel: "Collapse sidebar",
4822
+ expanded: true,
4823
+ onClick: onToggle,
4824
+ children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react11.ChevronLeft, { className: "size-4" })
4825
+ }
4826
+ )
4827
+ ] });
4828
+ };
4829
+
4830
+ // src/components/studio/sidebar-workforce.ts
4831
+ function workforceItemId(w) {
4832
+ return w.id ?? w.uid ?? w.name ?? "";
4833
+ }
4834
+ function workforceItemLabel(w) {
4835
+ return w.name ?? workforceItemId(w);
4836
+ }
4837
+ function workforceItemInitial(w) {
4838
+ const label = workforceItemLabel(w);
4839
+ return label.charAt(0).toUpperCase() || "?";
4840
+ }
4841
+
4842
+ // src/components/studio/sidebar-nav.tsx
4843
+ var import_jsx_runtime38 = require("react/jsx-runtime");
4844
+ var StudioSidebarNav = ({
4845
+ workforces,
4846
+ selectedId,
4847
+ onSelect,
4848
+ iconOnlyLayout,
4849
+ showTooltips
4850
+ }) => {
4851
+ if (workforces.length === 0) return null;
4852
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
4853
+ "nav",
4854
+ {
4855
+ className: cn(
4856
+ "flex min-h-0 flex-1 flex-col overflow-y-auto py-1",
4857
+ iconOnlyLayout ? cn(studioSidebarCollapsedRailInsetClass, "gap-1") : "gap-0.5 px-2"
4858
+ ),
4859
+ "aria-label": "Agents",
4860
+ children: workforces.map((w) => {
4861
+ const id = workforceItemId(w);
4862
+ const isActive = id === selectedId;
4863
+ const label = workforceItemLabel(w);
4864
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
4865
+ StudioSidebarEntryMotion,
4866
+ {
4867
+ className: iconOnlyLayout ? studioSidebarCollapsedRailChipRowClass : void 0,
4868
+ children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(StudioSidebarTooltip, { label, enabled: showTooltips, children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
4869
+ "button",
4870
+ {
4871
+ type: "button",
4872
+ onClick: () => onSelect(id),
4873
+ "aria-pressed": isActive,
4874
+ "aria-label": label,
4875
+ className: cn(
4876
+ studioSidebarNavItemClasses(iconOnlyLayout, isActive),
4877
+ iconOnlyLayout && "inline-flex"
4878
+ ),
4879
+ children: iconOnlyLayout ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "text-xs font-semibold leading-none", children: workforceItemInitial(w) }) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "min-w-0 truncate", children: label })
4880
+ }
4881
+ ) })
4882
+ },
4883
+ id
4884
+ );
4885
+ })
4886
+ }
4887
+ );
4888
+ };
4889
+
4890
+ // src/components/studio/timbal-mark.tsx
4891
+ var import_shaders_react = require("@paper-design/shaders-react");
4892
+ var import_jsx_runtime39 = require("react/jsx-runtime");
4893
+ var DEFAULT_SIZE = 64;
4894
+ var TRANSPARENT_BACK = "#00000000";
4895
+ 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=";
4896
+ function TimbalMark({
4897
+ className,
4898
+ size = DEFAULT_SIZE,
4899
+ src = TIMBAL_SYMBOL_DATA_URI
4900
+ }) {
4901
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
4902
+ "div",
4903
+ {
4904
+ className: cn("relative shrink-0 bg-transparent", className),
4905
+ style: { width: size, height: size },
4906
+ role: "img",
4907
+ "aria-label": "Timbal",
4908
+ children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
4909
+ import_shaders_react.LiquidMetal,
4910
+ {
4911
+ width: size,
4912
+ height: size,
4913
+ image: src,
4914
+ colorBack: TRANSPARENT_BACK,
4915
+ colorTint: "#ffffff",
4916
+ shape: "none",
4917
+ repetition: 2,
4918
+ softness: 0.1,
4919
+ shiftRed: 0.3,
4920
+ shiftBlue: 0.3,
4921
+ distortion: 0.07,
4922
+ contour: 0.4,
4923
+ angle: 70,
4924
+ speed: 1,
4925
+ scale: 0.6,
4926
+ fit: "contain",
4927
+ className: "size-full bg-transparent",
4928
+ style: { background: "transparent" },
4929
+ webGlContextAttributes: {
4930
+ alpha: true,
4931
+ premultipliedAlpha: false
4932
+ }
4933
+ }
4934
+ )
4935
+ }
4936
+ );
4937
+ }
4938
+
4939
+ // src/components/studio/sidebar.tsx
4940
+ var import_jsx_runtime40 = require("react/jsx-runtime");
4941
+ var DEFAULT_BREAKPOINT_PX = 768;
4942
+ function readPersistedCollapsed(key) {
4943
+ if (!key || typeof window === "undefined") return false;
4944
+ try {
4945
+ return window.localStorage.getItem(key) === "1";
4946
+ } catch {
4947
+ return false;
4948
+ }
4949
+ }
4950
+ function writePersistedCollapsed(key, collapsed) {
4951
+ if (!key || typeof window === "undefined") return;
4952
+ try {
4953
+ window.localStorage.setItem(key, collapsed ? "1" : "0");
4954
+ } catch {
4955
+ }
4956
+ }
4957
+ var StudioSidebarPanel = ({
4958
+ workforces,
4959
+ selectedId,
4960
+ onSelect,
4961
+ collapsed,
4962
+ onCollapsedChange,
4963
+ isMobile,
4964
+ mobileOpen,
4965
+ onMobileOpenChange,
4966
+ widthCollapsed,
4967
+ entriesVisible,
4968
+ onEntriesBlurOutComplete,
4969
+ onPanelWidthComplete,
4970
+ brand,
4971
+ emptyCaption = null
4972
+ }) => {
4973
+ const reducedMotion = (0, import_react36.useReducedMotion)();
4974
+ const isCollapsedRail = widthCollapsed && !isMobile;
4975
+ const iconOnlyLayout = studioSidebarIconOnlyLayout(isMobile, isCollapsedRail);
4976
+ const isDrawerOpen = isMobile && mobileOpen;
4977
+ const widthDirection = widthCollapsed ? "collapse" : "expand";
4978
+ const widthTransition = studioSidebarWidthTransition(
4979
+ !!reducedMotion,
4980
+ widthDirection
4981
+ );
4982
+ const handleToggle = () => {
4983
+ if (isMobile) {
4984
+ onMobileOpenChange(false);
4985
+ return;
4986
+ }
4987
+ onCollapsedChange(!collapsed);
4988
+ };
4989
+ const panelWidthPx = isMobile ? SIDEBAR_MOBILE_PX : widthCollapsed ? SIDEBAR_WIDTH_COLLAPSED_PX : SIDEBAR_WIDTH_PX;
4990
+ const brandNode = brand ?? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(TimbalMark, { size: 32 });
4991
+ const panel = /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
4992
+ import_react36.motion.div,
4993
+ {
4994
+ "data-sidebar-collapsed": isCollapsedRail ? "" : void 0,
4995
+ className: cn(
4996
+ "flex h-full flex-col overflow-hidden",
4997
+ studioSidebarPanelClass,
4998
+ isMobile ? "rounded-none rounded-r-2xl" : "rounded-2xl"
4999
+ ),
5000
+ initial: false,
5001
+ animate: { width: panelWidthPx },
5002
+ transition: widthTransition,
5003
+ style: { willChange: entriesVisible ? void 0 : "width" },
5004
+ onAnimationComplete: isMobile || entriesVisible ? void 0 : () => onPanelWidthComplete(),
5005
+ children: [
5006
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
5007
+ StudioSidebarHeader,
5008
+ {
5009
+ isCollapsedRail,
5010
+ isMobile,
5011
+ mobileOpen,
5012
+ onToggle: handleToggle,
5013
+ brand: brandNode
5014
+ }
5015
+ ),
5016
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
5017
+ StudioSidebarEntries,
5018
+ {
5019
+ visible: entriesVisible,
5020
+ onBlurOutComplete: onEntriesBlurOutComplete,
5021
+ children: [
5022
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
5023
+ "div",
5024
+ {
5025
+ id: DOM_IDS.sidebarRuntimeAnchor,
5026
+ className: cn(
5027
+ "min-h-0 shrink-0 empty:hidden",
5028
+ iconOnlyLayout ? "px-1.5 pt-1.5" : "px-2 pt-1.5"
5029
+ )
5030
+ }
5031
+ ),
5032
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
5033
+ StudioSidebarNav,
5034
+ {
5035
+ workforces,
5036
+ selectedId,
5037
+ onSelect,
5038
+ iconOnlyLayout,
5039
+ showTooltips: isCollapsedRail
5040
+ }
5041
+ ),
5042
+ workforces.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "min-h-0 flex-1" }) : null,
5043
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
5044
+ StudioSidebarFooter,
5045
+ {
5046
+ iconOnlyLayout,
5047
+ showTooltips: isCollapsedRail,
5048
+ onSignOut: isMobile ? () => onMobileOpenChange(false) : void 0,
5049
+ emptyCaption
5050
+ }
5051
+ )
5052
+ ]
5053
+ }
5054
+ )
5055
+ ]
5056
+ }
5057
+ );
5058
+ if (isMobile) {
5059
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
5060
+ import_react36.motion.aside,
5061
+ {
5062
+ className: "fixed inset-y-0 left-0 z-[60] flex",
5063
+ "aria-label": "Studio navigation",
5064
+ "aria-hidden": !mobileOpen,
5065
+ initial: false,
5066
+ animate: {
5067
+ x: isDrawerOpen ? 0 : -(SIDEBAR_MOBILE_PX + 32)
5068
+ },
5069
+ transition: studioSidebarDrawerTransition(!!reducedMotion),
5070
+ style: { pointerEvents: isDrawerOpen ? "auto" : "none" },
5071
+ children: panel
5072
+ }
5073
+ );
5074
+ }
5075
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
5076
+ "aside",
5077
+ {
5078
+ className: "absolute inset-y-0 left-0 z-[60] flex py-[var(--studio-sidebar-gap)] pl-[var(--studio-sidebar-gap)]",
5079
+ "aria-label": "Studio navigation",
5080
+ children: panel
5081
+ }
5082
+ );
5083
+ };
5084
+ var StudioSidebar = ({
5085
+ workforces: workforcesProp,
5086
+ selectedId: selectedIdProp,
5087
+ onSelect,
5088
+ defaultCollapsed = false,
5089
+ persistKey = STORAGE_KEYS.sidebarCollapsed,
5090
+ mobileBreakpointPx = DEFAULT_BREAKPOINT_PX,
5091
+ brand,
5092
+ emptyCaption,
5093
+ mobileOpen: mobileOpenProp,
5094
+ onMobileOpenChange: onMobileOpenChangeProp
5095
+ }) => {
5096
+ const reducedMotion = (0, import_react36.useReducedMotion)();
5097
+ const fetched = useWorkforces({ enabled: workforcesProp === void 0 });
5098
+ const workforces = workforcesProp ?? fetched.workforces;
5099
+ const [internalSelected, setInternalSelected] = (0, import_react35.useState)(
5100
+ selectedIdProp ?? ""
5101
+ );
5102
+ (0, import_react35.useEffect)(() => {
5103
+ if (selectedIdProp !== void 0) return;
5104
+ if (internalSelected) return;
5105
+ const first = workforces[0]?.id ?? workforces[0]?.uid ?? workforces[0]?.name;
5106
+ if (first) setInternalSelected(first);
5107
+ }, [workforces, selectedIdProp, internalSelected]);
5108
+ const selectedId = selectedIdProp ?? internalSelected ?? workforces[0]?.id ?? workforces[0]?.uid ?? workforces[0]?.name ?? "";
5109
+ const handleSelect = (0, import_react35.useCallback)(
5110
+ (id) => {
5111
+ if (selectedIdProp === void 0) setInternalSelected(id);
5112
+ onSelect?.(id);
5113
+ },
5114
+ [selectedIdProp, onSelect]
5115
+ );
5116
+ const [collapsed, setCollapsed] = (0, import_react35.useState)(() => {
5117
+ const persisted = readPersistedCollapsed(persistKey);
5118
+ return persisted || defaultCollapsed;
5119
+ });
5120
+ const handleCollapsedChange = (0, import_react35.useCallback)(
5121
+ (next) => {
5122
+ setCollapsed(next);
5123
+ writePersistedCollapsed(persistKey, next);
5124
+ },
5125
+ [persistKey]
5126
+ );
5127
+ const [isMobile, setIsMobile] = (0, import_react35.useState)(() => {
5128
+ if (typeof window === "undefined") return false;
5129
+ return window.innerWidth < mobileBreakpointPx;
5130
+ });
5131
+ (0, import_react35.useEffect)(() => {
5132
+ if (typeof window === "undefined") return;
5133
+ const onResize = () => setIsMobile(window.innerWidth < mobileBreakpointPx);
5134
+ onResize();
5135
+ window.addEventListener("resize", onResize);
5136
+ return () => window.removeEventListener("resize", onResize);
5137
+ }, [mobileBreakpointPx]);
5138
+ const [internalMobileOpen, setInternalMobileOpen] = (0, import_react35.useState)(false);
5139
+ const mobileOpen = mobileOpenProp ?? internalMobileOpen;
5140
+ const setMobileOpen = (0, import_react35.useCallback)(
5141
+ (next) => {
5142
+ if (mobileOpenProp === void 0) setInternalMobileOpen(next);
5143
+ onMobileOpenChangeProp?.(next);
5144
+ },
5145
+ [mobileOpenProp, onMobileOpenChangeProp]
5146
+ );
5147
+ const effectiveCollapsed = isMobile ? false : collapsed;
5148
+ const {
5149
+ widthCollapsed,
5150
+ entriesVisible: phaseEntriesVisible,
5151
+ onEntriesBlurOutComplete,
5152
+ onPanelWidthComplete
5153
+ } = useSidebarCollapsePhase(effectiveCollapsed, !!reducedMotion);
5154
+ const entriesVisible = isMobile || phaseEntriesVisible;
5155
+ const isCollapsedRail = widthCollapsed && !isMobile;
5156
+ const iconOnlyLayout = studioSidebarIconOnlyLayout(isMobile, isCollapsedRail);
5157
+ const contextValue = (0, import_react35.useMemo)(
5158
+ () => ({
5159
+ collapsed: effectiveCollapsed,
5160
+ isMobile,
5161
+ isCollapsedRail,
5162
+ iconOnlyLayout
5163
+ }),
5164
+ [effectiveCollapsed, isMobile, isCollapsedRail, iconOnlyLayout]
5165
+ );
5166
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(StudioSidebarContext.Provider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
5167
+ StudioSidebarPanel,
5168
+ {
5169
+ workforces,
5170
+ selectedId,
5171
+ onSelect: handleSelect,
5172
+ collapsed: effectiveCollapsed,
5173
+ onCollapsedChange: handleCollapsedChange,
5174
+ isMobile,
5175
+ mobileOpen,
5176
+ onMobileOpenChange: setMobileOpen,
5177
+ widthCollapsed,
5178
+ entriesVisible,
5179
+ onEntriesBlurOutComplete,
5180
+ onPanelWidthComplete,
5181
+ brand,
5182
+ emptyCaption
5183
+ }
5184
+ ) });
5185
+ };
5186
+
5187
+ // src/components/studio/sidebar-runtime-portal.tsx
5188
+ var import_react37 = require("react");
5189
+ var import_react_dom = require("react-dom");
5190
+ var import_lucide_react12 = require("lucide-react");
5191
+ var import_react38 = require("@assistant-ui/react");
5192
+ var import_jsx_runtime41 = require("react/jsx-runtime");
5193
+ var StudioSidebarRuntimePortal = ({
5194
+ label = "New chat"
5195
+ }) => {
5196
+ const { iconOnlyLayout } = useStudioSidebarLayout();
5197
+ const hasMessages = (0, import_react38.useThread)((s) => s.messages.length > 0);
5198
+ const { clear } = useTimbalRuntime();
5199
+ const [anchor, setAnchor] = (0, import_react37.useState)(null);
5200
+ const startNewChat = (0, import_react37.useCallback)(() => {
5201
+ clear();
5202
+ }, [clear]);
5203
+ (0, import_react37.useLayoutEffect)(() => {
5204
+ setAnchor(document.getElementById(DOM_IDS.sidebarRuntimeAnchor));
5205
+ }, []);
5206
+ if (!anchor || !hasMessages) return null;
5207
+ const button = /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
5208
+ "button",
5209
+ {
5210
+ type: "button",
5211
+ onClick: startNewChat,
5212
+ "aria-label": label,
5213
+ className: studioSidebarNavItemClasses(iconOnlyLayout, false),
5214
+ children: [
5215
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react12.MessageSquarePlus, { className: "size-3.5 shrink-0" }),
5216
+ !iconOnlyLayout ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "min-w-0 truncate", children: label }) : null
5217
+ ]
5218
+ }
5219
+ );
5220
+ return (0, import_react_dom.createPortal)(
5221
+ iconOnlyLayout ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(Tooltip, { children: [
5222
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(TooltipTrigger, { asChild: true, children: button }),
5223
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(TooltipContent, { side: "right", className: "text-xs", children: label })
5224
+ ] }) : button,
5225
+ anchor
5226
+ );
5227
+ };
5228
+
5229
+ // src/components/studio/welcome.tsx
5230
+ var import_react39 = require("motion/react");
5231
+ var import_react40 = require("@assistant-ui/react");
5232
+ var import_jsx_runtime42 = require("react/jsx-runtime");
5233
+ var luxuryEase2 = [0.16, 1, 0.3, 1];
5234
+ var welcomeStagger2 = {
5235
+ initial: {},
5236
+ animate: {
5237
+ transition: { staggerChildren: 0.16, delayChildren: 0.18 }
5238
+ }
5239
+ };
5240
+ var welcomeItem2 = {
5241
+ initial: { opacity: 0, y: 14 },
5242
+ animate: {
5243
+ opacity: 1,
5244
+ y: 0,
5245
+ transition: { duration: 0.9, ease: luxuryEase2 }
5246
+ }
5247
+ };
5248
+ var welcomeIcon2 = {
5249
+ initial: { opacity: 0, y: 10, scale: 0.96 },
5250
+ animate: {
5251
+ opacity: 1,
5252
+ y: 0,
5253
+ scale: 1,
5254
+ transition: { duration: 1.1, ease: luxuryEase2 }
5255
+ }
5256
+ };
5257
+ var StudioWelcome = ({ config, icon }) => {
5258
+ const isEmpty = (0, import_react40.useThread)((s) => s.messages.length === 0);
5259
+ if (!isEmpty) return null;
5260
+ const iconNode = icon ?? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
5261
+ TimbalMark,
5262
+ {
5263
+ size: 112,
5264
+ className: "max-md:scale-[0.58] max-md:origin-center"
5265
+ }
5266
+ );
5267
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "aui-thread-welcome-center flex w-full grow flex-col items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
5268
+ import_react39.motion.div,
5269
+ {
5270
+ className: "aui-thread-welcome-message flex flex-col items-center justify-center px-2 text-center sm:px-4",
5271
+ variants: welcomeStagger2,
5272
+ initial: "initial",
5273
+ animate: "animate",
5274
+ children: [
5275
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_react39.motion.div, { variants: welcomeIcon2, className: "mb-4 md:mb-5", children: iconNode }),
5276
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
5277
+ import_react39.motion.h1,
5278
+ {
5279
+ variants: welcomeItem2,
5280
+ className: "aui-thread-welcome-message-inner text-xl font-semibold sm:text-2xl",
5281
+ children: config?.heading ?? "How can I help you today?"
5282
+ }
5283
+ ),
5284
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
5285
+ import_react39.motion.p,
5286
+ {
5287
+ variants: welcomeItem2,
5288
+ className: "aui-thread-welcome-message-inner mt-2 text-muted-foreground",
5289
+ children: config?.subheading ?? "Send a message to start a conversation."
5290
+ }
5291
+ )
5292
+ ]
5293
+ }
5294
+ ) }) });
5295
+ };
5296
+
5297
+ // src/components/studio/studio-shell.tsx
5298
+ var import_jsx_runtime43 = require("react/jsx-runtime");
5299
+ var import_react43 = require("react");
5300
+ var DEFAULT_BREAKPOINT_PX2 = 768;
5301
+ function readPersistedCollapsed2(key) {
5302
+ if (!key || typeof window === "undefined") return false;
5303
+ try {
5304
+ return window.localStorage.getItem(key) === "1";
5305
+ } catch {
5306
+ return false;
5307
+ }
5308
+ }
5309
+ function writePersistedCollapsed2(key, collapsed) {
5310
+ if (!key || typeof window === "undefined") return;
5311
+ try {
5312
+ window.localStorage.setItem(key, collapsed ? "1" : "0");
5313
+ } catch {
5314
+ }
5315
+ }
5316
+ function makeComposerWithPortal(BaseComposer) {
5317
+ const Resolved = BaseComposer ?? Composer;
5318
+ return function StudioComposerWithSidebar(props) {
5319
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(import_jsx_runtime43.Fragment, { children: [
5320
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(StudioSidebarRuntimePortal, {}),
5321
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(Resolved, { ...props })
5322
+ ] });
5323
+ };
5324
+ }
5325
+ var TimbalStudioShell = ({
5326
+ workforceId,
5327
+ workforces: workforcesProp,
5328
+ workforcesFetch,
5329
+ workforcesBaseUrl,
5330
+ brand,
5331
+ headerActions,
5332
+ headerStart,
5333
+ defaultCollapsed = false,
5334
+ persistKey = STORAGE_KEYS.sidebarCollapsed,
5335
+ mobileBreakpointPx = DEFAULT_BREAKPOINT_PX2,
5336
+ sidebarEmptyCaption = null,
5337
+ welcome,
5338
+ components,
5339
+ ...chatProps
5340
+ }) => {
5341
+ const reducedMotion = (0, import_react42.useReducedMotion)();
5342
+ const shouldFetchWorkforces = !workforceId && workforcesProp === void 0;
5343
+ const fetched = useWorkforces({
5344
+ enabled: shouldFetchWorkforces,
5345
+ fetch: workforcesFetch,
5346
+ baseUrl: workforcesBaseUrl
5347
+ });
5348
+ const workforces = workforcesProp ?? fetched.workforces;
5349
+ const [internalSelected, setInternalSelected] = (0, import_react41.useState)(
5350
+ workforceId ?? ""
5351
+ );
5352
+ (0, import_react41.useEffect)(() => {
5353
+ if (workforceId) return;
5354
+ if (internalSelected) return;
5355
+ const first = workforces[0]?.id ?? workforces[0]?.uid ?? workforces[0]?.name;
5356
+ if (first) setInternalSelected(first);
5357
+ }, [workforces, workforceId, internalSelected]);
5358
+ const activeWorkforceId = workforceId ?? internalSelected ?? fetched.selectedId ?? "";
5359
+ const [collapsed, setCollapsed] = (0, import_react41.useState)(() => {
5360
+ const persisted = readPersistedCollapsed2(persistKey);
5361
+ return persisted || defaultCollapsed;
5362
+ });
5363
+ const [isMobile, setIsMobile] = (0, import_react41.useState)(() => {
5364
+ if (typeof window === "undefined") return false;
5365
+ return window.innerWidth < mobileBreakpointPx;
5366
+ });
5367
+ const [mobileSidebarOpen, setMobileSidebarOpen] = (0, import_react41.useState)(false);
5368
+ (0, import_react41.useEffect)(() => {
5369
+ if (typeof window === "undefined") return;
5370
+ const onResize = () => setIsMobile(window.innerWidth < mobileBreakpointPx);
5371
+ onResize();
5372
+ window.addEventListener("resize", onResize);
5373
+ return () => window.removeEventListener("resize", onResize);
5374
+ }, [mobileBreakpointPx]);
5375
+ (0, import_react41.useEffect)(() => {
5376
+ if (!isMobile) setMobileSidebarOpen(false);
5377
+ }, [isMobile]);
5378
+ (0, import_react41.useEffect)(() => {
5379
+ if (!mobileSidebarOpen) return;
5380
+ const onKeyDown = (e) => {
5381
+ if (e.key === "Escape") setMobileSidebarOpen(false);
5382
+ };
5383
+ window.addEventListener("keydown", onKeyDown);
5384
+ return () => window.removeEventListener("keydown", onKeyDown);
5385
+ }, [mobileSidebarOpen]);
5386
+ const effectiveCollapsed = isMobile ? false : collapsed;
5387
+ const {
5388
+ widthCollapsed,
5389
+ entriesVisible: phaseEntriesVisible,
5390
+ onEntriesBlurOutComplete,
5391
+ onPanelWidthComplete
5392
+ } = useSidebarCollapsePhase(effectiveCollapsed, !!reducedMotion);
5393
+ const entriesVisible = isMobile || phaseEntriesVisible;
5394
+ const isCollapsedRail = widthCollapsed && !isMobile;
5395
+ const iconOnlyLayout = studioSidebarIconOnlyLayout(isMobile, isCollapsedRail);
5396
+ const layoutDirection = widthCollapsed ? "collapse" : "expand";
5397
+ const layoutTransition = studioSidebarWidthTransition(
5398
+ !!reducedMotion,
5399
+ layoutDirection
5400
+ );
5401
+ const desktopInsetPx = widthCollapsed ? SIDEBAR_INSET_PX_COLLAPSED : SIDEBAR_INSET_PX_EXPANDED;
5402
+ const onCollapsedChange = (0, import_react41.useCallback)(
5403
+ (next) => {
5404
+ setCollapsed(next);
5405
+ writePersistedCollapsed2(persistKey, next);
5406
+ },
5407
+ [persistKey]
5408
+ );
5409
+ const handleSelectWorkforce = (0, import_react41.useCallback)(
5410
+ (id) => {
5411
+ if (!workforceId) setInternalSelected(id);
5412
+ if (isMobile) setMobileSidebarOpen(false);
5413
+ },
5414
+ [workforceId, isMobile]
5415
+ );
5416
+ const sidebarContext = (0, import_react41.useMemo)(
5417
+ () => ({
5418
+ collapsed: effectiveCollapsed,
5419
+ isMobile,
5420
+ isCollapsedRail,
5421
+ iconOnlyLayout
5422
+ }),
5423
+ [effectiveCollapsed, isMobile, isCollapsedRail, iconOnlyLayout]
5424
+ );
5425
+ const resolvedComponents = (0, import_react41.useMemo)(() => {
5426
+ const next = { Welcome: StudioWelcome, ...components };
5427
+ next.Composer = makeComposerWithPortal(components?.Composer);
5428
+ return next;
5429
+ }, [components]);
5430
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(StudioSidebarContext.Provider, { value: sidebarContext, children: /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
5431
+ "div",
5432
+ {
5433
+ className: cn(
5434
+ "relative h-dvh overflow-hidden bg-background",
5435
+ isMobile && mobileSidebarOpen && "max-md:overflow-hidden"
5436
+ ),
5437
+ style: studioChromeShellStyle,
5438
+ children: [
5439
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
5440
+ "div",
5441
+ {
5442
+ className: "pointer-events-none absolute inset-0 z-0 bg-background",
5443
+ "aria-hidden": true
5444
+ }
5445
+ ),
5446
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
5447
+ "div",
5448
+ {
5449
+ className: cn(
5450
+ "pointer-events-none absolute inset-0 z-0",
5451
+ studioPlaygroundGradientClass
5452
+ ),
5453
+ "aria-hidden": true
5454
+ }
5455
+ ),
5456
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
5457
+ StudioSidebarBackdrop,
5458
+ {
5459
+ open: isMobile && mobileSidebarOpen,
5460
+ onClose: () => setMobileSidebarOpen(false)
5461
+ }
5462
+ ),
5463
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
5464
+ StudioSidebarPanel,
5465
+ {
5466
+ workforces,
5467
+ selectedId: activeWorkforceId,
5468
+ onSelect: handleSelectWorkforce,
5469
+ collapsed: effectiveCollapsed,
5470
+ onCollapsedChange,
5471
+ isMobile,
5472
+ mobileOpen: mobileSidebarOpen,
5473
+ onMobileOpenChange: setMobileSidebarOpen,
5474
+ widthCollapsed,
5475
+ entriesVisible,
5476
+ onEntriesBlurOutComplete,
5477
+ onPanelWidthComplete,
5478
+ brand,
5479
+ emptyCaption: sidebarEmptyCaption
5480
+ }
5481
+ ),
5482
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
5483
+ import_react42.motion.header,
5484
+ {
5485
+ className: cn(
5486
+ "absolute top-0 right-0 z-40 flex items-start justify-between gap-2",
5487
+ "px-3 pt-[var(--studio-topbar-gap)] md:px-4",
5488
+ "left-0"
5489
+ ),
5490
+ initial: false,
5491
+ animate: { left: isMobile ? 0 : desktopInsetPx },
5492
+ transition: layoutTransition,
5493
+ children: [
5494
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
5495
+ "div",
5496
+ {
5497
+ className: cn(
5498
+ "flex min-w-0 flex-1 items-center gap-2",
5499
+ studioTopbarPillHeightClass
5500
+ ),
5501
+ children: [
5502
+ isMobile && !mobileSidebarOpen ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
5503
+ TimbalV2Button,
5504
+ {
5505
+ variant: "secondary",
5506
+ size: "sm",
5507
+ isIconOnly: true,
5508
+ className: studioTopbarIconPillClass,
5509
+ onClick: () => setMobileSidebarOpen(true),
5510
+ "aria-label": "Open menu",
5511
+ "aria-expanded": false,
5512
+ children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react13.Menu, { className: "size-4" })
5513
+ }
5514
+ ) : null,
5515
+ headerStart
5516
+ ]
5517
+ }
5518
+ ),
5519
+ headerActions ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "flex shrink-0 items-center gap-1", children: headerActions }) : null
5520
+ ]
5521
+ }
5522
+ ),
5523
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
5524
+ import_react42.motion.main,
5525
+ {
5526
+ className: cn(
5527
+ "relative z-10 flex h-full min-w-0 flex-col",
5528
+ "pt-[var(--studio-inset-top)]",
5529
+ "px-3 md:px-0"
5530
+ ),
5531
+ initial: false,
5532
+ animate: { paddingLeft: isMobile ? 12 : desktopInsetPx },
5533
+ transition: layoutTransition,
5534
+ children: activeWorkforceId ? /* @__PURE__ */ (0, import_react43.createElement)(
5535
+ TimbalChat,
5536
+ {
5537
+ ...chatProps,
5538
+ workforceId: activeWorkforceId,
5539
+ key: activeWorkforceId,
5540
+ welcome,
5541
+ components: resolvedComponents,
5542
+ className: cn("min-h-0 flex-1 bg-transparent", chatProps.className)
5543
+ }
5544
+ ) : null
5545
+ }
5546
+ )
5547
+ ]
5548
+ }
5549
+ ) });
5550
+ };
5551
+
5552
+ // src/components/studio/mode-toggle.tsx
5553
+ var import_react44 = require("react");
5554
+ var import_lucide_react14 = require("lucide-react");
5555
+ var import_jsx_runtime44 = require("react/jsx-runtime");
5556
+ var ModeToggle = ({
5557
+ theme,
5558
+ setTheme,
5559
+ className,
5560
+ label = "Toggle theme"
5561
+ }) => {
5562
+ const isControlled = theme !== void 0;
5563
+ const [internalIsDark, setInternalIsDark] = (0, import_react44.useState)(false);
5564
+ (0, import_react44.useEffect)(() => {
5565
+ if (isControlled) return;
5566
+ if (typeof document === "undefined") return;
5567
+ setInternalIsDark(document.documentElement.classList.contains("dark"));
5568
+ }, [isControlled]);
5569
+ const isDark = isControlled ? theme === "dark" : internalIsDark;
5570
+ const onClick = (0, import_react44.useCallback)(() => {
5571
+ const next = isDark ? "light" : "dark";
5572
+ if (setTheme) {
5573
+ setTheme(next);
5574
+ return;
5575
+ }
5576
+ if (typeof document === "undefined") return;
5577
+ document.documentElement.classList.toggle("dark", next === "dark");
5578
+ setInternalIsDark(next === "dark");
5579
+ }, [isDark, setTheme]);
5580
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
5581
+ TimbalV2Button,
5582
+ {
5583
+ variant: "secondary",
5584
+ size: "sm",
5585
+ isIconOnly: true,
5586
+ onClick,
5587
+ className: cn(
5588
+ studioTopbarPillHeightClass,
5589
+ studioTopbarIconPillClass,
5590
+ "relative",
5591
+ className
5592
+ ),
5593
+ "aria-label": label,
5594
+ title: label,
5595
+ children: [
5596
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react14.Sun, { className: "size-3.5 scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" }),
5597
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react14.Moon, { className: "absolute size-3.5 scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" }),
5598
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "sr-only", children: label })
5599
+ ]
5600
+ }
5601
+ );
5602
+ };
5603
+
5604
+ // src/artifacts/agent-instructions.ts
5605
+ var ARTIFACT_AGENT_INSTRUCTIONS = `
5606
+ ## Rich artifacts (Timbal chat UI)
5607
+
5608
+ 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.
5609
+
5610
+ ### Delivery channels (either works)
5611
+
5612
+ 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.
5613
+ 2. **Inline markdown fence** \u2014 embed the same JSON inside a fenced block:
5614
+
5615
+ \`\`\`timbal-artifact
5616
+ {"type":"chart","data":[{"month":"Jan","sales":120}]}
5617
+ \`\`\`
5618
+
5619
+ The alias \`\`\`timbal\`\`\` is also accepted.
5620
+
5621
+ ### Built-in artifact types
5622
+
5623
+ | \`type\` | Use for |
5624
+ |---|---|
5625
+ | \`chart\` | Bar, line, area, or pie charts. Fields: \`data\`, optional \`chartType\`, \`xKey\`, \`dataKey\`, \`title\`, \`unit\`. |
5626
+ | \`table\` | Tabular data. Fields: \`rows\`, optional \`columns\`, \`title\`. |
5627
+ | \`question\` | In-thread multiple choice. Fields: \`options: [{ id, label, description? }]\`, optional \`prompt\`, \`multi\`. User replies are sent back as a normal user message. |
5628
+ | \`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). |
5629
+ | \`json\` | Fallback structured view. Fields: \`data\`, optional \`title\`. |
5630
+ | \`ui\` | **Interactive UI** composed from a fixed node palette (hover, click, drag). See below. |
5631
+
5632
+ ### When to use \`type: "html"\`
5633
+
5634
+ 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\`.
5635
+
5636
+ - Inline \`<style>\`, \`<script>\`, SVG, and canvas are supported.
5637
+ - Default \`sandboxed: true\` runs in an isolated iframe with scripts enabled.
5638
+ - Set \`sandboxed: false\` only for trusted content that needs external CDN scripts/styles or full DOM freedom.
5639
+ - Prefer \`ui\` when controls should send chat messages or host events; prefer \`html\` for self-contained mini-apps and visual demos.
5640
+
5641
+ ### When to use \`type: "ui"\`
5642
+
5643
+ 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.
5644
+
5645
+ Each \`ui\` artifact has:
5646
+
5647
+ - \`initialState\` \u2014 optional object seeding local state (per widget instance).
5648
+ - \`root\` \u2014 a single node tree (see node kinds below).
5649
+ - optional \`title\` \u2014 card heading.
5650
+
5651
+ **Bindings:** anywhere a primitive is accepted, you may use \`{ "$bind": "dotted.path" }\` to read from \`initialState\` / local state (e.g. \`{ "$bind": "qty" }\`).
5652
+
5653
+ **Actions:** nodes may attach \`onClick\`, \`onChange\`, or \`onDragEnd\` with one action or an array:
5654
+
5655
+ | Action | Shape | Effect |
5656
+ |---|---|---|
5657
+ | User message | \`{ "kind": "message", "text": "..." }\` or \`{ "kind": "message", "text": { "$bind": "path" } }\` | Sends text as the next user message. |
5658
+ | Set state | \`{ "kind": "set", "path": "foo", "value": 1 }\` | Writes local widget state. |
5659
+ | Toggle boolean | \`{ "kind": "toggle", "path": "enabled" }\` | Flips a boolean at \`path\`. |
5660
+ | Host event | \`{ "kind": "emit", "name": "event-name", "payload": { ... } }\` | Bubbles to the host app (\`onArtifactEvent\` on \`<Thread>\`). |
5661
+
5662
+ ### \`ui\` node palette (\`root.kind\`)
5663
+
5664
+ | \`kind\` | Purpose | Key fields |
5665
+ |---|---|---|
5666
+ | \`box\` | Layout container | \`children\`, \`direction\` (\`row\`/\`col\`), \`gap\`, \`padding\`, \`align\`, \`justify\`, \`wrap\` |
5667
+ | \`text\` | Body text | \`value\`, optional \`muted\`, \`size\`, \`weight\` |
5668
+ | \`heading\` | Heading | \`value\`, optional \`level\` (1\u20134) |
5669
+ | \`badge\` | Pill label | \`value\`, optional \`tone\` (\`default\`, \`primary\`, \`success\`, \`warn\`, \`danger\`) |
5670
+ | \`button\` | Clickable button | \`label\`, optional \`variant\`, \`size\`, \`disabled\`, \`onClick\` |
5671
+ | \`toggle\` | Boolean switch | \`binding\` (state path), optional \`label\`, \`onChange\` |
5672
+ | \`slider\` | Numeric range | \`binding\`, optional \`min\`, \`max\`, \`step\`, \`label\`, \`showValue\`, \`onChange\` |
5673
+ | \`tooltip\` | Hover tooltip | \`content\`, \`child\` (single node), optional \`side\` |
5674
+ | \`draggable\` | Drag gesture | \`child\`, optional \`axis\` (\`x\`/\`y\`/\`both\`), \`snapBack\`, \`onDragEnd\` |
5675
+ | \`custom\` | Host-registered widget | \`name\`, optional \`props\`, \`children\` \u2014 only if the app registered that name |
5676
+
5677
+ ### Example \`ui\` artifact
5678
+
5679
+ \`\`\`json
5680
+ {
5681
+ "type": "ui",
5682
+ "title": "Configure plan",
5683
+ "initialState": { "qty": 1, "premium": false },
5684
+ "root": {
5685
+ "kind": "box",
5686
+ "direction": "col",
5687
+ "gap": 3,
5688
+ "children": [
5689
+ { "kind": "heading", "value": "Choose quantity", "level": 3 },
5690
+ {
5691
+ "kind": "tooltip",
5692
+ "content": "Drag to adjust quantity",
5693
+ "child": {
5694
+ "kind": "slider",
5695
+ "binding": "qty",
5696
+ "min": 1,
5697
+ "max": 50,
5698
+ "label": "Quantity",
5699
+ "onChange": { "kind": "emit", "name": "qty-changed" }
5700
+ }
5701
+ },
5702
+ { "kind": "toggle", "binding": "premium", "label": "Premium support" },
5703
+ {
5704
+ "kind": "button",
5705
+ "label": "Confirm",
5706
+ "onClick": { "kind": "message", "text": { "$bind": "qty" } }
5707
+ }
5708
+ ]
5709
+ }
5710
+ }
5711
+ \`\`\`
5712
+
5713
+ ### Rules
5714
+
5715
+ - Always set \`type\` to a built-in value above unless the app documented a custom type.
5716
+ - Prefer \`ui\` over \`html\` when actions must bubble to the host chat (\`message\`, \`emit\`).
5717
+ - Prefer \`question\` for simple A/B/C choices; use \`ui\` when you need sliders, toggles, drag, or multi-control layouts.
5718
+ - Keep \`data\` arrays reasonably small (charts/tables).
5719
+
5720
+ ### After calling an artifact tool (critical)
5721
+
5722
+ When you call a tool that returns an artifact (\`make_chart\`, \`ask_question\`, \`show_table\`, \`show_html\`, \`make_ui_demo\`, etc.):
5723
+
5724
+ 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.
5725
+ 2. **Do not** emit a matching \`\`\`timbal-artifact\`\`\` block for the same payload \u2014 pick **one** channel (tool result only).
5726
+ 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.
5727
+ 4. Treat the widget as visible to the user; refer to it as "above" / "the chart" / "the choices" \u2014 never reproduce its contents.
5728
+ `.trim();
5729
+
4387
5730
  // src/auth/guard.tsx
4388
- var import_lucide_react10 = require("lucide-react");
4389
- var import_jsx_runtime32 = require("react/jsx-runtime");
5731
+ var import_lucide_react15 = require("lucide-react");
5732
+ var import_jsx_runtime45 = require("react/jsx-runtime");
4390
5733
  var AuthGuard = ({
4391
5734
  children,
4392
5735
  requireAuth = false,
@@ -4397,7 +5740,7 @@ var AuthGuard = ({
4397
5740
  return children;
4398
5741
  }
4399
5742
  if (loading) {
4400
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react10.Loader2, { className: "w-8 h-8 animate-spin" }) });
5743
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_lucide_react15.Loader2, { className: "w-8 h-8 animate-spin" }) });
4401
5744
  }
4402
5745
  if (requireAuth && !isAuthenticated && !isEmbedded) {
4403
5746
  const returnTo = encodeURIComponent(
@@ -4408,11 +5751,13 @@ var AuthGuard = ({
4408
5751
  }
4409
5752
  return children;
4410
5753
  };
5754
+
5755
+ // src/index.ts
5756
+ var import_react45 = require("@assistant-ui/react");
4411
5757
  // Annotate the CommonJS export names for ESM import in node:
4412
5758
  0 && (module.exports = {
4413
5759
  ARTIFACT_AGENT_INSTRUCTIONS,
4414
5760
  ARTIFACT_FENCE_LANGUAGES,
4415
- ActionBarMorePrimitive,
4416
5761
  ActionBarPrimitive,
4417
5762
  ArtifactCard,
4418
5763
  ArtifactRegistryProvider,
@@ -4426,8 +5771,6 @@ var AuthGuard = ({
4426
5771
  Button,
4427
5772
  ChartArtifactView,
4428
5773
  Composer,
4429
- ComposerAddAttachment,
4430
- ComposerAttachments,
4431
5774
  ComposerPrimitive,
4432
5775
  DEFAULT_UPLOAD_ACCEPT,
4433
5776
  Dialog,
@@ -4437,44 +5780,27 @@ var AuthGuard = ({
4437
5780
  DialogPortal,
4438
5781
  DialogTitle,
4439
5782
  DialogTrigger,
4440
- ErrorPrimitive,
4441
5783
  HtmlArtifactView,
4442
5784
  JsonArtifactView,
4443
5785
  MarkdownText,
4444
- MessagePartPrimitive,
4445
5786
  MessagePrimitive,
5787
+ ModeToggle,
4446
5788
  QuestionArtifactView,
4447
- STUDIO_INSET_LEFT,
4448
- STUDIO_PILL_HEIGHT,
4449
- STUDIO_SIDEBAR_GAP,
4450
- STUDIO_SIDEBAR_WIDTH,
4451
- STUDIO_TOPBAR_GAP,
4452
- STUDIO_TOPBAR_HEIGHT,
4453
5789
  SessionProvider,
4454
5790
  Shimmer,
5791
+ StudioSidebar,
5792
+ StudioWelcome,
4455
5793
  Suggestions,
4456
- SyntaxHighlighter,
4457
- TIMBAL_V2_BORDER,
4458
- TIMBAL_V2_FILL,
4459
- TIMBAL_V2_LABEL,
4460
- TIMBAL_V2_PILL_SURFACE,
4461
- TIMBAL_V2_SECONDARY_CHROME,
4462
- TIMBAL_V2_SHADOW,
4463
- TIMBAL_V2_SIZE_HEIGHT,
4464
- TIMBAL_V2_SIZE_ICON,
4465
- TIMBAL_V2_SIZE_LABEL_PX,
4466
5794
  TableArtifactView,
4467
5795
  Thread,
4468
5796
  ThreadPrimitive,
4469
5797
  TimbalChat,
4470
5798
  TimbalChatShell,
5799
+ TimbalMark,
4471
5800
  TimbalRuntimeProvider,
4472
- TimbalV2Button,
5801
+ TimbalStudioShell,
4473
5802
  ToolArtifactFallback,
4474
- ToolBodyPresence,
4475
5803
  ToolFallback,
4476
- ToolMotion,
4477
- ToolPresence,
4478
5804
  Tooltip,
4479
5805
  TooltipContent,
4480
5806
  TooltipIconButton,
@@ -4484,10 +5810,8 @@ var AuthGuard = ({
4484
5810
  UiCustomNodeRegistryProvider,
4485
5811
  UiEventProvider,
4486
5812
  UiNodeView,
4487
- UserMessageAttachments,
4488
5813
  WorkforceSelector,
4489
5814
  authFetch,
4490
- buttonVariants,
4491
5815
  clearTokens,
4492
5816
  cn,
4493
5817
  createDefaultAttachmentAdapter,
@@ -4501,7 +5825,6 @@ var AuthGuard = ({
4501
5825
  isArtifact,
4502
5826
  isArtifactFenceLanguage,
4503
5827
  isUiBinding,
4504
- luxuryEase,
4505
5828
  parseArtifactFromToolResult,
4506
5829
  parseSSELine,
4507
5830
  refreshAccessToken,
@@ -4511,35 +5834,10 @@ var AuthGuard = ({
4511
5834
  setPath,
4512
5835
  setRefreshToken,
4513
5836
  splitMarkdownByArtifacts,
4514
- studioArtifactShellClass,
4515
- studioChromeShellStyle,
4516
- studioComposeInputShellClass,
4517
- studioComposerIoWellClass,
4518
- studioIntegrationBorder,
4519
- studioIntegrationCardClass,
4520
- studioIntegrationIconTileClass,
4521
- studioIntegrationSurfaceSolid,
4522
- studioListRowButtonClass,
4523
- studioPillSurfaceClass,
4524
- studioPlaygroundGradientClass,
4525
- studioQuestionOptionClass,
4526
- studioQuestionOptionSelectedClass,
4527
- studioSecondaryChromeClass,
4528
- studioTimelineActionClass,
4529
- studioTimelineBodyPadClass,
4530
- studioTimelineChevronClass,
4531
- studioTimelineDetailClass,
4532
- studioTimelineRowButtonClass,
4533
- studioTimelineShimmerActionClass,
4534
- studioTimelineTextClass,
4535
- studioToolCardShellClass,
4536
- studioTopbarIconPillClass,
4537
- studioTopbarPillHeightClass,
4538
- toolPresenceTransition,
4539
5837
  useArtifactRegistry,
4540
- useAuiState,
4541
5838
  useComposerRuntime,
4542
5839
  useMessageRuntime,
5840
+ useOptionalSession,
4543
5841
  useResolvedSuggestions,
4544
5842
  useSession,
4545
5843
  useThread,