@timbal-ai/timbal-react 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -867,6 +867,30 @@ function findParentIdFromAuiParent(messages, auiParentId) {
867
867
  // src/index.ts
868
868
  import { parseSSELine as parseSSELine2 } from "@timbal-ai/timbal-sdk";
869
869
 
870
+ // src/components/thread.tsx
871
+ import { useEffect as useEffect5 } from "react";
872
+ import {
873
+ ActionBarMorePrimitive,
874
+ ActionBarPrimitive,
875
+ AuiIf as AuiIf2,
876
+ ComposerPrimitive as ComposerPrimitive3,
877
+ ErrorPrimitive,
878
+ MessagePartPrimitive,
879
+ MessagePrimitive as MessagePrimitive2,
880
+ ThreadPrimitive,
881
+ useThread
882
+ } from "@assistant-ui/react";
883
+ import {
884
+ ArrowDownIcon,
885
+ CheckIcon as CheckIcon3,
886
+ CopyIcon as CopyIcon2,
887
+ DownloadIcon,
888
+ MoreHorizontalIcon,
889
+ PencilIcon,
890
+ RefreshCwIcon
891
+ } from "lucide-react";
892
+ import { motion as motion4 } from "motion/react";
893
+
870
894
  // src/components/attachment.tsx
871
895
  import { useEffect as useEffect2, useState as useState2 } from "react";
872
896
  import { XIcon as XIcon2, PlusIcon, FileText } from "lucide-react";
@@ -1079,87 +1103,170 @@ function AvatarFallback({
1079
1103
  }
1080
1104
 
1081
1105
  // src/components/tooltip-icon-button.tsx
1082
- import { forwardRef } from "react";
1083
- import { Slottable } from "@radix-ui/react-slot";
1106
+ import { forwardRef as forwardRef2 } from "react";
1084
1107
 
1085
- // src/ui/button.tsx
1086
- import { cva } from "class-variance-authority";
1108
+ // src/ui/timbal-v2-button.tsx
1109
+ import * as React from "react";
1087
1110
  import { Slot } from "radix-ui";
1088
- import { jsx as jsx5 } from "react/jsx-runtime";
1089
- var buttonVariants = cva(
1090
- "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",
1091
- {
1092
- variants: {
1093
- variant: {
1094
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
1095
- destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
1096
- 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",
1097
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
1098
- ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
1099
- link: "text-primary underline-offset-4 hover:underline"
1100
- },
1101
- size: {
1102
- default: "h-9 px-4 py-2 has-[>svg]:px-3",
1103
- xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
1104
- sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
1105
- lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
1106
- icon: "size-9",
1107
- "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
1108
- "icon-sm": "size-8",
1109
- "icon-lg": "size-10"
1110
- }
1111
- },
1112
- defaultVariants: {
1113
- variant: "default",
1114
- size: "default"
1115
- }
1116
- }
1117
- );
1118
- function Button({
1119
- className,
1120
- variant = "default",
1121
- size = "default",
1111
+
1112
+ // src/design/button-tokens.ts
1113
+ var TIMBAL_V2_SIZE_HEIGHT = {
1114
+ xs: "min-h-8 h-8",
1115
+ sm: "min-h-9 h-9",
1116
+ md: "min-h-10 h-10",
1117
+ lg: "min-h-11 h-11"
1118
+ };
1119
+ var TIMBAL_V2_SIZE_ICON = {
1120
+ xs: "min-h-8 min-w-8 size-8",
1121
+ sm: "min-h-8 min-w-8 size-8",
1122
+ md: "min-h-10 min-w-10 size-10",
1123
+ lg: "min-h-11 min-w-11 size-11"
1124
+ };
1125
+ var TIMBAL_V2_SIZE_LABEL_PX = {
1126
+ xs: "px-3",
1127
+ sm: "px-4",
1128
+ md: "px-5",
1129
+ lg: "px-6"
1130
+ };
1131
+ var TIMBAL_V2_FILL = {
1132
+ primary: [
1133
+ "bg-gradient-to-b from-primary-fill-from to-primary-fill-to",
1134
+ "group-hover/tbv2:from-primary-fill-hover-from group-hover/tbv2:to-primary-fill-hover-to",
1135
+ "group-active/tbv2:from-primary-fill-active-from group-active/tbv2:to-primary-fill-active-to"
1136
+ ].join(" "),
1137
+ informative: [
1138
+ "bg-primary",
1139
+ "group-active/tbv2:[background-image:linear-gradient(to_top,rgba(0,0,0,0.08),transparent_55%)]"
1140
+ ].join(" "),
1141
+ destructive: [
1142
+ "bg-gradient-to-b from-elevated-from to-elevated-to",
1143
+ "group-hover/tbv2:from-destructive-fill-hover-from group-hover/tbv2:to-destructive-fill-hover-to",
1144
+ "group-active/tbv2:from-destructive-fill-active-from group-active/tbv2:to-destructive-fill-active-to"
1145
+ ].join(" "),
1146
+ secondary: [
1147
+ "bg-gradient-to-b from-elevated-from to-elevated-to",
1148
+ "group-hover/tbv2:from-secondary-fill-hover-from group-hover/tbv2:to-secondary-fill-hover-to",
1149
+ "group-active/tbv2:from-secondary-fill-active-from group-active/tbv2:to-secondary-fill-active-to"
1150
+ ].join(" "),
1151
+ ghost: [
1152
+ "bg-transparent",
1153
+ "group-hover/tbv2:bg-ghost-fill-hover",
1154
+ "group-active/tbv2:bg-ghost-fill-active"
1155
+ ].join(" "),
1156
+ link: "bg-transparent"
1157
+ };
1158
+ var TIMBAL_V2_LABEL = {
1159
+ primary: "text-primary-foreground",
1160
+ informative: "text-primary-foreground",
1161
+ destructive: "text-destructive",
1162
+ secondary: "text-foreground",
1163
+ ghost: "text-foreground",
1164
+ link: "text-foreground underline decoration-foreground/25 underline-offset-2 group-hover/tbv2:decoration-foreground/45"
1165
+ };
1166
+ var TIMBAL_V2_BORDER = {
1167
+ primary: "",
1168
+ informative: "border border-foreground/15",
1169
+ destructive: "border border-destructive/45",
1170
+ secondary: "border border-border",
1171
+ ghost: "",
1172
+ link: ""
1173
+ };
1174
+ var TIMBAL_V2_SHADOW = {
1175
+ primary: "shadow-card",
1176
+ informative: "shadow-card",
1177
+ destructive: "shadow-card",
1178
+ secondary: "shadow-card",
1179
+ ghost: "",
1180
+ link: ""
1181
+ };
1182
+ var TIMBAL_V2_SECONDARY_CHROME = [
1183
+ "bg-gradient-to-b from-elevated-from to-elevated-to border border-border shadow-card",
1184
+ "transition-[background-color,box-shadow,border-color] duration-200 ease-in-out",
1185
+ "hover:from-secondary-fill-hover-from hover:to-secondary-fill-hover-to",
1186
+ "active:from-secondary-fill-active-from active:to-secondary-fill-active-to"
1187
+ ].join(" ");
1188
+
1189
+ // src/ui/timbal-v2-button.tsx
1190
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1191
+ var TimbalV2Button = React.forwardRef(function TimbalV2Button2({
1192
+ variant = "secondary",
1193
+ size = "sm",
1194
+ isIconOnly = false,
1195
+ isLoading = false,
1196
+ fullWidth = false,
1122
1197
  asChild = false,
1198
+ className,
1199
+ disabled,
1200
+ type = "button",
1201
+ children,
1123
1202
  ...props
1124
- }) {
1203
+ }, ref) {
1204
+ const isDisabled = disabled || isLoading;
1125
1205
  const Comp = asChild ? Slot.Root : "button";
1126
- return /* @__PURE__ */ jsx5(
1206
+ const sizeClass = isIconOnly ? TIMBAL_V2_SIZE_ICON[size] : TIMBAL_V2_SIZE_HEIGHT[size];
1207
+ const radiusClass = variant === "link" || variant === "ghost" ? "rounded-md" : "rounded-full";
1208
+ return /* @__PURE__ */ jsxs3(
1127
1209
  Comp,
1128
1210
  {
1129
- "data-slot": "button",
1211
+ ref,
1212
+ type: asChild ? void 0 : type,
1213
+ disabled: asChild ? void 0 : isDisabled,
1214
+ "aria-disabled": asChild && isDisabled ? true : void 0,
1215
+ "data-slot": "timbal-v2-button",
1130
1216
  "data-variant": variant,
1131
- "data-size": size,
1132
- className: cn(buttonVariants({ variant, size, className })),
1133
- ...props
1217
+ className: cn(
1218
+ "group/tbv2 relative box-border inline-flex flex-col items-stretch overflow-hidden border-0 bg-transparent p-0 text-sm font-normal shadow-none transition duration-200 ease-in-out",
1219
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/60 focus-visible:ring-offset-1 focus-visible:ring-offset-background",
1220
+ sizeClass,
1221
+ radiusClass,
1222
+ TIMBAL_V2_BORDER[variant],
1223
+ TIMBAL_V2_SHADOW[variant],
1224
+ fullWidth && "w-full",
1225
+ isDisabled && "pointer-events-none opacity-50",
1226
+ className
1227
+ ),
1228
+ ...props,
1229
+ children: [
1230
+ /* @__PURE__ */ jsx5(
1231
+ "span",
1232
+ {
1233
+ "aria-hidden": true,
1234
+ className: cn(
1235
+ "pointer-events-none absolute inset-0 transition duration-200 ease-in-out",
1236
+ TIMBAL_V2_FILL[variant]
1237
+ )
1238
+ }
1239
+ ),
1240
+ /* @__PURE__ */ jsx5(
1241
+ "span",
1242
+ {
1243
+ className: cn(
1244
+ "relative z-10 flex min-h-0 flex-1 items-center justify-center gap-1 leading-tight",
1245
+ !isIconOnly && TIMBAL_V2_SIZE_LABEL_PX[size],
1246
+ TIMBAL_V2_LABEL[variant]
1247
+ ),
1248
+ children: isLoading ? /* @__PURE__ */ jsx5("span", { className: "size-4 animate-spin rounded-full border-2 border-current border-t-transparent" }) : children
1249
+ }
1250
+ )
1251
+ ]
1134
1252
  }
1135
1253
  );
1136
- }
1254
+ });
1137
1255
 
1138
1256
  // src/components/tooltip-icon-button.tsx
1139
- import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
1140
- var TooltipIconButton = forwardRef(({ children, tooltip, side = "bottom", className, ...rest }, ref) => {
1141
- return /* @__PURE__ */ jsxs3(Tooltip, { children: [
1142
- /* @__PURE__ */ jsx6(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs3(
1143
- Button,
1144
- {
1145
- variant: "ghost",
1146
- size: "icon",
1147
- ...rest,
1148
- className: cn("aui-button-icon size-6 p-1", className),
1149
- ref,
1150
- children: [
1151
- /* @__PURE__ */ jsx6(Slottable, { children }),
1152
- /* @__PURE__ */ jsx6("span", { className: "aui-sr-only sr-only", children: tooltip })
1153
- ]
1154
- }
1155
- ) }),
1257
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1258
+ var TooltipIconButton = forwardRef2(function TooltipIconButton2({ tooltip, side = "bottom", variant = "secondary", children, ...props }, ref) {
1259
+ return /* @__PURE__ */ jsxs4(Tooltip, { children: [
1260
+ /* @__PURE__ */ jsx6(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs4(TimbalV2Button, { ref, variant, size: "sm", isIconOnly: true, ...props, children: [
1261
+ children,
1262
+ /* @__PURE__ */ jsx6("span", { className: "sr-only", children: tooltip })
1263
+ ] }) }),
1156
1264
  /* @__PURE__ */ jsx6(TooltipContent, { side, children: tooltip })
1157
1265
  ] });
1158
1266
  });
1159
- TooltipIconButton.displayName = "TooltipIconButton";
1160
1267
 
1161
1268
  // src/components/attachment.tsx
1162
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
1269
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1163
1270
  var useFileSrc = (file) => {
1164
1271
  const [src, setSrc] = useState2(void 0);
1165
1272
  useEffect2(() => {
@@ -1205,7 +1312,7 @@ var AttachmentPreview = ({ src }) => {
1205
1312
  var AttachmentPreviewDialog = ({ children }) => {
1206
1313
  const src = useAttachmentSrc();
1207
1314
  if (!src) return children;
1208
- return /* @__PURE__ */ jsxs4(Dialog, { children: [
1315
+ return /* @__PURE__ */ jsxs5(Dialog, { children: [
1209
1316
  /* @__PURE__ */ jsx7(
1210
1317
  DialogTrigger,
1211
1318
  {
@@ -1214,7 +1321,7 @@ var AttachmentPreviewDialog = ({ children }) => {
1214
1321
  children
1215
1322
  }
1216
1323
  ),
1217
- /* @__PURE__ */ jsxs4(DialogContent, { className: "aui-attachment-preview-dialog-content p-2 sm:max-w-3xl [&>button]:rounded-full [&>button]:bg-foreground/60 [&>button]:p-1 [&>button]:opacity-100 [&>button]:ring-0! [&_svg]:text-background [&>button]:hover:[&_svg]:text-destructive", children: [
1324
+ /* @__PURE__ */ jsxs5(DialogContent, { className: "aui-attachment-preview-dialog-content p-2 sm:max-w-3xl [&>button]:rounded-full [&>button]:bg-foreground/60 [&>button]:p-1 [&>button]:opacity-100 [&>button]:ring-0! [&_svg]:text-background [&>button]:hover:[&_svg]:text-destructive", children: [
1218
1325
  /* @__PURE__ */ jsx7(DialogTitle, { className: "aui-sr-only sr-only", children: "Image Attachment Preview" }),
1219
1326
  /* @__PURE__ */ jsx7("div", { className: "aui-attachment-preview relative mx-auto flex max-h-[80dvh] w-full items-center justify-center overflow-hidden bg-background", children: /* @__PURE__ */ jsx7(AttachmentPreview, { src }) })
1220
1327
  ] })
@@ -1223,7 +1330,7 @@ var AttachmentPreviewDialog = ({ children }) => {
1223
1330
  var AttachmentThumb = () => {
1224
1331
  const isImage = useAuiState((s) => s.attachment.type === "image");
1225
1332
  const src = useAttachmentSrc();
1226
- return /* @__PURE__ */ jsxs4(Avatar, { className: "aui-attachment-tile-avatar h-full w-full rounded-none", children: [
1333
+ return /* @__PURE__ */ jsxs5(Avatar, { className: "aui-attachment-tile-avatar h-full w-full rounded-none", children: [
1227
1334
  /* @__PURE__ */ jsx7(
1228
1335
  AvatarImage,
1229
1336
  {
@@ -1252,8 +1359,8 @@ var AttachmentUI = () => {
1252
1359
  throw new Error(`Unknown attachment type: ${type}`);
1253
1360
  }
1254
1361
  });
1255
- return /* @__PURE__ */ jsxs4(Tooltip, { children: [
1256
- /* @__PURE__ */ jsxs4(
1362
+ return /* @__PURE__ */ jsxs5(Tooltip, { children: [
1363
+ /* @__PURE__ */ jsxs5(
1257
1364
  AttachmentPrimitive.Root,
1258
1365
  {
1259
1366
  className: cn(
@@ -1286,9 +1393,9 @@ var AttachmentRemove = () => {
1286
1393
  TooltipIconButton,
1287
1394
  {
1288
1395
  tooltip: "Remove file",
1289
- className: "aui-attachment-tile-remove absolute top-1.5 right-1.5 size-3.5 rounded-full bg-white text-muted-foreground opacity-100 shadow-sm hover:bg-white! [&_svg]:text-black hover:[&_svg]:text-destructive",
1396
+ className: "aui-attachment-tile-remove absolute top-1.5 right-1.5 size-3.5 rounded-full bg-card text-foreground opacity-100 shadow-card hover:bg-card! [&_svg]:text-foreground hover:[&_svg]:text-destructive",
1290
1397
  side: "top",
1291
- children: /* @__PURE__ */ jsx7(XIcon2, { className: "aui-attachment-remove-icon size-3 dark:stroke-[2.5px]" })
1398
+ children: /* @__PURE__ */ jsx7(XIcon2, { className: "aui-attachment-remove-icon size-3" })
1292
1399
  }
1293
1400
  ) });
1294
1401
  };
@@ -1309,11 +1416,10 @@ var ComposerAddAttachment = () => {
1309
1416
  {
1310
1417
  tooltip: "Add Attachment",
1311
1418
  side: "bottom",
1312
- variant: "ghost",
1313
- size: "icon",
1314
- className: "aui-composer-add-attachment size-8.5 rounded-full p-1 font-semibold text-xs hover:bg-muted-foreground/15 dark:border-muted-foreground/15 dark:hover:bg-muted-foreground/30",
1419
+ variant: "secondary",
1420
+ className: "aui-composer-add-attachment shrink-0 text-muted-foreground",
1315
1421
  "aria-label": "Add Attachment",
1316
- children: /* @__PURE__ */ jsx7(PlusIcon, { className: "aui-attachment-add-icon size-5 stroke-[1.5px]" })
1422
+ children: /* @__PURE__ */ jsx7(PlusIcon, { className: "aui-attachment-add-icon size-4 stroke-[1.5]" })
1317
1423
  }
1318
1424
  ) });
1319
1425
  };
@@ -1344,10 +1450,10 @@ import { createContext as createContext3, useContext as useContext3, useMemo as
1344
1450
  import { useMemo as useMemo2 } from "react";
1345
1451
 
1346
1452
  // src/artifacts/artifact-card.tsx
1347
- import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1453
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1348
1454
  var ArtifactCard = ({ title, kind, className, bodyClassName, toolbar, children }) => {
1349
1455
  const hasHeader = Boolean(title || toolbar);
1350
- return /* @__PURE__ */ jsxs5(
1456
+ return /* @__PURE__ */ jsxs6(
1351
1457
  "div",
1352
1458
  {
1353
1459
  className: cn(
@@ -1356,7 +1462,7 @@ var ArtifactCard = ({ title, kind, className, bodyClassName, toolbar, children }
1356
1462
  ),
1357
1463
  "data-artifact-kind": kind,
1358
1464
  children: [
1359
- hasHeader && /* @__PURE__ */ jsxs5("div", { className: "aui-artifact-header flex items-center gap-2 border-b border-border/40 bg-muted/30 px-3 py-1.5", children: [
1465
+ hasHeader && /* @__PURE__ */ jsxs6("div", { className: "aui-artifact-header flex items-center gap-2 border-b border-border/40 bg-muted/30 px-3 py-1.5", children: [
1360
1466
  title && /* @__PURE__ */ jsx8("span", { className: "aui-artifact-title flex-1 truncate text-xs font-semibold text-foreground/80", children: title }),
1361
1467
  !title && /* @__PURE__ */ jsx8("span", { className: "flex-1" }),
1362
1468
  toolbar
@@ -1368,7 +1474,7 @@ var ArtifactCard = ({ title, kind, className, bodyClassName, toolbar, children }
1368
1474
  };
1369
1475
 
1370
1476
  // src/artifacts/chart-artifact.tsx
1371
- import { Fragment, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1477
+ import { Fragment, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
1372
1478
  var ChartArtifactView = ({
1373
1479
  artifact
1374
1480
  }) => {
@@ -1379,7 +1485,7 @@ var ChartArtifactView = ({
1379
1485
  if (typeof artifact.dataKey === "string") return [artifact.dataKey];
1380
1486
  return inferDataKeys(data, xKey);
1381
1487
  }, [artifact.dataKey, data, xKey]);
1382
- return /* @__PURE__ */ jsx9(ArtifactCard, { title: artifact.title, kind: "chart", children: /* @__PURE__ */ jsxs6("div", { className: "aui-artifact-chart p-3", children: [
1488
+ return /* @__PURE__ */ jsx9(ArtifactCard, { title: artifact.title, kind: "chart", children: /* @__PURE__ */ jsxs7("div", { className: "aui-artifact-chart p-3", children: [
1383
1489
  /* @__PURE__ */ jsx9(
1384
1490
  ChartSvg,
1385
1491
  {
@@ -1422,7 +1528,7 @@ var ChartSvg = ({ chartType, data, xKey, dataKeys, unit }) => {
1422
1528
  const xStep = xCount > 1 ? innerW / (xCount - 1) : innerW;
1423
1529
  const xPos = (i) => chartType === "bar" ? PAD.left + innerW * (i + 0.5) / xCount : PAD.left + i * xStep;
1424
1530
  const ticks = niceTicks(minV, maxV);
1425
- return /* @__PURE__ */ jsxs6(
1531
+ return /* @__PURE__ */ jsxs7(
1426
1532
  "svg",
1427
1533
  {
1428
1534
  viewBox: `0 0 ${W} ${H}`,
@@ -1430,7 +1536,7 @@ var ChartSvg = ({ chartType, data, xKey, dataKeys, unit }) => {
1430
1536
  role: "img",
1431
1537
  "aria-label": "Chart",
1432
1538
  children: [
1433
- ticks.map((t, i) => /* @__PURE__ */ jsxs6("g", { children: [
1539
+ ticks.map((t, i) => /* @__PURE__ */ jsxs7("g", { children: [
1434
1540
  /* @__PURE__ */ jsx9(
1435
1541
  "line",
1436
1542
  {
@@ -1538,7 +1644,7 @@ var Area = ({ data, dataKey, xPos, yScale, baseY, color }) => {
1538
1644
  if (data.length === 0) return null;
1539
1645
  const top = data.map((d, i) => `${xPos(i)},${yScale(toNum(d[dataKey]))}`).join(" ");
1540
1646
  const path = `M ${xPos(0)},${baseY} L ${top} L ${xPos(data.length - 1)},${baseY} Z`;
1541
- return /* @__PURE__ */ jsxs6(Fragment, { children: [
1647
+ return /* @__PURE__ */ jsxs7(Fragment, { children: [
1542
1648
  /* @__PURE__ */ jsx9("path", { d: path, fill: color, fillOpacity: 0.18 }),
1543
1649
  /* @__PURE__ */ jsx9(Polyline, { data, dataKey, xPos, yScale, color })
1544
1650
  ] });
@@ -1588,7 +1694,7 @@ var PieSlice = ({ cx, cy, r, start, end, color, label }) => {
1588
1694
  const mid = (start + end) / 2;
1589
1695
  const lx = cx + Math.sin(mid) * (r * 0.65);
1590
1696
  const ly = cy - Math.cos(mid) * (r * 0.65);
1591
- return /* @__PURE__ */ jsxs6("g", { children: [
1697
+ return /* @__PURE__ */ jsxs7("g", { children: [
1592
1698
  /* @__PURE__ */ jsx9("path", { d: path, fill: color, stroke: "var(--background, #fff)", strokeWidth: 1 }),
1593
1699
  /* @__PURE__ */ jsx9(
1594
1700
  "text",
@@ -1603,7 +1709,7 @@ var PieSlice = ({ cx, cy, r, start, end, color, label }) => {
1603
1709
  )
1604
1710
  ] });
1605
1711
  };
1606
- var Legend = ({ dataKeys }) => /* @__PURE__ */ jsx9("div", { className: "aui-artifact-chart-legend mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: dataKeys.map((k, i) => /* @__PURE__ */ jsxs6("span", { className: "inline-flex items-center gap-1.5", children: [
1712
+ var Legend = ({ dataKeys }) => /* @__PURE__ */ jsx9("div", { className: "aui-artifact-chart-legend mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: dataKeys.map((k, i) => /* @__PURE__ */ jsxs7("span", { className: "inline-flex items-center gap-1.5", children: [
1607
1713
  /* @__PURE__ */ jsx9(
1608
1714
  "span",
1609
1715
  {
@@ -1661,89 +1767,208 @@ function formatTick(v, unit) {
1661
1767
  }
1662
1768
 
1663
1769
  // src/artifacts/question-artifact.tsx
1664
- import { useState as useState3 } from "react";
1770
+ import { useCallback as useCallback2, useState as useState3 } from "react";
1665
1771
  import { useThreadRuntime } from "@assistant-ui/react";
1666
1772
  import { CheckIcon } from "lucide-react";
1667
- import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
1773
+
1774
+ // src/design/classes.ts
1775
+ var studioTopbarPillHeightClass = "h-[var(--studio-chrome-pill-height)] min-h-[var(--studio-chrome-pill-height)]";
1776
+ var studioTopbarIconPillClass = "shrink-0 flex-none size-[var(--studio-chrome-pill-height)] min-h-[var(--studio-chrome-pill-height)] min-w-[var(--studio-chrome-pill-height)]";
1777
+ var studioPlaygroundGradientClass = "bg-gradient-to-b from-playground-from via-playground-via to-playground-to";
1778
+ var studioComposeInputShellClass = cn(
1779
+ "flex w-full flex-col rounded-2xl bg-composer-bg shadow-card-elevated outline-none",
1780
+ "border border-composer-border",
1781
+ "transition-[box-shadow,border-color]",
1782
+ "focus-within:border-composer-border-focus focus-within:ring-2 focus-within:ring-foreground/5"
1783
+ );
1784
+ var studioSecondaryChromeClass = TIMBAL_V2_SECONDARY_CHROME;
1785
+ var studioIntegrationSurfaceSolid = "bg-gradient-to-b from-elevated-from to-elevated-to shadow-card";
1786
+ var studioIntegrationBorder = "border border-border";
1787
+ var studioIntegrationCardClass = cn(
1788
+ "rounded-xl",
1789
+ studioIntegrationSurfaceSolid,
1790
+ studioIntegrationBorder
1791
+ );
1792
+ var studioIntegrationIconTileClass = cn(
1793
+ "flex size-9 shrink-0 items-center justify-center rounded-lg",
1794
+ studioIntegrationSurfaceSolid,
1795
+ studioIntegrationBorder
1796
+ );
1797
+ var studioListRowButtonClass = cn(
1798
+ "flex w-full cursor-pointer items-center gap-3 rounded-xl px-3 py-2.5 text-left",
1799
+ studioIntegrationCardClass,
1800
+ "transition-[background-color,box-shadow,border-color] duration-200 ease-in-out",
1801
+ "hover:border-foreground/20",
1802
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15 focus-visible:ring-offset-2 focus-visible:ring-offset-background"
1803
+ );
1804
+ var studioComposerIoWellClass = cn(
1805
+ "rounded-lg",
1806
+ studioIntegrationSurfaceSolid,
1807
+ studioIntegrationBorder
1808
+ );
1809
+ var studioToolCardShellClass = cn(
1810
+ studioIntegrationCardClass,
1811
+ "my-2 min-h-0 overflow-hidden"
1812
+ );
1813
+ var studioSidebarPanelClass = cn(
1814
+ "bg-sidebar text-sidebar-foreground",
1815
+ "border border-sidebar-border",
1816
+ "shadow-card-elevated"
1817
+ );
1818
+ var studioSidebarNavItemClass = cn(
1819
+ "flex items-center rounded-lg text-sm",
1820
+ "transition-[color,background-color,box-shadow,border-color] duration-200 ease-in-out",
1821
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15 focus-visible:ring-offset-2"
1822
+ );
1823
+ function studioSidebarNavItemLayout(iconOnly) {
1824
+ return iconOnly ? "box-border size-8 min-h-8 min-w-8 shrink-0 justify-center rounded-lg p-0 focus-visible:ring-offset-0" : "w-full gap-2 px-2.5 py-2";
1825
+ }
1826
+ var studioSidebarNavItemSurfaceClass = cn(
1827
+ "bg-gradient-to-b from-elevated-from to-elevated-to text-foreground",
1828
+ "border border-border",
1829
+ "shadow-card"
1830
+ );
1831
+ var studioSidebarNavItemIdleClass = cn(
1832
+ "border border-transparent text-muted-foreground shadow-none",
1833
+ "hover:text-foreground",
1834
+ "hover:bg-gradient-to-b hover:from-elevated-from hover:to-elevated-to",
1835
+ "hover:border-border hover:shadow-card"
1836
+ );
1837
+ var studioSidebarCollapsedRailItemClass = cn(
1838
+ "border border-border shadow-card bg-sidebar-accent"
1839
+ );
1840
+ var studioSidebarCollapsedRailItemIdleClass = cn(
1841
+ studioSidebarCollapsedRailItemClass,
1842
+ "text-muted-foreground hover:text-foreground"
1843
+ );
1844
+ var studioSidebarCollapsedRailItemActiveClass = cn(
1845
+ studioSidebarCollapsedRailItemClass,
1846
+ studioSidebarNavItemSurfaceClass,
1847
+ "text-foreground"
1848
+ );
1849
+ var studioSidebarNavItemActiveClass = studioSidebarNavItemSurfaceClass;
1850
+ var studioTimelineRowButtonClass = "group flex w-full min-w-0 cursor-pointer items-center justify-start rounded-md border-0 bg-transparent py-1 text-left shadow-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15 focus-visible:ring-offset-2";
1851
+ var studioTimelineTextClass = "text-xs font-normal leading-snug";
1852
+ var studioTimelineActionClass = cn(
1853
+ studioTimelineTextClass,
1854
+ "shrink-0 text-foreground/70 transition-colors duration-150 group-hover:text-foreground/80"
1855
+ );
1856
+ var studioTimelineShimmerActionClass = cn(
1857
+ studioTimelineTextClass,
1858
+ "shrink-0"
1859
+ );
1860
+ var studioTimelineDetailClass = cn(
1861
+ studioTimelineTextClass,
1862
+ "min-w-0 truncate text-muted-foreground transition-colors duration-150"
1863
+ );
1864
+ function studioTimelineChevronClass(expanded) {
1865
+ return cn(
1866
+ "ml-0.5 size-3 min-h-3 min-w-3 shrink-0 transition-all duration-150",
1867
+ expanded ? "rotate-90 text-foreground opacity-60" : "text-muted-foreground opacity-0 group-hover:opacity-70"
1868
+ );
1869
+ }
1870
+ var studioTimelineBodyPadClass = "flex flex-col gap-2 pt-0.5 pb-0.5";
1871
+ var studioArtifactShellClass = cn(
1872
+ studioIntegrationCardClass,
1873
+ "my-2 w-full min-w-0 overflow-hidden"
1874
+ );
1875
+ var studioQuestionOptionClass = "flex w-full items-center gap-2 rounded-lg border border-transparent px-2 py-1.5 text-left text-sm transition-[background-color,border-color,box-shadow] duration-200 hover:bg-muted/70 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15 focus-visible:ring-offset-2";
1876
+ var studioQuestionOptionSelectedClass = cn(
1877
+ studioQuestionOptionClass,
1878
+ "border-border bg-accent ring-1 ring-foreground/10"
1879
+ );
1880
+
1881
+ // src/artifacts/question-artifact.tsx
1882
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
1883
+ function optionKey(option, index) {
1884
+ const id = option.id?.trim();
1885
+ return id ? id : `__option-${index}`;
1886
+ }
1887
+ var OptionRadio = ({ selected }) => /* @__PURE__ */ jsx10(
1888
+ "span",
1889
+ {
1890
+ className: cn(
1891
+ "flex size-4 shrink-0 items-center justify-center rounded-full border-2 transition-colors",
1892
+ selected ? "border-foreground bg-foreground text-background" : "border-border bg-background"
1893
+ ),
1894
+ "aria-hidden": true,
1895
+ children: selected ? /* @__PURE__ */ jsx10(CheckIcon, { className: "size-2.5 stroke-[3]" }) : null
1896
+ }
1897
+ );
1668
1898
  var QuestionArtifactView = ({
1669
1899
  artifact
1670
1900
  }) => {
1671
1901
  const runtime = useThreadRuntime();
1672
1902
  const [selected, setSelected] = useState3([]);
1673
- const [submitted, setSubmitted] = useState3(null);
1903
+ const [submittedIds, setSubmittedIds] = useState3(null);
1674
1904
  const isMulti = artifact.multi === true;
1675
- const send = (labels) => {
1676
- if (labels.length === 0) return;
1677
- const text = labels.join(", ");
1678
- setSubmitted(text);
1679
- runtime.append({ role: "user", content: [{ type: "text", text }] });
1680
- };
1681
- const onPick = (option) => {
1682
- if (submitted) return;
1683
- if (!isMulti) {
1684
- send([option.label]);
1685
- return;
1686
- }
1687
- setSelected(
1688
- (prev) => prev.includes(option.id) ? prev.filter((id) => id !== option.id) : [...prev, option.id]
1689
- );
1690
- };
1691
- const onConfirm = () => {
1692
- const labels = artifact.options.filter((o) => selected.includes(o.id)).map((o) => o.label);
1693
- send(labels);
1694
- };
1695
- return /* @__PURE__ */ jsx10(ArtifactCard, { kind: "question", children: /* @__PURE__ */ jsxs7("div", { className: "aui-artifact-question p-3", children: [
1696
- artifact.prompt && /* @__PURE__ */ jsx10("p", { className: "aui-artifact-question-prompt mb-2 text-sm text-foreground/85", children: artifact.prompt }),
1697
- /* @__PURE__ */ jsx10("div", { className: "aui-artifact-question-options flex flex-col gap-1.5", children: artifact.options.map((option) => {
1698
- const isSelected = isMulti && selected.includes(option.id);
1699
- const isDisabled = Boolean(submitted);
1700
- return /* @__PURE__ */ jsxs7(
1905
+ const isDisabled = submittedIds !== null;
1906
+ const send = useCallback2(
1907
+ (keys) => {
1908
+ if (keys.length === 0) return;
1909
+ const labels = artifact.options.map((option, index) => ({ option, key: optionKey(option, index) })).filter(({ key }) => keys.includes(key)).map(({ option }) => option.label);
1910
+ setSubmittedIds(keys);
1911
+ runtime.append({
1912
+ role: "user",
1913
+ content: [{ type: "text", text: labels.join(", ") }]
1914
+ });
1915
+ },
1916
+ [artifact.options, runtime]
1917
+ );
1918
+ const onPick = useCallback2(
1919
+ (key) => {
1920
+ if (isDisabled) return;
1921
+ if (!isMulti) {
1922
+ send([key]);
1923
+ return;
1924
+ }
1925
+ setSelected(
1926
+ (prev) => prev.includes(key) ? prev.filter((id) => id !== key) : [...prev, key]
1927
+ );
1928
+ },
1929
+ [isDisabled, isMulti, send]
1930
+ );
1931
+ const onConfirm = useCallback2(() => {
1932
+ send(selected);
1933
+ }, [selected, send]);
1934
+ return /* @__PURE__ */ jsx10("div", { className: studioArtifactShellClass, "data-artifact-kind": "question", children: /* @__PURE__ */ jsxs8("div", { className: "px-2.5 py-2", children: [
1935
+ artifact.prompt ? /* @__PURE__ */ jsx10("p", { className: "mb-2 text-sm font-normal leading-snug text-foreground", children: artifact.prompt }) : null,
1936
+ /* @__PURE__ */ jsx10("div", { className: "flex flex-col gap-0.5", role: "list", children: artifact.options.map((option, index) => {
1937
+ const key = optionKey(option, index);
1938
+ const isSelected = submittedIds ? submittedIds.includes(key) : isMulti && selected.includes(key);
1939
+ return /* @__PURE__ */ jsxs8(
1701
1940
  "button",
1702
1941
  {
1703
1942
  type: "button",
1943
+ role: "listitem",
1704
1944
  disabled: isDisabled,
1705
- onClick: () => onPick(option),
1945
+ onClick: () => onPick(key),
1706
1946
  className: cn(
1707
- "aui-artifact-question-option flex items-center gap-2 rounded-lg border px-3 py-2 text-left text-sm transition-colors",
1708
- "border-border/60 hover:border-primary/40 hover:bg-muted/40",
1709
- isSelected && "border-primary/60 bg-primary/5",
1710
- isDisabled && "cursor-not-allowed opacity-60 hover:border-border/60 hover:bg-transparent"
1947
+ isSelected ? studioQuestionOptionSelectedClass : studioQuestionOptionClass,
1948
+ isDisabled && (isSelected ? "cursor-default" : "cursor-not-allowed opacity-50")
1711
1949
  ),
1712
1950
  children: [
1713
- /* @__PURE__ */ jsx10(
1714
- "span",
1715
- {
1716
- className: cn(
1717
- "flex size-4 shrink-0 items-center justify-center rounded-full border",
1718
- isSelected ? "border-primary bg-primary text-primary-foreground" : "border-border"
1719
- ),
1720
- "aria-hidden": true,
1721
- children: isSelected && /* @__PURE__ */ jsx10(CheckIcon, { className: "size-3" })
1722
- }
1723
- ),
1724
- /* @__PURE__ */ jsxs7("div", { className: "aui-artifact-question-option-text flex-1", children: [
1725
- /* @__PURE__ */ jsx10("div", { className: "font-medium text-foreground/90", children: option.label }),
1726
- option.description && /* @__PURE__ */ jsx10("div", { className: "text-xs text-muted-foreground", children: option.description })
1951
+ /* @__PURE__ */ jsx10(OptionRadio, { selected: isSelected }),
1952
+ /* @__PURE__ */ jsxs8("span", { className: "min-w-0 flex-1 text-left", children: [
1953
+ /* @__PURE__ */ jsx10("span", { className: "block font-normal text-foreground", children: option.label }),
1954
+ option.description ? /* @__PURE__ */ jsx10("span", { className: "mt-0.5 block text-xs text-muted-foreground", children: option.description }) : null
1727
1955
  ] })
1728
1956
  ]
1729
1957
  },
1730
- option.id
1958
+ key
1731
1959
  );
1732
1960
  }) }),
1733
- isMulti && !submitted && /* @__PURE__ */ jsx10("div", { className: "mt-2 flex justify-end", children: /* @__PURE__ */ jsx10(
1734
- Button,
1961
+ isMulti && !submittedIds ? /* @__PURE__ */ jsx10("div", { className: "mt-2 flex justify-end", children: /* @__PURE__ */ jsx10(
1962
+ TimbalV2Button,
1735
1963
  {
1736
1964
  type: "button",
1965
+ variant: "primary",
1737
1966
  size: "sm",
1738
1967
  disabled: selected.length === 0,
1739
1968
  onClick: onConfirm,
1740
1969
  children: "Confirm"
1741
1970
  }
1742
- ) }),
1743
- submitted && /* @__PURE__ */ jsxs7("p", { className: "aui-artifact-question-submitted mt-2 text-xs text-muted-foreground", children: [
1744
- "Sent: ",
1745
- submitted
1746
- ] })
1971
+ ) }) : null
1747
1972
  ] }) });
1748
1973
  };
1749
1974
 
@@ -1782,11 +2007,11 @@ var JsonArtifactView = ({
1782
2007
  };
1783
2008
 
1784
2009
  // src/artifacts/table-artifact.tsx
1785
- import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
2010
+ import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
1786
2011
  var TableArtifactView = ({ artifact }) => {
1787
2012
  const rows = artifact.rows ?? [];
1788
2013
  const columns = artifact.columns ?? deriveColumns(rows);
1789
- return /* @__PURE__ */ jsx13(ArtifactCard, { title: artifact.title, kind: "table", children: /* @__PURE__ */ jsx13("div", { className: "aui-artifact-table-wrap overflow-x-auto", children: /* @__PURE__ */ jsxs8("table", { className: "aui-artifact-table w-full border-collapse text-sm", children: [
2014
+ return /* @__PURE__ */ jsx13(ArtifactCard, { title: artifact.title, kind: "table", children: /* @__PURE__ */ jsx13("div", { className: "aui-artifact-table-wrap overflow-x-auto", children: /* @__PURE__ */ jsxs9("table", { className: "aui-artifact-table w-full border-collapse text-sm", children: [
1790
2015
  /* @__PURE__ */ jsx13("thead", { children: /* @__PURE__ */ jsx13("tr", { className: "border-b border-border/40 bg-muted/20", children: columns.map((col) => /* @__PURE__ */ jsx13(
1791
2016
  "th",
1792
2017
  {
@@ -1914,32 +2139,87 @@ function useUiCustomNodeRegistry() {
1914
2139
  }
1915
2140
 
1916
2141
  // src/artifacts/ui/nodes.tsx
1917
- import { useCallback as useCallback2 } from "react";
2142
+ import { useCallback as useCallback3 } from "react";
1918
2143
  import { motion } from "motion/react";
1919
2144
  import { useThreadRuntime as useThreadRuntime2 } from "@assistant-ui/react";
1920
- import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
2145
+
2146
+ // src/ui/button.tsx
2147
+ import { cva } from "class-variance-authority";
2148
+ import { Slot as Slot2 } from "radix-ui";
2149
+ import { jsx as jsx15 } from "react/jsx-runtime";
2150
+ var buttonVariants = cva(
2151
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/30 aria-invalid:border-destructive",
2152
+ {
2153
+ variants: {
2154
+ variant: {
2155
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
2156
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90 focus-visible:ring-destructive/30",
2157
+ outline: "border border-border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
2158
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
2159
+ ghost: "hover:bg-accent hover:text-accent-foreground",
2160
+ link: "text-primary underline-offset-4 hover:underline"
2161
+ },
2162
+ size: {
2163
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
2164
+ xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
2165
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
2166
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
2167
+ icon: "size-9",
2168
+ "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
2169
+ "icon-sm": "size-8",
2170
+ "icon-lg": "size-10"
2171
+ }
2172
+ },
2173
+ defaultVariants: {
2174
+ variant: "default",
2175
+ size: "default"
2176
+ }
2177
+ }
2178
+ );
2179
+ function Button({
2180
+ className,
2181
+ variant = "default",
2182
+ size = "default",
2183
+ asChild = false,
2184
+ ...props
2185
+ }) {
2186
+ const Comp = asChild ? Slot2.Root : "button";
2187
+ return /* @__PURE__ */ jsx15(
2188
+ Comp,
2189
+ {
2190
+ "data-slot": "button",
2191
+ "data-variant": variant,
2192
+ "data-size": size,
2193
+ className: cn(buttonVariants({ variant, size, className })),
2194
+ ...props
2195
+ }
2196
+ );
2197
+ }
2198
+
2199
+ // src/artifacts/ui/nodes.tsx
2200
+ import { jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
1921
2201
  var UiNodeView = ({ node }) => {
1922
2202
  switch (node.kind) {
1923
2203
  case "box":
1924
- return /* @__PURE__ */ jsx15(BoxNode, { node });
2204
+ return /* @__PURE__ */ jsx16(BoxNode, { node });
1925
2205
  case "text":
1926
- return /* @__PURE__ */ jsx15(TextNode, { node });
2206
+ return /* @__PURE__ */ jsx16(TextNode, { node });
1927
2207
  case "heading":
1928
- return /* @__PURE__ */ jsx15(HeadingNode, { node });
2208
+ return /* @__PURE__ */ jsx16(HeadingNode, { node });
1929
2209
  case "badge":
1930
- return /* @__PURE__ */ jsx15(BadgeNode, { node });
2210
+ return /* @__PURE__ */ jsx16(BadgeNode, { node });
1931
2211
  case "button":
1932
- return /* @__PURE__ */ jsx15(ButtonNode, { node });
2212
+ return /* @__PURE__ */ jsx16(ButtonNode, { node });
1933
2213
  case "toggle":
1934
- return /* @__PURE__ */ jsx15(ToggleNode, { node });
2214
+ return /* @__PURE__ */ jsx16(ToggleNode, { node });
1935
2215
  case "slider":
1936
- return /* @__PURE__ */ jsx15(SliderNode, { node });
2216
+ return /* @__PURE__ */ jsx16(SliderNode, { node });
1937
2217
  case "tooltip":
1938
- return /* @__PURE__ */ jsx15(TooltipNode, { node });
2218
+ return /* @__PURE__ */ jsx16(TooltipNode, { node });
1939
2219
  case "draggable":
1940
- return /* @__PURE__ */ jsx15(DraggableNode, { node });
2220
+ return /* @__PURE__ */ jsx16(DraggableNode, { node });
1941
2221
  case "custom":
1942
- return /* @__PURE__ */ jsx15(CustomNode, { node });
2222
+ return /* @__PURE__ */ jsx16(CustomNode, { node });
1943
2223
  default:
1944
2224
  return null;
1945
2225
  }
@@ -1949,7 +2229,7 @@ function useActionRunner() {
1949
2229
  const dispatch = useUiDispatch();
1950
2230
  const runtime = useThreadRuntime2();
1951
2231
  const emit = useUiEventEmitter();
1952
- return useCallback2(
2232
+ return useCallback3(
1953
2233
  (actions) => {
1954
2234
  if (!actions) return;
1955
2235
  const list = Array.isArray(actions) ? actions : [actions];
@@ -1999,7 +2279,7 @@ var JUSTIFY_CLS = {
1999
2279
  };
2000
2280
  var BoxNode = ({ node }) => {
2001
2281
  const dir = node.direction ?? "col";
2002
- return /* @__PURE__ */ jsx15(
2282
+ return /* @__PURE__ */ jsx16(
2003
2283
  "div",
2004
2284
  {
2005
2285
  className: cn(
@@ -2014,7 +2294,7 @@ var BoxNode = ({ node }) => {
2014
2294
  gap: node.gap !== void 0 ? `${node.gap * 0.25}rem` : void 0,
2015
2295
  padding: node.padding !== void 0 ? `${node.padding * 0.25}rem` : void 0
2016
2296
  },
2017
- children: node.children?.map((child, i) => /* @__PURE__ */ jsx15(UiNodeView, { node: child }, child.id ?? i))
2297
+ children: node.children?.map((child, i) => /* @__PURE__ */ jsx16(UiNodeView, { node: child }, child.id ?? i))
2018
2298
  }
2019
2299
  );
2020
2300
  };
@@ -2033,7 +2313,7 @@ var TEXT_WEIGHT = {
2033
2313
  var TextNode = ({ node }) => {
2034
2314
  const state = useUiState();
2035
2315
  const value = resolveBindable(node.value, state);
2036
- return /* @__PURE__ */ jsx15(
2316
+ return /* @__PURE__ */ jsx16(
2037
2317
  "span",
2038
2318
  {
2039
2319
  className: cn(
@@ -2064,13 +2344,13 @@ var HeadingNode = ({ node }) => {
2064
2344
  );
2065
2345
  switch (level) {
2066
2346
  case 1:
2067
- return /* @__PURE__ */ jsx15("h1", { className: cls, children: value });
2347
+ return /* @__PURE__ */ jsx16("h1", { className: cls, children: value });
2068
2348
  case 2:
2069
- return /* @__PURE__ */ jsx15("h2", { className: cls, children: value });
2349
+ return /* @__PURE__ */ jsx16("h2", { className: cls, children: value });
2070
2350
  case 3:
2071
- return /* @__PURE__ */ jsx15("h3", { className: cls, children: value });
2351
+ return /* @__PURE__ */ jsx16("h3", { className: cls, children: value });
2072
2352
  case 4:
2073
- return /* @__PURE__ */ jsx15("h4", { className: cls, children: value });
2353
+ return /* @__PURE__ */ jsx16("h4", { className: cls, children: value });
2074
2354
  }
2075
2355
  };
2076
2356
  var BADGE_TONE = {
@@ -2083,7 +2363,7 @@ var BADGE_TONE = {
2083
2363
  var BadgeNode = ({ node }) => {
2084
2364
  const state = useUiState();
2085
2365
  const value = String(resolveBindable(node.value, state) ?? "");
2086
- return /* @__PURE__ */ jsx15(
2366
+ return /* @__PURE__ */ jsx16(
2087
2367
  "span",
2088
2368
  {
2089
2369
  className: cn(
@@ -2100,7 +2380,7 @@ var ButtonNode = ({ node }) => {
2100
2380
  const run = useActionRunner();
2101
2381
  const label = String(resolveBindable(node.label, state) ?? "");
2102
2382
  const disabled = node.disabled !== void 0 ? Boolean(resolveBindable(node.disabled, state)) : false;
2103
- return /* @__PURE__ */ jsx15(
2383
+ return /* @__PURE__ */ jsx16(
2104
2384
  Button,
2105
2385
  {
2106
2386
  variant: node.variant ?? "default",
@@ -2122,7 +2402,7 @@ var ToggleNode = ({ node }) => {
2122
2402
  dispatch({ type: "toggle", path: node.binding });
2123
2403
  run(node.onChange);
2124
2404
  };
2125
- return /* @__PURE__ */ jsxs9(
2405
+ return /* @__PURE__ */ jsxs10(
2126
2406
  "label",
2127
2407
  {
2128
2408
  className: cn(
@@ -2130,7 +2410,7 @@ var ToggleNode = ({ node }) => {
2130
2410
  node.className
2131
2411
  ),
2132
2412
  children: [
2133
- /* @__PURE__ */ jsx15(
2413
+ /* @__PURE__ */ jsx16(
2134
2414
  "button",
2135
2415
  {
2136
2416
  type: "button",
@@ -2141,7 +2421,7 @@ var ToggleNode = ({ node }) => {
2141
2421
  "relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border transition-colors",
2142
2422
  value ? "border-primary bg-primary" : "border-border bg-muted hover:bg-muted/80"
2143
2423
  ),
2144
- children: /* @__PURE__ */ jsx15(
2424
+ children: /* @__PURE__ */ jsx16(
2145
2425
  "span",
2146
2426
  {
2147
2427
  className: cn(
@@ -2153,7 +2433,7 @@ var ToggleNode = ({ node }) => {
2153
2433
  )
2154
2434
  }
2155
2435
  ),
2156
- label && /* @__PURE__ */ jsx15("span", { className: "text-foreground/85", children: label })
2436
+ label && /* @__PURE__ */ jsx16("span", { className: "text-foreground/85", children: label })
2157
2437
  ]
2158
2438
  }
2159
2439
  );
@@ -2173,12 +2453,12 @@ var SliderNode = ({ node }) => {
2173
2453
  const next = Number(e.target.value);
2174
2454
  dispatch({ type: "set", path: node.binding, value: next });
2175
2455
  };
2176
- return /* @__PURE__ */ jsxs9("div", { className: cn("aui-ui-slider flex flex-col gap-1", node.className), children: [
2177
- (label || showValue) && /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: [
2178
- label && /* @__PURE__ */ jsx15("span", { children: label }),
2179
- showValue && /* @__PURE__ */ jsx15("span", { className: "font-mono", children: value })
2456
+ return /* @__PURE__ */ jsxs10("div", { className: cn("aui-ui-slider flex flex-col gap-1", node.className), children: [
2457
+ (label || showValue) && /* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: [
2458
+ label && /* @__PURE__ */ jsx16("span", { children: label }),
2459
+ showValue && /* @__PURE__ */ jsx16("span", { className: "font-mono", children: value })
2180
2460
  ] }),
2181
- /* @__PURE__ */ jsx15(
2461
+ /* @__PURE__ */ jsx16(
2182
2462
  "input",
2183
2463
  {
2184
2464
  type: "range",
@@ -2200,9 +2480,9 @@ var SliderNode = ({ node }) => {
2200
2480
  var TooltipNode = ({ node }) => {
2201
2481
  const state = useUiState();
2202
2482
  const content = String(resolveBindable(node.content, state) ?? "");
2203
- return /* @__PURE__ */ jsx15(TooltipProvider, { children: /* @__PURE__ */ jsxs9(Tooltip, { children: [
2204
- /* @__PURE__ */ jsx15(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx15("span", { className: cn("aui-ui-tooltip-trigger inline-flex", node.className), children: /* @__PURE__ */ jsx15(UiNodeView, { node: node.child }) }) }),
2205
- /* @__PURE__ */ jsx15(TooltipContent, { side: node.side ?? "top", children: content })
2483
+ return /* @__PURE__ */ jsx16(TooltipProvider, { children: /* @__PURE__ */ jsxs10(Tooltip, { children: [
2484
+ /* @__PURE__ */ jsx16(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx16("span", { className: cn("aui-ui-tooltip-trigger inline-flex", node.className), children: /* @__PURE__ */ jsx16(UiNodeView, { node: node.child }) }) }),
2485
+ /* @__PURE__ */ jsx16(TooltipContent, { side: node.side ?? "top", children: content })
2206
2486
  ] }) });
2207
2487
  };
2208
2488
  var DraggableNode = ({ node }) => {
@@ -2210,7 +2490,7 @@ var DraggableNode = ({ node }) => {
2210
2490
  const snapBack = node.snapBack ?? true;
2211
2491
  const axis = node.axis ?? "both";
2212
2492
  const dragProp = axis === "both" ? true : axis;
2213
- return /* @__PURE__ */ jsx15(
2493
+ return /* @__PURE__ */ jsx16(
2214
2494
  motion.div,
2215
2495
  {
2216
2496
  drag: dragProp,
@@ -2222,7 +2502,7 @@ var DraggableNode = ({ node }) => {
2222
2502
  "aui-ui-draggable inline-block cursor-grab touch-none",
2223
2503
  node.className
2224
2504
  ),
2225
- children: /* @__PURE__ */ jsx15(UiNodeView, { node: node.child })
2505
+ children: /* @__PURE__ */ jsx16(UiNodeView, { node: node.child })
2226
2506
  }
2227
2507
  );
2228
2508
  };
@@ -2232,8 +2512,8 @@ var CustomNode = ({ node }) => {
2232
2512
  const Renderer = registry[node.name];
2233
2513
  if (!Renderer) return null;
2234
2514
  const resolvedProps = resolveProps(node.props ?? {}, state);
2235
- const children = node.children?.map((child, i) => /* @__PURE__ */ jsx15(UiNodeView, { node: child }, child.id ?? i));
2236
- return /* @__PURE__ */ jsx15(Renderer, { props: resolvedProps, children });
2515
+ const children = node.children?.map((child, i) => /* @__PURE__ */ jsx16(UiNodeView, { node: child }, child.id ?? i));
2516
+ return /* @__PURE__ */ jsx16(Renderer, { props: resolvedProps, children });
2237
2517
  };
2238
2518
  function resolveProps(props, state) {
2239
2519
  const out = {};
@@ -2244,17 +2524,17 @@ function resolveProps(props, state) {
2244
2524
  }
2245
2525
 
2246
2526
  // src/artifacts/ui/ui-artifact.tsx
2247
- import { jsx as jsx16 } from "react/jsx-runtime";
2527
+ import { jsx as jsx17 } from "react/jsx-runtime";
2248
2528
  var UiArtifactView = ({ artifact }) => {
2249
2529
  const [state, dispatch] = useReducer(
2250
2530
  uiStateReducer,
2251
2531
  artifact.initialState ?? {}
2252
2532
  );
2253
- return /* @__PURE__ */ jsx16(ArtifactCard, { title: artifact.title, kind: "ui", children: /* @__PURE__ */ jsx16(UiStateProvider, { state, dispatch, children: /* @__PURE__ */ jsx16("div", { className: "aui-ui-root p-3", children: /* @__PURE__ */ jsx16(UiNodeView, { node: artifact.root }) }) }) });
2533
+ return /* @__PURE__ */ jsx17(ArtifactCard, { title: artifact.title, kind: "ui", children: /* @__PURE__ */ jsx17(UiStateProvider, { state, dispatch, children: /* @__PURE__ */ jsx17("div", { className: "aui-ui-root p-3", children: /* @__PURE__ */ jsx17(UiNodeView, { node: artifact.root }) }) }) });
2254
2534
  };
2255
2535
 
2256
2536
  // src/artifacts/registry.tsx
2257
- import { jsx as jsx17 } from "react/jsx-runtime";
2537
+ import { jsx as jsx18 } from "react/jsx-runtime";
2258
2538
  var defaultArtifactRenderers = {
2259
2539
  chart: ChartArtifactView,
2260
2540
  question: QuestionArtifactView,
@@ -2272,7 +2552,7 @@ var ArtifactRegistryProvider = ({ renderers, override, children }) => {
2272
2552
  if (override) return renderers;
2273
2553
  return { ...defaultArtifactRenderers, ...renderers };
2274
2554
  }, [renderers, override]);
2275
- return /* @__PURE__ */ jsx17(ArtifactRegistryContext.Provider, { value: merged, children });
2555
+ return /* @__PURE__ */ jsx18(ArtifactRegistryContext.Provider, { value: merged, children });
2276
2556
  };
2277
2557
  function useArtifactRegistry() {
2278
2558
  return useContext3(ArtifactRegistryContext);
@@ -2281,7 +2561,7 @@ var ArtifactView = ({ artifact }) => {
2281
2561
  const registry = useArtifactRegistry();
2282
2562
  const Renderer = registry[artifact.type] ?? registry.json;
2283
2563
  if (!Renderer) return null;
2284
- return /* @__PURE__ */ jsx17(Renderer, { artifact });
2564
+ return /* @__PURE__ */ jsx18(Renderer, { artifact });
2285
2565
  };
2286
2566
 
2287
2567
  // src/artifacts/parse.ts
@@ -2397,7 +2677,7 @@ import langC from "shiki/langs/c.mjs";
2397
2677
  import langCpp from "shiki/langs/cpp.mjs";
2398
2678
  import themeVitesseDark from "shiki/themes/vitesse-dark.mjs";
2399
2679
  import themeVitesseLight from "shiki/themes/vitesse-light.mjs";
2400
- import { jsx as jsx18 } from "react/jsx-runtime";
2680
+ import { jsx as jsx19 } from "react/jsx-runtime";
2401
2681
  var SHIKI_THEME_DARK = "vitesse-dark";
2402
2682
  var SHIKI_THEME_LIGHT = "vitesse-light";
2403
2683
  var highlighterPromise = null;
@@ -2466,13 +2746,13 @@ var ShikiSyntaxHighlighter = ({
2466
2746
  try {
2467
2747
  const parsed = JSON.parse(code);
2468
2748
  if (isArtifact(parsed)) {
2469
- return /* @__PURE__ */ jsx18(ArtifactView, { artifact: parsed });
2749
+ return /* @__PURE__ */ jsx19(ArtifactView, { artifact: parsed });
2470
2750
  }
2471
2751
  } catch {
2472
2752
  }
2473
2753
  }
2474
2754
  if (html) {
2475
- return /* @__PURE__ */ jsx18(
2755
+ return /* @__PURE__ */ jsx19(
2476
2756
  "div",
2477
2757
  {
2478
2758
  className: "shiki-wrapper [&>pre]:!m-0 [&>pre]:!rounded-t-none [&>pre]:!rounded-b-lg [&>pre]:!border [&>pre]:!border-t-0 [&>pre]:!border-border/50 [&>pre]:!p-3 [&>pre]:!text-xs [&>pre]:!leading-relaxed [&>pre]:overflow-x-auto",
@@ -2480,14 +2760,14 @@ var ShikiSyntaxHighlighter = ({
2480
2760
  }
2481
2761
  );
2482
2762
  }
2483
- return /* @__PURE__ */ jsx18(Pre, { children: /* @__PURE__ */ jsx18(Code2, { children: code }) });
2763
+ return /* @__PURE__ */ jsx19(Pre, { children: /* @__PURE__ */ jsx19(Code2, { children: code }) });
2484
2764
  };
2485
2765
  var syntax_highlighter_default = ShikiSyntaxHighlighter;
2486
2766
 
2487
2767
  // src/components/markdown-text.tsx
2488
- import { jsx as jsx19, jsxs as jsxs10 } from "react/jsx-runtime";
2768
+ import { jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
2489
2769
  var MarkdownTextImpl = () => {
2490
- return /* @__PURE__ */ jsx19(
2770
+ return /* @__PURE__ */ jsx20(
2491
2771
  MarkdownTextPrimitive,
2492
2772
  {
2493
2773
  remarkPlugins: [remarkGfm, remarkMath],
@@ -2508,20 +2788,20 @@ var CodeHeader = ({ language, code }) => {
2508
2788
  if (!code || isCopied) return;
2509
2789
  copyToClipboard(code);
2510
2790
  };
2511
- return /* @__PURE__ */ jsxs10("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: [
2512
- /* @__PURE__ */ jsxs10("span", { className: "flex items-center gap-2 text-xs font-semibold tracking-wide text-muted-foreground/80 uppercase", children: [
2513
- /* @__PURE__ */ jsx19("span", { className: "inline-block h-2 w-2 rounded-full bg-primary/40" }),
2791
+ return /* @__PURE__ */ jsxs11("div", { className: "aui-code-header flex items-center justify-between rounded-t-lg border border-b-0 border-border/50 bg-code-header-bg px-4 py-2", children: [
2792
+ /* @__PURE__ */ jsxs11("span", { className: "flex items-center gap-2 text-xs font-semibold tracking-wide text-muted-foreground/80 uppercase", children: [
2793
+ /* @__PURE__ */ jsx20("span", { className: "inline-block h-2 w-2 rounded-full bg-primary/40" }),
2514
2794
  language
2515
2795
  ] }),
2516
- /* @__PURE__ */ jsxs10(
2796
+ /* @__PURE__ */ jsxs11(
2517
2797
  TooltipIconButton,
2518
2798
  {
2519
2799
  tooltip: isCopied ? "Copied!" : "Copy",
2520
2800
  onClick: onCopy,
2521
2801
  className: "transition-colors hover:text-foreground",
2522
2802
  children: [
2523
- !isCopied && /* @__PURE__ */ jsx19(CopyIcon, { className: "h-3.5 w-3.5" }),
2524
- isCopied && /* @__PURE__ */ jsx19(CheckIcon2, { className: "h-3.5 w-3.5 text-emerald-500" })
2803
+ !isCopied && /* @__PURE__ */ jsx20(CopyIcon, { className: "h-3.5 w-3.5" }),
2804
+ isCopied && /* @__PURE__ */ jsx20(CheckIcon2, { className: "h-3.5 w-3.5 text-emerald-500" })
2525
2805
  ]
2526
2806
  }
2527
2807
  )
@@ -2541,7 +2821,7 @@ var useCopyToClipboard = ({
2541
2821
  return { isCopied, copyToClipboard };
2542
2822
  };
2543
2823
  var defaultComponents = memoizeMarkdownComponents({
2544
- h1: ({ className, ...props }) => /* @__PURE__ */ jsx19(
2824
+ h1: ({ className, ...props }) => /* @__PURE__ */ jsx20(
2545
2825
  "h1",
2546
2826
  {
2547
2827
  className: cn(
@@ -2551,7 +2831,7 @@ var defaultComponents = memoizeMarkdownComponents({
2551
2831
  ...props
2552
2832
  }
2553
2833
  ),
2554
- h2: ({ className, ...props }) => /* @__PURE__ */ jsx19(
2834
+ h2: ({ className, ...props }) => /* @__PURE__ */ jsx20(
2555
2835
  "h2",
2556
2836
  {
2557
2837
  className: cn(
@@ -2561,7 +2841,7 @@ var defaultComponents = memoizeMarkdownComponents({
2561
2841
  ...props
2562
2842
  }
2563
2843
  ),
2564
- h3: ({ className, ...props }) => /* @__PURE__ */ jsx19(
2844
+ h3: ({ className, ...props }) => /* @__PURE__ */ jsx20(
2565
2845
  "h3",
2566
2846
  {
2567
2847
  className: cn(
@@ -2571,7 +2851,7 @@ var defaultComponents = memoizeMarkdownComponents({
2571
2851
  ...props
2572
2852
  }
2573
2853
  ),
2574
- h4: ({ className, ...props }) => /* @__PURE__ */ jsx19(
2854
+ h4: ({ className, ...props }) => /* @__PURE__ */ jsx20(
2575
2855
  "h4",
2576
2856
  {
2577
2857
  className: cn(
@@ -2581,7 +2861,7 @@ var defaultComponents = memoizeMarkdownComponents({
2581
2861
  ...props
2582
2862
  }
2583
2863
  ),
2584
- h5: ({ className, ...props }) => /* @__PURE__ */ jsx19(
2864
+ h5: ({ className, ...props }) => /* @__PURE__ */ jsx20(
2585
2865
  "h5",
2586
2866
  {
2587
2867
  className: cn(
@@ -2591,7 +2871,7 @@ var defaultComponents = memoizeMarkdownComponents({
2591
2871
  ...props
2592
2872
  }
2593
2873
  ),
2594
- h6: ({ className, ...props }) => /* @__PURE__ */ jsx19(
2874
+ h6: ({ className, ...props }) => /* @__PURE__ */ jsx20(
2595
2875
  "h6",
2596
2876
  {
2597
2877
  className: cn(
@@ -2601,7 +2881,7 @@ var defaultComponents = memoizeMarkdownComponents({
2601
2881
  ...props
2602
2882
  }
2603
2883
  ),
2604
- p: ({ className, ...props }) => /* @__PURE__ */ jsx19(
2884
+ p: ({ className, ...props }) => /* @__PURE__ */ jsx20(
2605
2885
  "p",
2606
2886
  {
2607
2887
  className: cn(
@@ -2611,7 +2891,7 @@ var defaultComponents = memoizeMarkdownComponents({
2611
2891
  ...props
2612
2892
  }
2613
2893
  ),
2614
- a: ({ className, ...props }) => /* @__PURE__ */ jsx19(
2894
+ a: ({ className, ...props }) => /* @__PURE__ */ jsx20(
2615
2895
  "a",
2616
2896
  {
2617
2897
  className: cn(
@@ -2623,7 +2903,7 @@ var defaultComponents = memoizeMarkdownComponents({
2623
2903
  ...props
2624
2904
  }
2625
2905
  ),
2626
- blockquote: ({ className, ...props }) => /* @__PURE__ */ jsx19(
2906
+ blockquote: ({ className, ...props }) => /* @__PURE__ */ jsx20(
2627
2907
  "blockquote",
2628
2908
  {
2629
2909
  className: cn(
@@ -2633,7 +2913,7 @@ var defaultComponents = memoizeMarkdownComponents({
2633
2913
  ...props
2634
2914
  }
2635
2915
  ),
2636
- ul: ({ className, ...props }) => /* @__PURE__ */ jsx19(
2916
+ ul: ({ className, ...props }) => /* @__PURE__ */ jsx20(
2637
2917
  "ul",
2638
2918
  {
2639
2919
  className: cn(
@@ -2643,7 +2923,7 @@ var defaultComponents = memoizeMarkdownComponents({
2643
2923
  ...props
2644
2924
  }
2645
2925
  ),
2646
- ol: ({ className, ...props }) => /* @__PURE__ */ jsx19(
2926
+ ol: ({ className, ...props }) => /* @__PURE__ */ jsx20(
2647
2927
  "ol",
2648
2928
  {
2649
2929
  className: cn(
@@ -2653,7 +2933,7 @@ var defaultComponents = memoizeMarkdownComponents({
2653
2933
  ...props
2654
2934
  }
2655
2935
  ),
2656
- hr: ({ className, ...props }) => /* @__PURE__ */ jsx19(
2936
+ hr: ({ className, ...props }) => /* @__PURE__ */ jsx20(
2657
2937
  "hr",
2658
2938
  {
2659
2939
  className: cn(
@@ -2663,14 +2943,14 @@ var defaultComponents = memoizeMarkdownComponents({
2663
2943
  ...props
2664
2944
  }
2665
2945
  ),
2666
- table: ({ className, ...props }) => /* @__PURE__ */ jsx19("div", { className: "my-4 w-full overflow-x-auto rounded-lg border border-border/50", children: /* @__PURE__ */ jsx19(
2946
+ table: ({ className, ...props }) => /* @__PURE__ */ jsx20("div", { className: "my-4 w-full overflow-x-auto rounded-lg border border-border/50", children: /* @__PURE__ */ jsx20(
2667
2947
  "table",
2668
2948
  {
2669
2949
  className: cn("aui-md-table w-full border-collapse text-sm", className),
2670
2950
  ...props
2671
2951
  }
2672
2952
  ) }),
2673
- th: ({ className, ...props }) => /* @__PURE__ */ jsx19(
2953
+ th: ({ className, ...props }) => /* @__PURE__ */ jsx20(
2674
2954
  "th",
2675
2955
  {
2676
2956
  className: cn(
@@ -2680,7 +2960,7 @@ var defaultComponents = memoizeMarkdownComponents({
2680
2960
  ...props
2681
2961
  }
2682
2962
  ),
2683
- td: ({ className, ...props }) => /* @__PURE__ */ jsx19(
2963
+ td: ({ className, ...props }) => /* @__PURE__ */ jsx20(
2684
2964
  "td",
2685
2965
  {
2686
2966
  className: cn(
@@ -2690,7 +2970,7 @@ var defaultComponents = memoizeMarkdownComponents({
2690
2970
  ...props
2691
2971
  }
2692
2972
  ),
2693
- tr: ({ className, ...props }) => /* @__PURE__ */ jsx19(
2973
+ tr: ({ className, ...props }) => /* @__PURE__ */ jsx20(
2694
2974
  "tr",
2695
2975
  {
2696
2976
  className: cn(
@@ -2700,8 +2980,8 @@ var defaultComponents = memoizeMarkdownComponents({
2700
2980
  ...props
2701
2981
  }
2702
2982
  ),
2703
- li: ({ className, ...props }) => /* @__PURE__ */ jsx19("li", { className: cn("aui-md-li leading-[1.7]", className), ...props }),
2704
- sup: ({ className, ...props }) => /* @__PURE__ */ jsx19(
2983
+ li: ({ className, ...props }) => /* @__PURE__ */ jsx20("li", { className: cn("aui-md-li leading-[1.7]", className), ...props }),
2984
+ sup: ({ className, ...props }) => /* @__PURE__ */ jsx20(
2705
2985
  "sup",
2706
2986
  {
2707
2987
  className: cn(
@@ -2711,11 +2991,11 @@ var defaultComponents = memoizeMarkdownComponents({
2711
2991
  ...props
2712
2992
  }
2713
2993
  ),
2714
- pre: ({ className, ...props }) => /* @__PURE__ */ jsx19(
2994
+ pre: ({ className, ...props }) => /* @__PURE__ */ jsx20(
2715
2995
  "pre",
2716
2996
  {
2717
2997
  className: cn(
2718
- "aui-md-pre overflow-x-auto rounded-t-none rounded-b-lg border border-t-0 border-border/50 bg-zinc-50 p-4 text-[13px] leading-relaxed dark:bg-zinc-900/80",
2998
+ "aui-md-pre overflow-x-auto rounded-t-none rounded-b-lg border border-t-0 border-border/50 bg-code-block-bg p-4 text-[13px] leading-relaxed",
2719
2999
  className
2720
3000
  ),
2721
3001
  ...props
@@ -2723,25 +3003,28 @@ var defaultComponents = memoizeMarkdownComponents({
2723
3003
  ),
2724
3004
  code: function Code({ className, ...props }) {
2725
3005
  const isCodeBlock = useIsMarkdownCodeBlock();
2726
- return /* @__PURE__ */ jsx19(
3006
+ return /* @__PURE__ */ jsx20(
2727
3007
  "code",
2728
3008
  {
2729
3009
  className: cn(
2730
- !isCodeBlock && "aui-md-inline-code rounded-[5px] border border-border/60 bg-muted/60 px-[0.4em] py-[0.15em] font-mono text-[0.85em] font-medium text-foreground/90 dark:bg-muted/40",
3010
+ !isCodeBlock && "aui-md-inline-code rounded-[5px] border border-border/60 bg-muted/60 px-[0.4em] py-[0.15em] font-mono text-[0.85em] font-medium text-foreground/90",
2731
3011
  className
2732
3012
  ),
2733
3013
  ...props
2734
3014
  }
2735
3015
  );
2736
3016
  },
2737
- strong: ({ className, ...props }) => /* @__PURE__ */ jsx19("strong", { className: cn("font-semibold text-foreground", className), ...props }),
2738
- em: ({ className, ...props }) => /* @__PURE__ */ jsx19("em", { className: cn("italic", className), ...props }),
3017
+ strong: ({ className, ...props }) => /* @__PURE__ */ jsx20("strong", { className: cn("font-semibold text-foreground", className), ...props }),
3018
+ em: ({ className, ...props }) => /* @__PURE__ */ jsx20("em", { className: cn("italic", className), ...props }),
2739
3019
  CodeHeader
2740
3020
  });
2741
3021
 
2742
3022
  // src/components/tool-fallback.tsx
2743
- import { memo as memo3, useState as useState6 } from "react";
2744
- import { ChevronDownIcon, WrenchIcon } from "lucide-react";
3023
+ import { memo as memo3, useMemo as useMemo5, useState as useState6 } from "react";
3024
+ import { ChevronRightIcon } from "lucide-react";
3025
+ import {
3026
+ useAuiState as useAuiState2
3027
+ } from "@assistant-ui/react";
2745
3028
 
2746
3029
  // src/ui/shimmer.tsx
2747
3030
  import { motion as motion2 } from "motion/react";
@@ -2749,7 +3032,7 @@ import {
2749
3032
  memo as memo2,
2750
3033
  useMemo as useMemo4
2751
3034
  } from "react";
2752
- import { jsx as jsx20 } from "react/jsx-runtime";
3035
+ import { jsx as jsx21 } from "react/jsx-runtime";
2753
3036
  var ShimmerComponent = ({
2754
3037
  children,
2755
3038
  as: Component = "p",
@@ -2764,7 +3047,7 @@ var ShimmerComponent = ({
2764
3047
  () => (children?.length ?? 0) * spread,
2765
3048
  [children, spread]
2766
3049
  );
2767
- return /* @__PURE__ */ jsx20(
3050
+ return /* @__PURE__ */ jsx21(
2768
3051
  MotionComponent,
2769
3052
  {
2770
3053
  animate: { backgroundPosition: "0% center" },
@@ -2789,78 +3072,130 @@ var ShimmerComponent = ({
2789
3072
  };
2790
3073
  var Shimmer = memo2(ShimmerComponent);
2791
3074
 
2792
- // src/components/tool-fallback.tsx
2793
- import { jsx as jsx21, jsxs as jsxs11 } from "react/jsx-runtime";
2794
- var ToolFallbackImpl = ({
2795
- toolName,
2796
- argsText,
2797
- result,
2798
- status
2799
- }) => {
2800
- const isRunning = status?.type === "running";
2801
- const isError = status?.type === "incomplete" && status.reason !== "cancelled";
2802
- if (isRunning) {
2803
- return /* @__PURE__ */ jsxs11("div", { className: "aui-tool-fallback-running flex items-center gap-2 py-1 text-sm text-muted-foreground", children: [
2804
- /* @__PURE__ */ jsx21(WrenchIcon, { className: "size-4" }),
2805
- /* @__PURE__ */ jsx21(Shimmer, { as: "span", duration: 1.8, spread: 2.5, children: `Using tool: ${toolName}` })
2806
- ] });
3075
+ // src/components/motion.tsx
3076
+ import { AnimatePresence, motion as motion3, useReducedMotion } from "motion/react";
3077
+ import { jsx as jsx22 } from "react/jsx-runtime";
3078
+ var luxuryEase = [0.16, 1, 0.3, 1];
3079
+ var TOOL_ENTER_MS = 0.78;
3080
+ var TOOL_EXIT_MS = 0.28;
3081
+ function toolPresenceTransition(reduced) {
3082
+ return {
3083
+ enter: {
3084
+ duration: reduced ? 0.35 : TOOL_ENTER_MS,
3085
+ ease: luxuryEase
3086
+ },
3087
+ exit: {
3088
+ duration: reduced ? 0.2 : TOOL_EXIT_MS,
3089
+ ease: [0.4, 0, 1, 1]
3090
+ }
3091
+ };
3092
+ }
3093
+ function toolMotionState(reduced, entering, variant) {
3094
+ if (reduced) {
3095
+ return entering ? { opacity: 0, y: variant === "executing" ? 8 : 10 } : { opacity: 1, y: 0 };
2807
3096
  }
2808
- return /* @__PURE__ */ jsx21(
2809
- ToolPanel,
3097
+ if (variant === "executing") {
3098
+ return entering ? { opacity: 0, y: 12 } : { opacity: 1, y: 0 };
3099
+ }
3100
+ return entering ? { opacity: 0, y: 14, filter: "blur(10px)" } : { opacity: 1, y: 0, filter: "blur(0px)" };
3101
+ }
3102
+ function ToolMotion({ children, className, motionKey }) {
3103
+ const reduced = useReducedMotion() ?? false;
3104
+ const { enter, exit } = toolPresenceTransition(reduced);
3105
+ return /* @__PURE__ */ jsx22(
3106
+ motion3.div,
2810
3107
  {
2811
- toolName,
2812
- argsText,
2813
- result,
2814
- isError
2815
- }
3108
+ className: cn("aui-tool-motion w-full min-w-0", className),
3109
+ initial: toolMotionState(reduced, true, "settled"),
3110
+ animate: toolMotionState(reduced, false, "settled"),
3111
+ exit: reduced ? { opacity: 0, y: 6, transition: exit } : { opacity: 0, y: 8, filter: "blur(6px)", transition: exit },
3112
+ transition: enter,
3113
+ style: { willChange: "opacity, transform, filter" },
3114
+ children
3115
+ },
3116
+ motionKey
2816
3117
  );
2817
- };
2818
- var ToolPanel = ({ toolName, argsText, result, isError }) => {
2819
- const [open, setOpen] = useState6(false);
2820
- return /* @__PURE__ */ jsxs11(
3118
+ }
3119
+ function ToolPresence({
3120
+ presenceKey,
3121
+ children,
3122
+ className,
3123
+ variant = "settled"
3124
+ }) {
3125
+ const reduced = useReducedMotion() ?? false;
3126
+ const { enter, exit } = toolPresenceTransition(reduced);
3127
+ const enterTransition = variant === "executing" ? { duration: reduced ? 0.3 : 0.52, ease: luxuryEase } : enter;
3128
+ return /* @__PURE__ */ jsx22(AnimatePresence, { mode: "wait", initial: true, children: /* @__PURE__ */ jsx22(
3129
+ motion3.div,
3130
+ {
3131
+ className: cn("aui-tool-presence w-full min-w-0", className),
3132
+ initial: toolMotionState(reduced, true, variant),
3133
+ animate: toolMotionState(reduced, false, variant),
3134
+ exit: reduced ? { opacity: 0, y: 6, transition: exit } : { opacity: 0, y: 8, filter: "blur(6px)", transition: exit },
3135
+ transition: enterTransition,
3136
+ style: {
3137
+ willChange: variant === "executing" ? "opacity, transform" : "opacity, transform, filter"
3138
+ },
3139
+ children
3140
+ },
3141
+ presenceKey
3142
+ ) });
3143
+ }
3144
+ function ToolBodyPresence({
3145
+ open,
3146
+ children,
3147
+ className
3148
+ }) {
3149
+ const reduced = useReducedMotion() ?? false;
3150
+ return /* @__PURE__ */ jsx22(
2821
3151
  "div",
2822
3152
  {
2823
3153
  className: cn(
2824
- "aui-tool-fallback-root my-2 overflow-hidden rounded-lg border border-border/60 bg-muted/30 text-sm",
2825
- isError && "border-destructive/50 bg-destructive/5"
3154
+ "aui-tool-body grid min-h-0 transition-[grid-template-rows]",
3155
+ open ? reduced ? "duration-200 ease-out" : "duration-[340ms] ease-[cubic-bezier(0.16,1,0.3,1)]" : reduced ? "duration-150 ease-[cubic-bezier(0.4,0,0.2,1)]" : "duration-200 ease-[cubic-bezier(0.4,0,0.2,1)]"
2826
3156
  ),
2827
- children: [
2828
- /* @__PURE__ */ jsxs11(
2829
- "button",
2830
- {
2831
- type: "button",
2832
- onClick: () => setOpen((v) => !v),
2833
- className: "aui-tool-fallback-header flex w-full items-center gap-2 px-3 py-2 text-left text-muted-foreground transition-colors hover:bg-muted/50",
2834
- "aria-expanded": open,
2835
- children: [
2836
- /* @__PURE__ */ jsx21(WrenchIcon, { className: "size-3.5" }),
2837
- /* @__PURE__ */ jsx21("span", { className: "aui-tool-fallback-name flex-1 truncate font-mono text-xs font-medium text-foreground/80", children: toolName }),
2838
- isError && /* @__PURE__ */ jsx21("span", { className: "aui-tool-fallback-status text-xs font-medium text-destructive", children: "error" }),
2839
- /* @__PURE__ */ jsx21(
2840
- ChevronDownIcon,
2841
- {
2842
- className: cn(
2843
- "size-3.5 shrink-0 transition-transform",
2844
- open && "rotate-180"
2845
- )
2846
- }
2847
- )
2848
- ]
2849
- }
2850
- ),
2851
- open && /* @__PURE__ */ jsxs11("div", { className: "aui-tool-fallback-body grid gap-2 border-t border-border/40 bg-background/50 px-3 py-2.5 text-xs", children: [
2852
- argsText && argsText !== "{}" && /* @__PURE__ */ jsx21(Section, { label: "Input", value: argsText }),
2853
- result !== void 0 && result !== null && /* @__PURE__ */ jsx21(Section, { label: "Output", value: formatResult(result) })
2854
- ] })
2855
- ]
3157
+ style: { gridTemplateRows: open ? "1fr" : "0fr" },
3158
+ children: /* @__PURE__ */ jsx22("div", { className: "min-h-0 overflow-hidden", children: /* @__PURE__ */ jsx22(
3159
+ "div",
3160
+ {
3161
+ className: cn(
3162
+ className,
3163
+ "transition-opacity",
3164
+ open ? reduced ? "opacity-100 duration-200 ease-out" : "opacity-100 duration-300 ease-[cubic-bezier(0.16,1,0.3,1)] delay-75" : reduced ? "opacity-0 duration-100 ease-in" : "opacity-0 duration-150 ease-[cubic-bezier(0.4,0,0.2,1)]"
3165
+ ),
3166
+ children
3167
+ }
3168
+ ) })
2856
3169
  }
2857
3170
  );
2858
- };
2859
- var Section = ({ label, value }) => /* @__PURE__ */ jsxs11("div", { className: "aui-tool-fallback-section", children: [
2860
- /* @__PURE__ */ jsx21("div", { className: "aui-tool-fallback-section-label mb-0.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: label }),
2861
- /* @__PURE__ */ jsx21("pre", { className: "aui-tool-fallback-section-value overflow-x-auto whitespace-pre-wrap break-words font-mono text-[11px] leading-relaxed text-foreground/85", children: value })
2862
- ] });
2863
- function formatResult(result) {
3171
+ }
3172
+
3173
+ // src/components/tool-fallback.tsx
3174
+ import { jsx as jsx23, jsxs as jsxs12 } from "react/jsx-runtime";
3175
+ function detectRunning({
3176
+ status,
3177
+ result,
3178
+ streamRunning
3179
+ }) {
3180
+ const isError = status?.type === "incomplete" && status.reason !== "cancelled";
3181
+ if (isError) return false;
3182
+ if (status?.type === "running") return true;
3183
+ if (status?.type === "complete") return false;
3184
+ return streamRunning && result === void 0;
3185
+ }
3186
+ function useToolRunning(props) {
3187
+ const { isRunning: streamRunning } = useTimbalRuntime();
3188
+ const partStatus = useAuiState2((s) => s.part.status);
3189
+ return detectRunning({
3190
+ status: partStatus ?? props.status,
3191
+ result: props.result,
3192
+ streamRunning
3193
+ });
3194
+ }
3195
+ function formatToolLabel(toolName) {
3196
+ return toolName.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase();
3197
+ }
3198
+ function formatToolResult(result) {
2864
3199
  if (typeof result === "string") return result;
2865
3200
  try {
2866
3201
  return JSON.stringify(result, null, 2);
@@ -2868,26 +3203,166 @@ function formatResult(result) {
2868
3203
  return String(result);
2869
3204
  }
2870
3205
  }
3206
+ var TimelineActionLabel = ({ action, detail, shimmer = false }) => /* @__PURE__ */ jsxs12("span", { className: "inline-flex min-w-0 max-w-full items-baseline gap-1", children: [
3207
+ action ? shimmer ? /* @__PURE__ */ jsx23(
3208
+ Shimmer,
3209
+ {
3210
+ as: "span",
3211
+ className: cn(studioTimelineShimmerActionClass, "aui-tool-shimmer"),
3212
+ duration: 1.8,
3213
+ spread: 2.5,
3214
+ children: action
3215
+ }
3216
+ ) : /* @__PURE__ */ jsx23("span", { className: studioTimelineActionClass, children: action }) : null,
3217
+ detail ? /* @__PURE__ */ jsx23("span", { className: studioTimelineDetailClass, children: detail }) : null
3218
+ ] });
3219
+ var TimelineHoverChevron = ({ expanded }) => /* @__PURE__ */ jsx23(
3220
+ ChevronRightIcon,
3221
+ {
3222
+ className: studioTimelineChevronClass(expanded),
3223
+ "aria-hidden": true
3224
+ }
3225
+ );
3226
+ var ToolPanel = ({ toolName, argsText, result, isError }) => {
3227
+ const [open, setOpen] = useState6(false);
3228
+ const detail = formatToolLabel(toolName);
3229
+ const formattedArgs = useMemo5(() => {
3230
+ if (!argsText || argsText === "{}") return null;
3231
+ try {
3232
+ return JSON.stringify(JSON.parse(argsText), null, 2);
3233
+ } catch {
3234
+ return argsText;
3235
+ }
3236
+ }, [argsText]);
3237
+ const formattedResult = useMemo5(() => {
3238
+ if (result === void 0 || result === null) return null;
3239
+ return formatToolResult(result);
3240
+ }, [result]);
3241
+ const hasBody = Boolean(formattedArgs || formattedResult);
3242
+ const action = isError ? "Failed" : "Used";
3243
+ if (!hasBody) {
3244
+ return /* @__PURE__ */ jsx23("div", { className: "aui-tool-row w-full min-w-0", children: /* @__PURE__ */ jsx23(TimelineActionLabel, { action, detail }) });
3245
+ }
3246
+ return /* @__PURE__ */ jsxs12("div", { className: "aui-tool-row w-full min-w-0", children: [
3247
+ /* @__PURE__ */ jsx23(
3248
+ "button",
3249
+ {
3250
+ type: "button",
3251
+ onClick: () => setOpen((v) => !v),
3252
+ "aria-expanded": open,
3253
+ "aria-label": `${action} ${detail}`,
3254
+ className: studioTimelineRowButtonClass,
3255
+ children: /* @__PURE__ */ jsxs12(
3256
+ "span",
3257
+ {
3258
+ className: cn(
3259
+ "inline-flex min-w-0 max-w-full items-center gap-0.5",
3260
+ studioTimelineTextClass,
3261
+ "text-foreground"
3262
+ ),
3263
+ children: [
3264
+ /* @__PURE__ */ jsx23(TimelineActionLabel, { action, detail }),
3265
+ /* @__PURE__ */ jsx23(TimelineHoverChevron, { expanded: open })
3266
+ ]
3267
+ }
3268
+ )
3269
+ }
3270
+ ),
3271
+ /* @__PURE__ */ jsxs12(
3272
+ ToolBodyPresence,
3273
+ {
3274
+ open,
3275
+ className: cn(studioTimelineBodyPadClass, "gap-2"),
3276
+ children: [
3277
+ formattedArgs ? /* @__PURE__ */ jsx23(
3278
+ "div",
3279
+ {
3280
+ className: cn(
3281
+ studioComposerIoWellClass,
3282
+ "max-h-48 overflow-auto px-2.5 py-2"
3283
+ ),
3284
+ children: /* @__PURE__ */ jsx23("pre", { className: "whitespace-pre-wrap break-words font-mono text-[11px] font-normal leading-relaxed text-foreground", children: formattedArgs })
3285
+ }
3286
+ ) : null,
3287
+ formattedResult ? /* @__PURE__ */ jsx23(
3288
+ "div",
3289
+ {
3290
+ className: cn(
3291
+ studioComposerIoWellClass,
3292
+ "max-h-48 overflow-auto px-2.5 py-2"
3293
+ ),
3294
+ children: /* @__PURE__ */ jsx23("pre", { className: "whitespace-pre-wrap break-words font-mono text-[11px] font-normal leading-relaxed text-foreground", children: formattedResult })
3295
+ }
3296
+ ) : null
3297
+ ]
3298
+ }
3299
+ )
3300
+ ] });
3301
+ };
3302
+ var ToolFallbackImpl = ({
3303
+ toolName,
3304
+ argsText,
3305
+ result,
3306
+ status
3307
+ }) => {
3308
+ const isRunning = useToolRunning({ status, result });
3309
+ const isError = status?.type === "incomplete" && status.reason !== "cancelled";
3310
+ const presenceKey = isRunning ? "running" : isError ? "error" : "complete";
3311
+ return /* @__PURE__ */ jsx23(
3312
+ ToolPresence,
3313
+ {
3314
+ presenceKey,
3315
+ variant: isRunning ? "executing" : "settled",
3316
+ className: "py-0.5",
3317
+ children: isRunning ? /* @__PURE__ */ jsx23("div", { className: "aui-tool-running", children: /* @__PURE__ */ jsx23(
3318
+ TimelineActionLabel,
3319
+ {
3320
+ action: "Using",
3321
+ detail: formatToolLabel(toolName),
3322
+ shimmer: true
3323
+ }
3324
+ ) }) : /* @__PURE__ */ jsx23(
3325
+ ToolPanel,
3326
+ {
3327
+ toolName,
3328
+ argsText,
3329
+ result,
3330
+ isError
3331
+ }
3332
+ )
3333
+ }
3334
+ );
3335
+ };
2871
3336
  var ToolFallback = memo3(
2872
3337
  ToolFallbackImpl
2873
3338
  );
2874
3339
  ToolFallback.displayName = "ToolFallback";
2875
3340
 
2876
3341
  // src/artifacts/tool-artifact.tsx
2877
- import { jsx as jsx22 } from "react/jsx-runtime";
3342
+ import { jsx as jsx24 } from "react/jsx-runtime";
2878
3343
  var ToolArtifactFallback = (props) => {
2879
3344
  const registry = useArtifactRegistry();
2880
- const isRunning = props.status?.type === "running";
3345
+ const isRunning = useToolRunning({
3346
+ status: props.status,
3347
+ result: props.result
3348
+ });
2881
3349
  if (!isRunning) {
2882
3350
  const artifact = parseArtifactFromToolResult(props.result);
2883
3351
  if (artifact) {
2884
3352
  const Renderer = registry[artifact.type];
2885
3353
  if (Renderer) {
2886
- return /* @__PURE__ */ jsx22(Renderer, { artifact });
3354
+ return /* @__PURE__ */ jsx24(
3355
+ ToolMotion,
3356
+ {
3357
+ motionKey: `artifact-${artifact.type}`,
3358
+ className: "aui-tool-artifact",
3359
+ children: /* @__PURE__ */ jsx24(Renderer, { artifact })
3360
+ }
3361
+ );
2887
3362
  }
2888
3363
  }
2889
3364
  }
2890
- return /* @__PURE__ */ jsx22(ToolFallback, { ...props });
3365
+ return /* @__PURE__ */ jsx24(ToolFallback, { ...props });
2891
3366
  };
2892
3367
 
2893
3368
  // src/components/composer.tsx
@@ -2897,7 +3372,7 @@ import {
2897
3372
  useComposerRuntime
2898
3373
  } from "@assistant-ui/react";
2899
3374
  import { ArrowUpIcon, SquareIcon } from "lucide-react";
2900
- import { Fragment as Fragment2, jsx as jsx23, jsxs as jsxs12 } from "react/jsx-runtime";
3375
+ import { Fragment as Fragment2, jsx as jsx25, jsxs as jsxs13 } from "react/jsx-runtime";
2901
3376
  var Composer = ({
2902
3377
  placeholder = "Send a message...",
2903
3378
  showAttachments = true,
@@ -2906,21 +3381,24 @@ var Composer = ({
2906
3381
  noAutoFocus,
2907
3382
  className
2908
3383
  }) => {
2909
- return /* @__PURE__ */ jsx23(
3384
+ return /* @__PURE__ */ jsx25(
2910
3385
  ComposerPrimitive2.Root,
2911
3386
  {
2912
3387
  className: cn(
2913
- "aui-composer-root relative mt-3 flex w-full flex-col",
3388
+ "aui-composer-root relative flex w-full flex-col",
2914
3389
  className
2915
3390
  ),
2916
- children: /* @__PURE__ */ jsxs12(
3391
+ children: /* @__PURE__ */ jsxs13(
2917
3392
  ComposerPrimitive2.AttachmentDropzone,
2918
3393
  {
2919
- className: "aui-composer-attachment-dropzone flex w-full flex-col rounded-2xl border border-input bg-background px-1 pt-2 outline-none transition-shadow has-[textarea:focus-visible]:border-ring has-[textarea:focus-visible]:ring-2 has-[textarea:focus-visible]:ring-ring/20 data-[dragging=true]:border-ring data-[dragging=true]:border-dashed data-[dragging=true]:bg-accent/50",
3394
+ className: cn(
3395
+ studioComposeInputShellClass,
3396
+ "data-[dragging=true]:border-2 data-[dragging=true]:border-dashed data-[dragging=true]:border-primary data-[dragging=true]:bg-accent/50"
3397
+ ),
2920
3398
  children: [
2921
- showAttachments && /* @__PURE__ */ jsx23(ComposerAttachments, {}),
2922
- /* @__PURE__ */ jsx23(ComposerInput, { placeholder, autoFocus: !noAutoFocus }),
2923
- /* @__PURE__ */ jsx23(
3399
+ showAttachments && /* @__PURE__ */ jsx25(ComposerAttachments, {}),
3400
+ /* @__PURE__ */ jsx25(ComposerInput, { placeholder, autoFocus: !noAutoFocus }),
3401
+ /* @__PURE__ */ jsx25(
2924
3402
  ComposerToolbar,
2925
3403
  {
2926
3404
  showAttachments,
@@ -2950,11 +3428,11 @@ var ComposerInput = ({
2950
3428
  el.style.height = "auto";
2951
3429
  el.style.height = `${Math.min(el.scrollHeight, 240)}px`;
2952
3430
  };
2953
- return /* @__PURE__ */ jsx23(
3431
+ return /* @__PURE__ */ jsx25(
2954
3432
  ComposerPrimitive2.Input,
2955
3433
  {
2956
3434
  placeholder,
2957
- className: "aui-composer-input mb-1 max-h-60 min-h-12 w-full resize-none bg-transparent px-4 pt-2 pb-3 text-sm outline-none placeholder:text-muted-foreground focus-visible:ring-0",
3435
+ className: "aui-composer-input max-h-60 min-h-14 w-full resize-none bg-transparent px-3 pt-3 pb-1 text-sm outline-none placeholder:text-muted-foreground/70 focus-visible:ring-0",
2958
3436
  rows: 1,
2959
3437
  autoFocus,
2960
3438
  "aria-label": "Message input",
@@ -2964,37 +3442,35 @@ var ComposerInput = ({
2964
3442
  );
2965
3443
  };
2966
3444
  var ComposerToolbar = ({ showAttachments, toolbar, sendTooltip }) => {
2967
- return /* @__PURE__ */ jsxs12("div", { className: "aui-composer-action-wrapper relative mx-2 mb-2 flex items-center gap-1", children: [
2968
- showAttachments && /* @__PURE__ */ jsx23(ComposerAddAttachment, {}),
2969
- toolbar,
2970
- /* @__PURE__ */ jsx23("div", { className: "flex-1" }),
2971
- /* @__PURE__ */ jsx23(ComposerSendOrCancel, { sendTooltip })
3445
+ return /* @__PURE__ */ jsxs13("div", { className: "aui-composer-action-wrapper flex items-center justify-between gap-1 px-2.5 pb-2.5", children: [
3446
+ /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-1", children: [
3447
+ showAttachments && /* @__PURE__ */ jsx25(ComposerAddAttachment, {}),
3448
+ toolbar
3449
+ ] }),
3450
+ /* @__PURE__ */ jsx25(ComposerSendOrCancel, { sendTooltip })
2972
3451
  ] });
2973
3452
  };
2974
3453
  var ComposerSendOrCancel = ({ sendTooltip }) => {
2975
- return /* @__PURE__ */ jsxs12(Fragment2, { children: [
2976
- /* @__PURE__ */ jsx23(AuiIf, { condition: (s) => !s.thread.isRunning, children: /* @__PURE__ */ jsx23(ComposerPrimitive2.Send, { asChild: true, children: /* @__PURE__ */ jsx23(
3454
+ return /* @__PURE__ */ jsxs13(Fragment2, { children: [
3455
+ /* @__PURE__ */ jsx25(AuiIf, { condition: (s) => !s.thread.isRunning, children: /* @__PURE__ */ jsx25(ComposerPrimitive2.Send, { asChild: true, children: /* @__PURE__ */ jsx25(
2977
3456
  TooltipIconButton,
2978
3457
  {
2979
3458
  tooltip: sendTooltip,
2980
- side: "bottom",
3459
+ variant: "primary",
2981
3460
  type: "submit",
2982
- variant: "default",
2983
- size: "icon",
2984
- className: "aui-composer-send size-8 rounded-full",
3461
+ className: "aui-composer-send shrink-0 disabled:opacity-30",
2985
3462
  "aria-label": "Send message",
2986
- children: /* @__PURE__ */ jsx23(ArrowUpIcon, { className: "aui-composer-send-icon size-4" })
3463
+ children: /* @__PURE__ */ jsx25(ArrowUpIcon, { className: "aui-composer-send-icon size-4" })
2987
3464
  }
2988
3465
  ) }) }),
2989
- /* @__PURE__ */ jsx23(AuiIf, { condition: (s) => s.thread.isRunning, children: /* @__PURE__ */ jsx23(ComposerPrimitive2.Cancel, { asChild: true, children: /* @__PURE__ */ jsx23(
2990
- Button,
3466
+ /* @__PURE__ */ jsx25(AuiIf, { condition: (s) => s.thread.isRunning, children: /* @__PURE__ */ jsx25(ComposerPrimitive2.Cancel, { asChild: true, children: /* @__PURE__ */ jsx25(
3467
+ TooltipIconButton,
2991
3468
  {
2992
- type: "button",
2993
- variant: "default",
2994
- size: "icon",
2995
- className: "aui-composer-cancel size-8 rounded-full",
3469
+ tooltip: "Stop generating",
3470
+ variant: "primary",
3471
+ className: "aui-composer-cancel shrink-0",
2996
3472
  "aria-label": "Stop generating",
2997
- children: /* @__PURE__ */ jsx23(SquareIcon, { className: "aui-composer-cancel-icon size-3 fill-current" })
3473
+ children: /* @__PURE__ */ jsx25(SquareIcon, { className: "aui-composer-cancel-icon size-3 fill-current" })
2998
3474
  }
2999
3475
  ) }) })
3000
3476
  ] });
@@ -3003,55 +3479,53 @@ var ComposerSendOrCancel = ({ sendTooltip }) => {
3003
3479
  // src/components/suggestions.tsx
3004
3480
  import {
3005
3481
  useEffect as useEffect4,
3006
- useMemo as useMemo5,
3482
+ useMemo as useMemo6,
3007
3483
  useState as useState7
3008
3484
  } from "react";
3009
3485
  import { useThreadRuntime as useThreadRuntime3 } from "@assistant-ui/react";
3010
- import { jsx as jsx24, jsxs as jsxs13 } from "react/jsx-runtime";
3486
+ import { ArrowUpIcon as ArrowUpIcon2 } from "lucide-react";
3487
+ import { jsx as jsx26, jsxs as jsxs14 } from "react/jsx-runtime";
3011
3488
  var Suggestions = ({
3012
3489
  suggestions,
3013
- layout = "grid",
3014
3490
  className
3015
3491
  }) => {
3016
3492
  const items = useResolvedSuggestions(suggestions);
3017
3493
  if (!items || items.length === 0) return null;
3018
- return /* @__PURE__ */ jsx24(
3494
+ return /* @__PURE__ */ jsx26(
3019
3495
  "div",
3020
3496
  {
3021
3497
  className: cn(
3022
- "aui-thread-suggestions w-full pb-4",
3023
- layout === "grid" ? "grid gap-2 @md:grid-cols-2" : "flex gap-2 overflow-x-auto pb-1 [&::-webkit-scrollbar]:hidden",
3498
+ "aui-thread-suggestions flex w-full flex-col gap-2 pb-2.5",
3024
3499
  className
3025
3500
  ),
3026
- children: items.map((s, i) => /* @__PURE__ */ jsx24(SuggestionChip, { suggestion: s, compact: layout === "row" }, s.title + i))
3501
+ role: "list",
3502
+ "aria-label": "Suggested prompts",
3503
+ children: items.map((suggestion, i) => /* @__PURE__ */ jsx26(SuggestionRow, { suggestion }, (suggestion.prompt ?? suggestion.title) + i))
3027
3504
  }
3028
3505
  );
3029
3506
  };
3030
- var SuggestionChip = ({
3031
- suggestion,
3032
- compact
3033
- }) => {
3507
+ var SuggestionRow = ({ suggestion }) => {
3034
3508
  const runtime = useThreadRuntime3();
3035
3509
  const onClick = () => {
3036
3510
  const text = suggestion.prompt ?? suggestion.title;
3037
3511
  runtime.append({ role: "user", content: [{ type: "text", text }] });
3038
3512
  };
3039
- return /* @__PURE__ */ jsx24("div", { className: "aui-thread-suggestion-display fade-in slide-in-from-bottom-2 animate-in fill-mode-both duration-200", children: /* @__PURE__ */ jsxs13(
3040
- Button,
3513
+ return /* @__PURE__ */ jsxs14(
3514
+ "button",
3041
3515
  {
3042
- variant: "ghost",
3516
+ type: "button",
3517
+ role: "listitem",
3043
3518
  onClick,
3044
- className: cn(
3045
- "aui-thread-suggestion h-auto rounded-2xl border text-left text-sm transition-colors hover:bg-muted",
3046
- compact ? "shrink-0 flex-row items-center gap-2 whitespace-nowrap px-3 py-2" : "w-full flex-wrap items-start justify-start gap-1 px-4 py-3 @md:flex-col"
3047
- ),
3519
+ className: cn("aui-thread-suggestion", studioListRowButtonClass),
3048
3520
  children: [
3049
- suggestion.icon && /* @__PURE__ */ jsx24("span", { className: "aui-thread-suggestion-icon shrink-0 text-muted-foreground", children: suggestion.icon }),
3050
- /* @__PURE__ */ jsx24("span", { className: "aui-thread-suggestion-text-1 font-medium", children: suggestion.title }),
3051
- suggestion.description && !compact && /* @__PURE__ */ jsx24("span", { className: "aui-thread-suggestion-text-2 text-muted-foreground", children: suggestion.description })
3521
+ /* @__PURE__ */ jsx26("span", { className: "aui-thread-suggestion-icon shrink-0 text-muted-foreground", children: suggestion.icon ?? /* @__PURE__ */ jsx26(ArrowUpIcon2, { className: "size-4", strokeWidth: 1.75, "aria-hidden": true }) }),
3522
+ /* @__PURE__ */ jsxs14("span", { className: "aui-thread-suggestion-text min-w-0 flex-1 text-left", children: [
3523
+ /* @__PURE__ */ jsx26("span", { className: "aui-thread-suggestion-text-1 block truncate text-sm font-normal text-foreground", children: suggestion.title }),
3524
+ suggestion.description && /* @__PURE__ */ jsx26("span", { className: "aui-thread-suggestion-text-2 mt-0.5 block truncate text-xs text-muted-foreground", children: suggestion.description })
3525
+ ] })
3052
3526
  ]
3053
3527
  }
3054
- ) });
3528
+ );
3055
3529
  };
3056
3530
  function useResolvedSuggestions(source) {
3057
3531
  const [resolved, setResolved] = useState7(
@@ -3076,29 +3550,80 @@ function useResolvedSuggestions(source) {
3076
3550
  cancelled = true;
3077
3551
  };
3078
3552
  }, [source]);
3079
- return useMemo5(() => resolved, [resolved]);
3553
+ return useMemo6(() => resolved, [resolved]);
3554
+ }
3555
+
3556
+ // src/design/theme-sanity.ts
3557
+ var scheduled = false;
3558
+ var warned = false;
3559
+ function isDev() {
3560
+ if (typeof process !== "undefined" && process.env?.NODE_ENV === "production") {
3561
+ return false;
3562
+ }
3563
+ return true;
3564
+ }
3565
+ function parseLuminance(color) {
3566
+ const value = color.trim();
3567
+ if (!value) return null;
3568
+ const oklch = value.match(/oklch\(\s*([0-9.]+)/i);
3569
+ if (oklch) {
3570
+ const lightness = Number.parseFloat(oklch[1]);
3571
+ if (Number.isFinite(lightness)) return lightness;
3572
+ }
3573
+ const rgb = value.match(/rgba?\(\s*([0-9.]+)[\s,]+([0-9.]+)[\s,]+([0-9.]+)/i);
3574
+ if (rgb) {
3575
+ const r = Number.parseFloat(rgb[1]) / 255;
3576
+ const g = Number.parseFloat(rgb[2]) / 255;
3577
+ const b = Number.parseFloat(rgb[3]) / 255;
3578
+ if ([r, g, b].every(Number.isFinite)) {
3579
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
3580
+ }
3581
+ }
3582
+ const hsl = value.match(/hsla?\(\s*[0-9.]+[\s,]+[0-9.]+%[\s,]+([0-9.]+)%/i);
3583
+ if (hsl) {
3584
+ const lightness = Number.parseFloat(hsl[1]) / 100;
3585
+ if (Number.isFinite(lightness)) return lightness;
3586
+ }
3587
+ return null;
3588
+ }
3589
+ function runCheck() {
3590
+ if (warned) return;
3591
+ if (typeof window === "undefined" || typeof document === "undefined") return;
3592
+ const root = document.documentElement;
3593
+ const styles = window.getComputedStyle(root);
3594
+ const background = styles.getPropertyValue("--background").trim();
3595
+ if (!background) {
3596
+ warned = true;
3597
+ console.warn(
3598
+ '[@timbal-ai/timbal-react] No `--background` CSS variable found on `<html>`. Did you `import "@timbal-ai/timbal-react/styles.css"` in your app entry? Components rely on semantic tokens (bg-background, text-foreground, \u2026) and will fall back to browser defaults.'
3599
+ );
3600
+ return;
3601
+ }
3602
+ const luminance = parseLuminance(background);
3603
+ if (luminance === null) return;
3604
+ const hasDarkClass = root.classList.contains("dark");
3605
+ const looksDark = luminance < 0.5;
3606
+ if (hasDarkClass !== looksDark) {
3607
+ warned = true;
3608
+ console.warn(
3609
+ `[@timbal-ai/timbal-react] Theme mismatch detected. \`<html>\` has${hasDarkClass ? "" : " no"} \`.dark\` class but the resolved \`--background\` is ${looksDark ? "dark" : "light"} (${background}). This usually means the consumer's CSS overrides \`--background\` only in one mode. Import \`@timbal-ai/timbal-react/styles.css\` for a complete light + dark token set, or declare matching \`:root\` AND \`.dark\` blocks in your own CSS.`
3610
+ );
3611
+ }
3612
+ }
3613
+ function scheduleThemeSanityCheck() {
3614
+ if (scheduled) return;
3615
+ if (!isDev()) return;
3616
+ if (typeof window === "undefined") return;
3617
+ scheduled = true;
3618
+ if (typeof queueMicrotask === "function") {
3619
+ queueMicrotask(() => setTimeout(runCheck, 0));
3620
+ } else {
3621
+ setTimeout(runCheck, 0);
3622
+ }
3080
3623
  }
3081
3624
 
3082
3625
  // src/components/thread.tsx
3083
- import {
3084
- ActionBarMorePrimitive,
3085
- ActionBarPrimitive,
3086
- AuiIf as AuiIf2,
3087
- ComposerPrimitive as ComposerPrimitive3,
3088
- ErrorPrimitive,
3089
- MessagePrimitive as MessagePrimitive2,
3090
- ThreadPrimitive
3091
- } from "@assistant-ui/react";
3092
- import {
3093
- ArrowDownIcon,
3094
- CheckIcon as CheckIcon3,
3095
- CopyIcon as CopyIcon2,
3096
- DownloadIcon,
3097
- MoreHorizontalIcon,
3098
- PencilIcon,
3099
- RefreshCwIcon
3100
- } from "lucide-react";
3101
- import { jsx as jsx25, jsxs as jsxs14 } from "react/jsx-runtime";
3626
+ import { jsx as jsx27, jsxs as jsxs15 } from "react/jsx-runtime";
3102
3627
  var Thread = ({
3103
3628
  className,
3104
3629
  maxWidth = "44rem",
@@ -3116,13 +3641,16 @@ var Thread = ({
3116
3641
  const EditComposerSlot = components?.EditComposer ?? EditComposer;
3117
3642
  const ScrollToBottomSlot = components?.ScrollToBottom ?? ThreadScrollToBottom;
3118
3643
  const SuggestionsSlot = components?.Suggestions ?? Suggestions;
3119
- return /* @__PURE__ */ jsx25(
3644
+ useEffect5(() => {
3645
+ scheduleThemeSanityCheck();
3646
+ }, []);
3647
+ return /* @__PURE__ */ jsx27(
3120
3648
  ArtifactRegistryProvider,
3121
3649
  {
3122
3650
  renderers: artifacts?.renderers,
3123
3651
  override: artifacts?.override,
3124
- children: /* @__PURE__ */ jsx25(UiEventProvider, { onEvent: onArtifactEvent ?? (() => {
3125
- }), children: /* @__PURE__ */ jsx25(
3652
+ children: /* @__PURE__ */ jsx27(UiEventProvider, { onEvent: onArtifactEvent ?? (() => {
3653
+ }), children: /* @__PURE__ */ jsx27(
3126
3654
  ThreadPrimitive.Root,
3127
3655
  {
3128
3656
  className: cn(
@@ -3130,13 +3658,13 @@ var Thread = ({
3130
3658
  className
3131
3659
  ),
3132
3660
  style: { ["--thread-max-width"]: maxWidth },
3133
- children: /* @__PURE__ */ jsxs14(
3661
+ children: /* @__PURE__ */ jsxs15(
3134
3662
  ThreadPrimitive.Viewport,
3135
3663
  {
3136
3664
  turnAnchor: "bottom",
3137
3665
  className: "aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll px-4 pt-4",
3138
3666
  children: [
3139
- /* @__PURE__ */ jsx25(
3667
+ /* @__PURE__ */ jsx27(
3140
3668
  WelcomeSlot,
3141
3669
  {
3142
3670
  config: welcome,
@@ -3144,7 +3672,7 @@ var Thread = ({
3144
3672
  Suggestions: SuggestionsSlot
3145
3673
  }
3146
3674
  ),
3147
- /* @__PURE__ */ jsx25(
3675
+ /* @__PURE__ */ jsx27(
3148
3676
  ThreadPrimitive.Messages,
3149
3677
  {
3150
3678
  components: {
@@ -3154,9 +3682,9 @@ var Thread = ({
3154
3682
  }
3155
3683
  }
3156
3684
  ),
3157
- /* @__PURE__ */ jsxs14(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: [
3158
- /* @__PURE__ */ jsx25(ScrollToBottomSlot, {}),
3159
- /* @__PURE__ */ jsx25(ComposerSlot, { placeholder: composerPlaceholder })
3685
+ /* @__PURE__ */ jsxs15(ThreadPrimitive.ViewportFooter, { className: "aui-thread-viewport-footer sticky bottom-0 mx-auto mt-auto flex w-full max-w-(--thread-max-width) flex-col gap-4 overflow-visible bg-transparent pb-4 md:pb-6", children: [
3686
+ /* @__PURE__ */ jsx27(ScrollToBottomSlot, {}),
3687
+ /* @__PURE__ */ jsx27(ComposerSlot, { placeholder: composerPlaceholder })
3160
3688
  ] })
3161
3689
  ]
3162
3690
  }
@@ -3167,105 +3695,164 @@ var Thread = ({
3167
3695
  );
3168
3696
  };
3169
3697
  var ThreadScrollToBottom = () => {
3170
- return /* @__PURE__ */ jsx25(ThreadPrimitive.ScrollToBottom, { asChild: true, children: /* @__PURE__ */ jsx25(
3698
+ return /* @__PURE__ */ jsx27(ThreadPrimitive.ScrollToBottom, { asChild: true, children: /* @__PURE__ */ jsx27(
3171
3699
  TooltipIconButton,
3172
3700
  {
3173
3701
  tooltip: "Scroll to bottom",
3174
- variant: "outline",
3175
- className: "aui-thread-scroll-to-bottom absolute -top-12 z-10 self-center rounded-full p-4 disabled:invisible dark:bg-background dark:hover:bg-accent",
3176
- children: /* @__PURE__ */ jsx25(ArrowDownIcon, {})
3702
+ variant: "secondary",
3703
+ className: "aui-thread-scroll-to-bottom absolute -top-12 z-10 self-center disabled:invisible",
3704
+ children: /* @__PURE__ */ jsx27(ArrowDownIcon, { className: "size-4" })
3177
3705
  }
3178
3706
  ) });
3179
3707
  };
3708
+ var welcomeStagger = {
3709
+ initial: {},
3710
+ animate: {
3711
+ transition: { staggerChildren: 0.16, delayChildren: 0.12 }
3712
+ }
3713
+ };
3714
+ var welcomeItem = {
3715
+ initial: { opacity: 0, y: 14 },
3716
+ animate: {
3717
+ opacity: 1,
3718
+ y: 0,
3719
+ transition: { duration: 0.9, ease: luxuryEase }
3720
+ }
3721
+ };
3722
+ var welcomeIcon = {
3723
+ initial: { opacity: 0, y: 10, scale: 0.96 },
3724
+ animate: {
3725
+ opacity: 1,
3726
+ y: 0,
3727
+ scale: 1,
3728
+ transition: { duration: 1.1, ease: luxuryEase }
3729
+ }
3730
+ };
3180
3731
  var ThreadWelcome = ({
3181
3732
  config,
3182
3733
  suggestions,
3183
3734
  Suggestions: SuggestionsSlot = Suggestions
3184
3735
  }) => {
3185
- return /* @__PURE__ */ jsx25(AuiIf2, { condition: (s) => s.thread.isEmpty, children: /* @__PURE__ */ jsxs14("div", { className: "aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col", children: [
3186
- /* @__PURE__ */ jsx25("div", { className: "aui-thread-welcome-center flex w-full grow flex-col items-center justify-center", children: /* @__PURE__ */ jsxs14("div", { className: "aui-thread-welcome-message flex size-full flex-col items-center justify-center px-4 text-center", children: [
3187
- /* @__PURE__ */ jsxs14("div", { className: "fade-in animate-in fill-mode-both relative mb-6 flex size-14 items-center justify-center duration-300", children: [
3188
- /* @__PURE__ */ jsx25("div", { className: "animate-ai-ring-glow absolute inset-0 rounded-2xl bg-gradient-to-br from-primary/15 to-primary/5 ring-1 ring-primary/15" }),
3189
- /* @__PURE__ */ jsx25("div", { className: "animate-ai-pulse-ring absolute inset-0" }),
3190
- /* @__PURE__ */ jsx25(
3191
- "svg",
3192
- {
3193
- xmlns: "http://www.w3.org/2000/svg",
3194
- viewBox: "0 0 24 24",
3195
- fill: "none",
3196
- stroke: "currentColor",
3197
- strokeWidth: "1.5",
3198
- strokeLinecap: "round",
3199
- strokeLinejoin: "round",
3200
- className: "animate-ai-breathe relative size-7 text-primary/75",
3201
- children: /* @__PURE__ */ jsx25("path", { d: "M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z" })
3202
- }
3203
- )
3204
- ] }),
3205
- /* @__PURE__ */ jsx25("h1", { className: "aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in fill-mode-both font-semibold text-2xl duration-200", children: config?.heading ?? "How can I help you today?" }),
3206
- /* @__PURE__ */ jsx25("p", { className: "aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in fill-mode-both text-muted-foreground mt-2 delay-75 duration-200", children: config?.subheading ?? "Send a message to start a conversation." })
3207
- ] }) }),
3208
- suggestions && /* @__PURE__ */ jsx25(SuggestionsSlot, { suggestions })
3209
- ] }) });
3736
+ const isEmpty = useThread((s) => s.messages.length === 0);
3737
+ if (!isEmpty) return null;
3738
+ return /* @__PURE__ */ jsxs15("div", { className: "aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col", children: [
3739
+ /* @__PURE__ */ jsx27("div", { className: "aui-thread-welcome-center flex w-full grow flex-col items-center justify-center", children: /* @__PURE__ */ jsxs15(
3740
+ motion4.div,
3741
+ {
3742
+ className: "aui-thread-welcome-message flex flex-col items-center justify-center px-4 text-center",
3743
+ variants: welcomeStagger,
3744
+ initial: "initial",
3745
+ animate: "animate",
3746
+ children: [
3747
+ config?.icon && /* @__PURE__ */ jsx27(motion4.div, { variants: welcomeIcon, className: "mb-5", children: config.icon }),
3748
+ /* @__PURE__ */ jsx27(
3749
+ motion4.h1,
3750
+ {
3751
+ variants: welcomeItem,
3752
+ className: "aui-thread-welcome-message-inner font-semibold text-2xl",
3753
+ children: config?.heading ?? "How can I help you today?"
3754
+ }
3755
+ ),
3756
+ /* @__PURE__ */ jsx27(
3757
+ motion4.p,
3758
+ {
3759
+ variants: welcomeItem,
3760
+ className: "aui-thread-welcome-message-inner mt-2 text-muted-foreground",
3761
+ children: config?.subheading ?? "Send a message to start a conversation."
3762
+ }
3763
+ )
3764
+ ]
3765
+ }
3766
+ ) }),
3767
+ suggestions && /* @__PURE__ */ jsx27("div", { className: "aui-thread-welcome-suggestions mx-auto w-full max-w-(--thread-max-width) px-2", children: /* @__PURE__ */ jsx27(SuggestionsSlot, { suggestions }) })
3768
+ ] });
3210
3769
  };
3211
3770
  var MessageError = () => {
3212
- return /* @__PURE__ */ jsx25(MessagePrimitive2.Error, { children: /* @__PURE__ */ jsx25(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__ */ jsx25(ErrorPrimitive.Message, { className: "aui-message-error-message line-clamp-2" }) }) });
3771
+ return /* @__PURE__ */ jsx27(MessagePrimitive2.Error, { children: /* @__PURE__ */ jsx27(ErrorPrimitive.Root, { className: "aui-message-error-root mt-2 rounded-md border border-destructive bg-destructive/10 p-3 text-destructive text-sm", children: /* @__PURE__ */ jsx27(ErrorPrimitive.Message, { className: "aui-message-error-message line-clamp-2" }) }) });
3213
3772
  };
3214
3773
  var AssistantMessage = () => {
3215
- return /* @__PURE__ */ jsxs14(
3774
+ return /* @__PURE__ */ jsxs15(
3216
3775
  MessagePrimitive2.Root,
3217
3776
  {
3218
3777
  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",
3219
3778
  "data-role": "assistant",
3220
3779
  children: [
3221
- /* @__PURE__ */ jsxs14("div", { className: "aui-assistant-message-content wrap-break-word px-2 text-foreground leading-relaxed", children: [
3222
- /* @__PURE__ */ jsx25(
3780
+ /* @__PURE__ */ jsxs15("div", { className: "aui-assistant-message-content wrap-break-word px-2 text-foreground leading-relaxed", children: [
3781
+ /* @__PURE__ */ jsx27(
3223
3782
  MessagePrimitive2.Parts,
3224
3783
  {
3225
3784
  components: {
3226
3785
  Text: MarkdownText,
3227
- tools: { Fallback: ToolArtifactFallback }
3786
+ // `Override` (not `Fallback`) replaces the default tool renderer
3787
+ // entirely so we never fall back to the assistant-ui boilerplate.
3788
+ tools: { Override: ToolArtifactFallback }
3228
3789
  }
3229
3790
  }
3230
3791
  ),
3231
- /* @__PURE__ */ jsx25(MessageError, {})
3792
+ /* @__PURE__ */ jsx27(MessageError, {})
3232
3793
  ] }),
3233
- /* @__PURE__ */ jsx25("div", { className: "aui-assistant-message-footer mt-1 ml-2 flex", children: /* @__PURE__ */ jsx25(AssistantActionBar, {}) })
3794
+ /* @__PURE__ */ jsx27("div", { className: "aui-assistant-message-footer mt-0 ml-1 flex", children: /* @__PURE__ */ jsx27(AssistantActionBar, {}) })
3234
3795
  ]
3235
3796
  }
3236
3797
  );
3237
3798
  };
3799
+ var ASSISTANT_ACTION_ICON_CLASS = cn(
3800
+ "size-6 min-h-6 min-w-6 text-muted-foreground/45 hover:text-muted-foreground/80",
3801
+ // The v2 fill span sits inside `group/tbv2 > span:first-child`. We mute it
3802
+ // here so action-bar buttons read as subtle icons rather than full pills.
3803
+ "[&>span:first-child]:bg-transparent",
3804
+ "[&>span:first-child]:group-hover/tbv2:bg-muted/70"
3805
+ );
3238
3806
  var AssistantActionBar = () => {
3239
- return /* @__PURE__ */ jsxs14(
3807
+ return /* @__PURE__ */ jsxs15(
3240
3808
  ActionBarPrimitive.Root,
3241
3809
  {
3242
3810
  hideWhenRunning: true,
3243
3811
  autohide: "not-last",
3244
- autohideFloat: "single-branch",
3245
- className: "aui-assistant-action-bar-root col-start-3 row-start-2 -ml-1 flex gap-1 text-muted-foreground data-floating:absolute data-floating:rounded-md data-floating:border data-floating:bg-background data-floating:p-1 data-floating:shadow-sm",
3812
+ className: "aui-assistant-action-bar-root flex items-center gap-0 bg-transparent px-0 py-0.5 text-muted-foreground/60",
3246
3813
  children: [
3247
- /* @__PURE__ */ jsx25(ActionBarPrimitive.Copy, { asChild: true, children: /* @__PURE__ */ jsxs14(TooltipIconButton, { tooltip: "Copy", children: [
3248
- /* @__PURE__ */ jsx25(AuiIf2, { condition: (s) => s.message.isCopied, children: /* @__PURE__ */ jsx25(CheckIcon3, {}) }),
3249
- /* @__PURE__ */ jsx25(AuiIf2, { condition: (s) => !s.message.isCopied, children: /* @__PURE__ */ jsx25(CopyIcon2, {}) })
3250
- ] }) }),
3251
- /* @__PURE__ */ jsx25(ActionBarPrimitive.Reload, { asChild: true, children: /* @__PURE__ */ jsx25(TooltipIconButton, { tooltip: "Refresh", children: /* @__PURE__ */ jsx25(RefreshCwIcon, {}) }) }),
3252
- /* @__PURE__ */ jsxs14(ActionBarMorePrimitive.Root, { children: [
3253
- /* @__PURE__ */ jsx25(ActionBarMorePrimitive.Trigger, { asChild: true, children: /* @__PURE__ */ jsx25(
3814
+ /* @__PURE__ */ jsx27(ActionBarPrimitive.Copy, { asChild: true, children: /* @__PURE__ */ jsxs15(
3815
+ TooltipIconButton,
3816
+ {
3817
+ tooltip: "Copy",
3818
+ variant: "ghost",
3819
+ className: ASSISTANT_ACTION_ICON_CLASS,
3820
+ children: [
3821
+ /* @__PURE__ */ jsx27(AuiIf2, { condition: (s) => s.message.isCopied, children: /* @__PURE__ */ jsx27(CheckIcon3, { className: "size-3" }) }),
3822
+ /* @__PURE__ */ jsx27(AuiIf2, { condition: (s) => !s.message.isCopied, children: /* @__PURE__ */ jsx27(CopyIcon2, { className: "size-3" }) })
3823
+ ]
3824
+ }
3825
+ ) }),
3826
+ /* @__PURE__ */ jsx27(ActionBarPrimitive.Reload, { asChild: true, children: /* @__PURE__ */ jsx27(
3827
+ TooltipIconButton,
3828
+ {
3829
+ tooltip: "Regenerate",
3830
+ variant: "ghost",
3831
+ className: ASSISTANT_ACTION_ICON_CLASS,
3832
+ children: /* @__PURE__ */ jsx27(RefreshCwIcon, { className: "size-3" })
3833
+ }
3834
+ ) }),
3835
+ /* @__PURE__ */ jsxs15(ActionBarMorePrimitive.Root, { children: [
3836
+ /* @__PURE__ */ jsx27(ActionBarMorePrimitive.Trigger, { asChild: true, children: /* @__PURE__ */ jsx27(
3254
3837
  TooltipIconButton,
3255
3838
  {
3256
3839
  tooltip: "More",
3257
- className: "data-[state=open]:bg-accent",
3258
- children: /* @__PURE__ */ jsx25(MoreHorizontalIcon, {})
3840
+ variant: "ghost",
3841
+ className: cn(
3842
+ ASSISTANT_ACTION_ICON_CLASS,
3843
+ "data-[state=open]:text-muted-foreground/80"
3844
+ ),
3845
+ children: /* @__PURE__ */ jsx27(MoreHorizontalIcon, { className: "size-3" })
3259
3846
  }
3260
3847
  ) }),
3261
- /* @__PURE__ */ jsx25(
3848
+ /* @__PURE__ */ jsx27(
3262
3849
  ActionBarMorePrimitive.Content,
3263
3850
  {
3264
3851
  side: "bottom",
3265
3852
  align: "start",
3266
- className: "aui-action-bar-more-content z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
3267
- children: /* @__PURE__ */ jsx25(ActionBarPrimitive.ExportMarkdown, { asChild: true, children: /* @__PURE__ */ jsxs14(ActionBarMorePrimitive.Item, { className: "aui-action-bar-more-item flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground", children: [
3268
- /* @__PURE__ */ jsx25(DownloadIcon, { className: "size-4" }),
3853
+ className: "aui-action-bar-more-content z-50 min-w-36 overflow-hidden rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-card-elevated",
3854
+ children: /* @__PURE__ */ jsx27(ActionBarPrimitive.ExportMarkdown, { asChild: true, children: /* @__PURE__ */ jsxs15(ActionBarMorePrimitive.Item, { className: "aui-action-bar-more-item flex cursor-pointer select-none items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none hover:bg-muted focus:bg-muted", children: [
3855
+ /* @__PURE__ */ jsx27(DownloadIcon, { className: "size-4 shrink-0" }),
3269
3856
  "Export as Markdown"
3270
3857
  ] }) })
3271
3858
  }
@@ -3275,51 +3862,71 @@ var AssistantActionBar = () => {
3275
3862
  }
3276
3863
  );
3277
3864
  };
3865
+ var UserMessageText = () => {
3866
+ return /* @__PURE__ */ jsx27("span", { className: "whitespace-pre-wrap", children: /* @__PURE__ */ jsx27(MessagePartPrimitive.Text, { smooth: false }) });
3867
+ };
3278
3868
  var UserMessage = () => {
3279
- return /* @__PURE__ */ jsxs14(
3869
+ return /* @__PURE__ */ jsxs15(
3280
3870
  MessagePrimitive2.Root,
3281
3871
  {
3282
- className: "aui-user-message-root fade-in slide-in-from-bottom-1 mx-auto grid w-full max-w-(--thread-max-width) animate-in auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] content-start gap-y-2 px-2 py-3 duration-150 [&:where(>*)]:col-start-2",
3872
+ 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",
3283
3873
  "data-role": "user",
3284
3874
  children: [
3285
- /* @__PURE__ */ jsx25(UserMessageAttachments, {}),
3286
- /* @__PURE__ */ jsxs14("div", { className: "aui-user-message-content-wrapper relative col-start-2 min-w-0", children: [
3287
- /* @__PURE__ */ jsx25("div", { className: "aui-user-message-content wrap-break-word rounded-2xl bg-muted px-4 py-2.5 text-foreground", children: /* @__PURE__ */ jsx25(MessagePrimitive2.Parts, {}) }),
3288
- /* @__PURE__ */ jsx25("div", { className: "aui-user-action-bar-wrapper absolute top-1/2 left-0 -translate-x-full -translate-y-1/2 pr-2", children: /* @__PURE__ */ jsx25(UserActionBar, {}) })
3289
- ] })
3875
+ /* @__PURE__ */ jsx27(UserMessageAttachments, {}),
3876
+ /* @__PURE__ */ jsxs15(
3877
+ motion4.div,
3878
+ {
3879
+ className: "aui-user-message-content relative inline-block max-w-[80%] rounded-2xl bg-bubble-user px-4 py-2.5 text-bubble-user-foreground",
3880
+ initial: { opacity: 0, y: 8, scale: 0.99 },
3881
+ animate: { opacity: 1, y: 0, scale: 1 },
3882
+ transition: { duration: 0.65, ease: luxuryEase },
3883
+ children: [
3884
+ /* @__PURE__ */ jsx27(MessagePrimitive2.Parts, { components: { Text: UserMessageText } }),
3885
+ /* @__PURE__ */ jsx27("div", { className: "aui-user-action-bar-wrapper absolute top-1/2 left-0 -translate-x-full -translate-y-1/2 pr-2", children: /* @__PURE__ */ jsx27(UserActionBar, {}) })
3886
+ ]
3887
+ }
3888
+ )
3290
3889
  ]
3291
3890
  }
3292
3891
  );
3293
3892
  };
3294
3893
  var UserActionBar = () => {
3295
- return /* @__PURE__ */ jsx25(
3894
+ return /* @__PURE__ */ jsx27(
3296
3895
  ActionBarPrimitive.Root,
3297
3896
  {
3298
3897
  hideWhenRunning: true,
3299
3898
  autohide: "not-last",
3300
3899
  className: "aui-user-action-bar-root flex flex-col items-end",
3301
- children: /* @__PURE__ */ jsx25(ActionBarPrimitive.Edit, { asChild: true, children: /* @__PURE__ */ jsx25(TooltipIconButton, { tooltip: "Edit", className: "aui-user-action-edit p-4", children: /* @__PURE__ */ jsx25(PencilIcon, {}) }) })
3900
+ children: /* @__PURE__ */ jsx27(ActionBarPrimitive.Edit, { asChild: true, children: /* @__PURE__ */ jsx27(
3901
+ TooltipIconButton,
3902
+ {
3903
+ tooltip: "Edit",
3904
+ variant: "ghost",
3905
+ className: ASSISTANT_ACTION_ICON_CLASS,
3906
+ children: /* @__PURE__ */ jsx27(PencilIcon, { className: "size-3" })
3907
+ }
3908
+ ) })
3302
3909
  }
3303
3910
  );
3304
3911
  };
3305
3912
  var EditComposer = () => {
3306
- return /* @__PURE__ */ jsx25(MessagePrimitive2.Root, { className: "aui-edit-composer-wrapper mx-auto flex w-full max-w-(--thread-max-width) flex-col px-2 py-3", children: /* @__PURE__ */ jsxs14(ComposerPrimitive3.Root, { className: "aui-edit-composer-root ml-auto flex w-full max-w-[85%] flex-col rounded-2xl bg-muted", children: [
3307
- /* @__PURE__ */ jsx25(
3913
+ return /* @__PURE__ */ jsx27(MessagePrimitive2.Root, { className: "aui-edit-composer-wrapper mx-auto flex w-full max-w-(--thread-max-width) flex-col px-2 py-3", children: /* @__PURE__ */ jsxs15(ComposerPrimitive3.Root, { className: "aui-edit-composer-root ml-auto flex w-full max-w-[85%] flex-col rounded-2xl bg-muted", children: [
3914
+ /* @__PURE__ */ jsx27(
3308
3915
  ComposerPrimitive3.Input,
3309
3916
  {
3310
3917
  className: "aui-edit-composer-input min-h-14 w-full resize-none bg-transparent p-4 text-foreground text-sm outline-none",
3311
3918
  autoFocus: true
3312
3919
  }
3313
3920
  ),
3314
- /* @__PURE__ */ jsxs14("div", { className: "aui-edit-composer-footer mx-3 mb-3 flex items-center gap-2 self-end", children: [
3315
- /* @__PURE__ */ jsx25(ComposerPrimitive3.Cancel, { asChild: true, children: /* @__PURE__ */ jsx25(Button, { variant: "ghost", size: "sm", children: "Cancel" }) }),
3316
- /* @__PURE__ */ jsx25(ComposerPrimitive3.Send, { asChild: true, children: /* @__PURE__ */ jsx25(Button, { size: "sm", children: "Update" }) })
3921
+ /* @__PURE__ */ jsxs15("div", { className: "aui-edit-composer-footer mx-3 mb-3 flex items-center gap-2 self-end", children: [
3922
+ /* @__PURE__ */ jsx27(ComposerPrimitive3.Cancel, { asChild: true, children: /* @__PURE__ */ jsx27(TimbalV2Button, { variant: "ghost", size: "sm", children: "Cancel" }) }),
3923
+ /* @__PURE__ */ jsx27(ComposerPrimitive3.Send, { asChild: true, children: /* @__PURE__ */ jsx27(TimbalV2Button, { variant: "primary", size: "sm", children: "Update" }) })
3317
3924
  ] })
3318
3925
  ] }) });
3319
3926
  };
3320
3927
 
3321
3928
  // src/components/chat.tsx
3322
- import { jsx as jsx26 } from "react/jsx-runtime";
3929
+ import { jsx as jsx28 } from "react/jsx-runtime";
3323
3930
  function TimbalChat({
3324
3931
  workforceId,
3325
3932
  baseUrl,
@@ -3330,7 +3937,7 @@ function TimbalChat({
3330
3937
  debug,
3331
3938
  ...threadProps
3332
3939
  }) {
3333
- return /* @__PURE__ */ jsx26(
3940
+ return /* @__PURE__ */ jsx28(
3334
3941
  TimbalRuntimeProvider,
3335
3942
  {
3336
3943
  workforceId,
@@ -3340,168 +3947,88 @@ function TimbalChat({
3340
3947
  attachmentsUploadUrl,
3341
3948
  attachmentsAccept,
3342
3949
  debug,
3343
- children: /* @__PURE__ */ jsx26(Thread, { ...threadProps })
3950
+ children: /* @__PURE__ */ jsx28(Thread, { ...threadProps })
3344
3951
  }
3345
3952
  );
3346
3953
  }
3347
3954
 
3348
- // src/artifacts/agent-instructions.ts
3349
- var ARTIFACT_AGENT_INSTRUCTIONS = `
3350
- ## Rich artifacts (Timbal chat UI)
3351
-
3352
- 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.
3353
-
3354
- ### Delivery channels (either works)
3355
-
3356
- 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.
3357
- 2. **Inline markdown fence** \u2014 embed the same JSON inside a fenced block:
3358
-
3359
- \`\`\`timbal-artifact
3360
- {"type":"chart","data":[{"month":"Jan","sales":120}]}
3361
- \`\`\`
3362
-
3363
- The alias \`\`\`timbal\`\`\` is also accepted.
3364
-
3365
- ### Built-in artifact types
3366
-
3367
- | \`type\` | Use for |
3368
- |---|---|
3369
- | \`chart\` | Bar, line, area, or pie charts. Fields: \`data\`, optional \`chartType\`, \`xKey\`, \`dataKey\`, \`title\`, \`unit\`. |
3370
- | \`table\` | Tabular data. Fields: \`rows\`, optional \`columns\`, \`title\`. |
3371
- | \`question\` | In-thread multiple choice. Fields: \`options: [{ id, label, description? }]\`, optional \`prompt\`, \`multi\`. User replies are sent back as a normal user message. |
3372
- | \`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). |
3373
- | \`json\` | Fallback structured view. Fields: \`data\`, optional \`title\`. |
3374
- | \`ui\` | **Interactive UI** composed from a fixed node palette (hover, click, drag). See below. |
3375
-
3376
- ### When to use \`type: "html"\`
3377
-
3378
- 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\`.
3379
-
3380
- - Inline \`<style>\`, \`<script>\`, SVG, and canvas are supported.
3381
- - Default \`sandboxed: true\` runs in an isolated iframe with scripts enabled.
3382
- - Set \`sandboxed: false\` only for trusted content that needs external CDN scripts/styles or full DOM freedom.
3383
- - Prefer \`ui\` when controls should send chat messages or host events; prefer \`html\` for self-contained mini-apps and visual demos.
3384
-
3385
- ### When to use \`type: "ui"\`
3386
-
3387
- 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.
3388
-
3389
- Each \`ui\` artifact has:
3390
-
3391
- - \`initialState\` \u2014 optional object seeding local state (per widget instance).
3392
- - \`root\` \u2014 a single node tree (see node kinds below).
3393
- - optional \`title\` \u2014 card heading.
3394
-
3395
- **Bindings:** anywhere a primitive is accepted, you may use \`{ "$bind": "dotted.path" }\` to read from \`initialState\` / local state (e.g. \`{ "$bind": "qty" }\`).
3396
-
3397
- **Actions:** nodes may attach \`onClick\`, \`onChange\`, or \`onDragEnd\` with one action or an array:
3398
-
3399
- | Action | Shape | Effect |
3400
- |---|---|---|
3401
- | User message | \`{ "kind": "message", "text": "..." }\` or \`{ "kind": "message", "text": { "$bind": "path" } }\` | Sends text as the next user message. |
3402
- | Set state | \`{ "kind": "set", "path": "foo", "value": 1 }\` | Writes local widget state. |
3403
- | Toggle boolean | \`{ "kind": "toggle", "path": "enabled" }\` | Flips a boolean at \`path\`. |
3404
- | Host event | \`{ "kind": "emit", "name": "event-name", "payload": { ... } }\` | Bubbles to the host app (\`onArtifactEvent\` on \`<Thread>\`). |
3405
-
3406
- ### \`ui\` node palette (\`root.kind\`)
3407
-
3408
- | \`kind\` | Purpose | Key fields |
3409
- |---|---|---|
3410
- | \`box\` | Layout container | \`children\`, \`direction\` (\`row\`/\`col\`), \`gap\`, \`padding\`, \`align\`, \`justify\`, \`wrap\` |
3411
- | \`text\` | Body text | \`value\`, optional \`muted\`, \`size\`, \`weight\` |
3412
- | \`heading\` | Heading | \`value\`, optional \`level\` (1\u20134) |
3413
- | \`badge\` | Pill label | \`value\`, optional \`tone\` (\`default\`, \`primary\`, \`success\`, \`warn\`, \`danger\`) |
3414
- | \`button\` | Clickable button | \`label\`, optional \`variant\`, \`size\`, \`disabled\`, \`onClick\` |
3415
- | \`toggle\` | Boolean switch | \`binding\` (state path), optional \`label\`, \`onChange\` |
3416
- | \`slider\` | Numeric range | \`binding\`, optional \`min\`, \`max\`, \`step\`, \`label\`, \`showValue\`, \`onChange\` |
3417
- | \`tooltip\` | Hover tooltip | \`content\`, \`child\` (single node), optional \`side\` |
3418
- | \`draggable\` | Drag gesture | \`child\`, optional \`axis\` (\`x\`/\`y\`/\`both\`), \`snapBack\`, \`onDragEnd\` |
3419
- | \`custom\` | Host-registered widget | \`name\`, optional \`props\`, \`children\` \u2014 only if the app registered that name |
3420
-
3421
- ### Example \`ui\` artifact
3422
-
3423
- \`\`\`json
3424
- {
3425
- "type": "ui",
3426
- "title": "Configure plan",
3427
- "initialState": { "qty": 1, "premium": false },
3428
- "root": {
3429
- "kind": "box",
3430
- "direction": "col",
3431
- "gap": 3,
3432
- "children": [
3433
- { "kind": "heading", "value": "Choose quantity", "level": 3 },
3434
- {
3435
- "kind": "tooltip",
3436
- "content": "Drag to adjust quantity",
3437
- "child": {
3438
- "kind": "slider",
3439
- "binding": "qty",
3440
- "min": 1,
3441
- "max": 50,
3442
- "label": "Quantity",
3443
- "onChange": { "kind": "emit", "name": "qty-changed" }
3444
- }
3445
- },
3446
- { "kind": "toggle", "binding": "premium", "label": "Premium support" },
3447
- {
3448
- "kind": "button",
3449
- "label": "Confirm",
3450
- "onClick": { "kind": "message", "text": { "$bind": "qty" } }
3451
- }
3452
- ]
3453
- }
3454
- }
3455
- \`\`\`
3456
-
3457
- ### Rules
3458
-
3459
- - Always set \`type\` to a built-in value above unless the app documented a custom type.
3460
- - Prefer \`ui\` over \`html\` when actions must bubble to the host chat (\`message\`, \`emit\`).
3461
- - Prefer \`question\` for simple A/B/C choices; use \`ui\` when you need sliders, toggles, drag, or multi-control layouts.
3462
- - Keep \`data\` arrays reasonably small (charts/tables).
3463
-
3464
- ### After calling an artifact tool (critical)
3465
-
3466
- When you call a tool that returns an artifact (\`make_chart\`, \`ask_question\`, \`show_table\`, \`show_html\`, \`make_ui_demo\`, etc.):
3467
-
3468
- 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.
3469
- 2. **Do not** emit a matching \`\`\`timbal-artifact\`\`\` block for the same payload \u2014 pick **one** channel (tool result only).
3470
- 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.
3471
- 4. Treat the widget as visible to the user; refer to it as "above" / "the chart" / "the choices" \u2014 never reproduce its contents.
3472
- `.trim();
3473
-
3474
- // src/index.ts
3475
- import {
3476
- ThreadPrimitive as ThreadPrimitive2,
3477
- MessagePrimitive as MessagePrimitive3,
3478
- ComposerPrimitive as ComposerPrimitive4,
3479
- ActionBarPrimitive as ActionBarPrimitive2,
3480
- AssistantRuntimeProvider as AssistantRuntimeProvider2,
3481
- useThread,
3482
- useThreadRuntime as useThreadRuntime4,
3483
- useMessageRuntime,
3484
- useComposerRuntime as useComposerRuntime2
3485
- } from "@assistant-ui/react";
3955
+ // src/components/workforce-selector.tsx
3956
+ import { ChevronDownIcon } from "lucide-react";
3957
+ import { jsx as jsx29, jsxs as jsxs16 } from "react/jsx-runtime";
3958
+ var WorkforceSelector = ({
3959
+ workforces,
3960
+ value,
3961
+ onChange,
3962
+ hideWhenSingle = true,
3963
+ className,
3964
+ placeholder = "Select agent"
3965
+ }) => {
3966
+ if (workforces.length === 0) return null;
3967
+ if (hideWhenSingle && workforces.length === 1) return null;
3968
+ return /* @__PURE__ */ jsxs16(
3969
+ "div",
3970
+ {
3971
+ className: cn(
3972
+ "aui-workforce-selector relative inline-flex items-center",
3973
+ studioTopbarPillHeightClass,
3974
+ studioSecondaryChromeClass,
3975
+ "rounded-full",
3976
+ className
3977
+ ),
3978
+ children: [
3979
+ /* @__PURE__ */ jsxs16(
3980
+ "select",
3981
+ {
3982
+ className: "aui-workforce-selector-input h-full cursor-pointer appearance-none rounded-full border-none bg-transparent pr-8 pl-3.5 text-sm font-medium text-foreground outline-none focus:outline-none",
3983
+ value,
3984
+ onChange: (e) => onChange(e.target.value),
3985
+ "aria-label": placeholder,
3986
+ children: [
3987
+ !value && /* @__PURE__ */ jsx29("option", { value: "", children: placeholder }),
3988
+ workforces.map((w) => {
3989
+ const id = idOf(w);
3990
+ return /* @__PURE__ */ jsx29("option", { value: id, children: w.name ?? id }, id);
3991
+ })
3992
+ ]
3993
+ }
3994
+ ),
3995
+ /* @__PURE__ */ jsx29(
3996
+ ChevronDownIcon,
3997
+ {
3998
+ className: "aui-workforce-selector-icon pointer-events-none absolute right-3 size-3.5 text-muted-foreground/70",
3999
+ "aria-hidden": true
4000
+ }
4001
+ )
4002
+ ]
4003
+ }
4004
+ );
4005
+ };
4006
+ function idOf(item) {
4007
+ return item.id ?? item.uid ?? item.name ?? "";
4008
+ }
3486
4009
 
3487
4010
  // src/hooks/use-workforces.ts
3488
- import { useEffect as useEffect5, useMemo as useMemo6, useRef as useRef2, useState as useState8 } from "react";
4011
+ import { useEffect as useEffect6, useMemo as useMemo7, useRef as useRef2, useState as useState8 } from "react";
3489
4012
  function useWorkforces(options = {}) {
3490
- const { baseUrl = "/api", fetch: fetchFn, pickInitial } = options;
4013
+ const { baseUrl = "/api", fetch: fetchFn, pickInitial, enabled = true } = options;
3491
4014
  const [workforces, setWorkforces] = useState8([]);
3492
4015
  const [selectedId, setSelectedId] = useState8("");
3493
- const [isLoading, setIsLoading] = useState8(true);
4016
+ const [isLoading, setIsLoading] = useState8(enabled);
3494
4017
  const [error, setError] = useState8(null);
3495
4018
  const fetchFnRef = useRef2(fetchFn ?? authFetch);
3496
- useEffect5(() => {
4019
+ useEffect6(() => {
3497
4020
  fetchFnRef.current = fetchFn ?? authFetch;
3498
4021
  }, [fetchFn]);
3499
4022
  const pickInitialRef = useRef2(pickInitial);
3500
- useEffect5(() => {
4023
+ useEffect6(() => {
3501
4024
  pickInitialRef.current = pickInitial;
3502
4025
  }, [pickInitial]);
3503
- const load = useMemo6(() => {
4026
+ const load = useMemo7(() => {
3504
4027
  return async () => {
4028
+ if (!enabled) {
4029
+ setIsLoading(false);
4030
+ return;
4031
+ }
3505
4032
  setIsLoading(true);
3506
4033
  setError(null);
3507
4034
  try {
@@ -3510,9 +4037,9 @@ function useWorkforces(options = {}) {
3510
4037
  const data = await res.json();
3511
4038
  setWorkforces(data);
3512
4039
  setSelectedId((current) => {
3513
- if (current && data.some((w) => idOf(w) === current)) return current;
4040
+ if (current && data.some((w) => idOf2(w) === current)) return current;
3514
4041
  const initial = pickInitialRef.current?.(data) ?? data.find((w) => w.type === "agent") ?? data[0];
3515
- return initial ? idOf(initial) : "";
4042
+ return initial ? idOf2(initial) : "";
3516
4043
  });
3517
4044
  } catch (err) {
3518
4045
  setError(err instanceof Error ? err : new Error(String(err)));
@@ -3520,12 +4047,12 @@ function useWorkforces(options = {}) {
3520
4047
  setIsLoading(false);
3521
4048
  }
3522
4049
  };
3523
- }, [baseUrl]);
3524
- useEffect5(() => {
4050
+ }, [baseUrl, enabled]);
4051
+ useEffect6(() => {
3525
4052
  load();
3526
4053
  }, [load]);
3527
- const selected = useMemo6(
3528
- () => workforces.find((w) => idOf(w) === selectedId),
4054
+ const selected = useMemo7(
4055
+ () => workforces.find((w) => idOf2(w) === selectedId),
3529
4056
  [workforces, selectedId]
3530
4057
  );
3531
4058
  return {
@@ -3538,49 +4065,53 @@ function useWorkforces(options = {}) {
3538
4065
  refresh: load
3539
4066
  };
3540
4067
  }
3541
- function idOf(item) {
4068
+ function idOf2(item) {
3542
4069
  return item.id ?? item.uid ?? item.name ?? "";
3543
4070
  }
3544
4071
 
3545
- // src/components/workforce-selector.tsx
3546
- import { ChevronDownIcon as ChevronDownIcon2 } from "lucide-react";
3547
- import { jsx as jsx27, jsxs as jsxs15 } from "react/jsx-runtime";
3548
- var WorkforceSelector = ({
3549
- workforces,
3550
- value,
3551
- onChange,
3552
- hideWhenSingle = true,
3553
- className,
3554
- placeholder = "Select agent"
3555
- }) => {
3556
- if (workforces.length === 0) return null;
3557
- if (hideWhenSingle && workforces.length === 1) return null;
3558
- return /* @__PURE__ */ jsxs15("div", { className: cn("aui-workforce-selector relative inline-flex items-center", className), children: [
3559
- /* @__PURE__ */ jsxs15(
3560
- "select",
3561
- {
3562
- className: "aui-workforce-selector-input h-7 cursor-pointer appearance-none rounded-md border-none bg-transparent pr-5 pl-1.5 text-xs font-medium text-muted-foreground shadow-none outline-none ring-0 transition-colors hover:text-foreground focus:ring-0",
3563
- value,
3564
- onChange: (e) => onChange(e.target.value),
3565
- "aria-label": placeholder,
3566
- children: [
3567
- !value && /* @__PURE__ */ jsx27("option", { value: "", children: placeholder }),
3568
- workforces.map((w) => {
3569
- const id = idOf2(w);
3570
- return /* @__PURE__ */ jsx27("option", { value: id, children: w.name ?? id }, id);
3571
- })
3572
- ]
3573
- }
3574
- ),
3575
- /* @__PURE__ */ jsx27(ChevronDownIcon2, { className: "aui-workforce-selector-icon pointer-events-none absolute right-1 size-3 text-muted-foreground" })
3576
- ] });
4072
+ // src/design/tokens.ts
4073
+ var SIDEBAR_WIDTH_PX = 224;
4074
+ var SIDEBAR_WIDTH_COLLAPSED_PX = 52;
4075
+ var SIDEBAR_MOBILE_PX = 272;
4076
+ var SIDEBAR_GAP_PX = 12;
4077
+ var SIDEBAR_CONTENT_GAP_PX = 8;
4078
+ var TOPBAR_GAP_PX = 8;
4079
+ var TOPBAR_HEIGHT_PX = 48;
4080
+ var PILL_HEIGHT_PX = 40;
4081
+ var SIDEBAR_INSET_PX_EXPANDED = SIDEBAR_GAP_PX + SIDEBAR_WIDTH_PX + SIDEBAR_CONTENT_GAP_PX;
4082
+ var SIDEBAR_INSET_PX_COLLAPSED = SIDEBAR_GAP_PX + SIDEBAR_WIDTH_COLLAPSED_PX + SIDEBAR_CONTENT_GAP_PX;
4083
+ var px = (n) => `${n / 16}rem`;
4084
+ var SIDEBAR_WIDTH = px(SIDEBAR_WIDTH_PX);
4085
+ var SIDEBAR_WIDTH_COLLAPSED = px(SIDEBAR_WIDTH_COLLAPSED_PX);
4086
+ var SIDEBAR_GAP = px(SIDEBAR_GAP_PX);
4087
+ var SIDEBAR_CONTENT_GAP = px(SIDEBAR_CONTENT_GAP_PX);
4088
+ var TOPBAR_GAP = px(TOPBAR_GAP_PX);
4089
+ var TOPBAR_HEIGHT = px(TOPBAR_HEIGHT_PX);
4090
+ var PILL_HEIGHT = px(PILL_HEIGHT_PX);
4091
+ var SIDEBAR_INSET_EXPANDED = px(SIDEBAR_INSET_PX_EXPANDED);
4092
+ var SIDEBAR_INSET_COLLAPSED = px(SIDEBAR_INSET_PX_COLLAPSED);
4093
+ var studioChromeShellStyle = {
4094
+ "--studio-topbar-gap": TOPBAR_GAP,
4095
+ "--studio-topbar-height": TOPBAR_HEIGHT,
4096
+ "--studio-chrome-pill-height": PILL_HEIGHT,
4097
+ "--studio-inset-top": `calc(${TOPBAR_GAP} + ${TOPBAR_HEIGHT})`,
4098
+ "--studio-sidebar-gap": SIDEBAR_GAP,
4099
+ "--studio-sidebar-width": SIDEBAR_WIDTH,
4100
+ "--studio-sidebar-width-collapsed": SIDEBAR_WIDTH_COLLAPSED,
4101
+ "--studio-sidebar-content-gap": SIDEBAR_CONTENT_GAP,
4102
+ "--studio-inset-left": SIDEBAR_INSET_EXPANDED,
4103
+ "--studio-inset-left-collapsed": SIDEBAR_INSET_COLLAPSED
4104
+ };
4105
+ var STORAGE_KEYS = {
4106
+ sidebarCollapsed: "timbal-studio-sidebar-collapsed"
4107
+ };
4108
+ var DOM_IDS = {
4109
+ sidebarRuntimeAnchor: "timbal-studio-sidebar-runtime-anchor",
4110
+ topbarBrandAnchor: "timbal-studio-topbar-brand-anchor"
3577
4111
  };
3578
- function idOf2(item) {
3579
- return item.id ?? item.uid ?? item.name ?? "";
3580
- }
3581
4112
 
3582
4113
  // src/components/chat-shell.tsx
3583
- import { Fragment as Fragment3, jsx as jsx28, jsxs as jsxs16 } from "react/jsx-runtime";
4114
+ import { jsx as jsx30, jsxs as jsxs17 } from "react/jsx-runtime";
3584
4115
  var TimbalChatShell = ({
3585
4116
  workforceId,
3586
4117
  brand,
@@ -3592,54 +4123,62 @@ var TimbalChatShell = ({
3592
4123
  fetch: fetch2,
3593
4124
  ...chatProps
3594
4125
  }) => {
3595
- const {
3596
- workforces,
3597
- selectedId,
3598
- setSelectedId
3599
- } = useWorkforces({ baseUrl, fetch: fetch2 });
4126
+ const { workforces, selectedId, setSelectedId } = useWorkforces({
4127
+ baseUrl,
4128
+ fetch: fetch2
4129
+ });
3600
4130
  const effectiveId = workforceId ?? selectedId;
3601
4131
  const showSelector = !hideWorkforceSelector && !workforceId && workforces.length > 0;
3602
- return /* @__PURE__ */ jsxs16(
4132
+ return /* @__PURE__ */ jsxs17(
3603
4133
  "div",
3604
4134
  {
3605
4135
  className: cn(
3606
- "aui-chat-shell flex h-screen flex-col overflow-hidden",
4136
+ "aui-chat-shell relative flex h-dvh flex-col overflow-hidden bg-background",
3607
4137
  className
3608
4138
  ),
4139
+ style: studioChromeShellStyle,
3609
4140
  children: [
3610
- /* @__PURE__ */ jsxs16(
4141
+ /* @__PURE__ */ jsx30(
4142
+ "div",
4143
+ {
4144
+ className: cn(
4145
+ "pointer-events-none absolute inset-0 z-0",
4146
+ studioPlaygroundGradientClass
4147
+ ),
4148
+ "aria-hidden": true
4149
+ }
4150
+ ),
4151
+ /* @__PURE__ */ jsxs17(
3611
4152
  "header",
3612
4153
  {
3613
4154
  className: cn(
3614
- "aui-chat-shell-header flex shrink-0 items-center justify-between border-b border-border/50 bg-background/90 px-5 py-2 backdrop-blur-md",
4155
+ "aui-chat-shell-header relative z-10 flex shrink-0 items-center justify-between px-4 pt-[var(--studio-topbar-gap)] pb-2",
3615
4156
  headerClassName
3616
4157
  ),
4158
+ style: { minHeight: "var(--studio-topbar-height)" },
3617
4159
  children: [
3618
- /* @__PURE__ */ jsxs16("div", { className: "flex items-center", children: [
4160
+ /* @__PURE__ */ jsxs17("div", { className: "flex min-w-0 items-center gap-2", children: [
3619
4161
  brand,
3620
- showSelector && /* @__PURE__ */ jsxs16(Fragment3, { children: [
3621
- /* @__PURE__ */ jsx28("div", { className: "mx-3.5 h-3.5 w-px bg-border" }),
3622
- /* @__PURE__ */ jsx28(
3623
- WorkforceSelector,
3624
- {
3625
- workforces,
3626
- value: selectedId,
3627
- onChange: setSelectedId
3628
- }
3629
- )
3630
- ] })
4162
+ showSelector && /* @__PURE__ */ jsx30(
4163
+ WorkforceSelector,
4164
+ {
4165
+ workforces,
4166
+ value: selectedId,
4167
+ onChange: setSelectedId
4168
+ }
4169
+ )
3631
4170
  ] }),
3632
- /* @__PURE__ */ jsx28("div", { className: "flex items-center gap-0.5", children: headerActions })
4171
+ /* @__PURE__ */ jsx30("div", { className: "flex shrink-0 items-center gap-1", children: headerActions })
3633
4172
  ]
3634
4173
  }
3635
4174
  ),
3636
- /* @__PURE__ */ jsx28(
4175
+ /* @__PURE__ */ jsx30(
3637
4176
  TimbalChat,
3638
4177
  {
3639
4178
  workforceId: effectiveId,
3640
4179
  baseUrl,
3641
4180
  fetch: fetch2,
3642
- className: "min-h-0 flex-1",
4181
+ className: "relative z-10 min-h-0 flex-1 bg-transparent",
3643
4182
  ...chatProps
3644
4183
  },
3645
4184
  effectiveId
@@ -3649,15 +4188,250 @@ var TimbalChatShell = ({
3649
4188
  );
3650
4189
  };
3651
4190
 
4191
+ // src/components/studio/studio-shell.tsx
4192
+ import {
4193
+ useCallback as useCallback8,
4194
+ useEffect as useEffect10,
4195
+ useMemo as useMemo9,
4196
+ useState as useState13
4197
+ } from "react";
4198
+ import { Menu } from "lucide-react";
4199
+ import { motion as motion10, useReducedMotion as useReducedMotion6 } from "motion/react";
4200
+
4201
+ // src/design/sidebar-motion.ts
4202
+ var STUDIO_SIDEBAR_EASE_ENTER = [0, 0, 0.2, 1];
4203
+ var STUDIO_SIDEBAR_EASE_EXIT = [0.4, 0, 1, 1];
4204
+ var STUDIO_SIDEBAR_EASE = [0.16, 1, 0.3, 1];
4205
+ var STUDIO_SIDEBAR_ENTRIES_OUT_S = 0.1;
4206
+ var STUDIO_SIDEBAR_WIDTH_S = 0.17;
4207
+ var STUDIO_SIDEBAR_ENTRY_ITEM_IN_S = 0.18;
4208
+ var STUDIO_SIDEBAR_STAGGER_S = 0.03;
4209
+ var STUDIO_SIDEBAR_EXPAND_REVEAL_FRAC = 0.5;
4210
+ var STUDIO_SIDEBAR_CONTENT_NUDGE_PX = 6;
4211
+ var studioSidebarEntriesContainerVariants = {
4212
+ hidden: {
4213
+ opacity: 0,
4214
+ transition: {
4215
+ duration: STUDIO_SIDEBAR_ENTRIES_OUT_S,
4216
+ ease: STUDIO_SIDEBAR_EASE_EXIT,
4217
+ staggerChildren: 0
4218
+ }
4219
+ },
4220
+ visible: {
4221
+ opacity: 1,
4222
+ transition: {
4223
+ duration: 0.06,
4224
+ ease: STUDIO_SIDEBAR_EASE_ENTER,
4225
+ staggerChildren: STUDIO_SIDEBAR_STAGGER_S,
4226
+ delayChildren: 0.02
4227
+ }
4228
+ }
4229
+ };
4230
+ var studioSidebarEntryItemVariants = {
4231
+ hidden: {
4232
+ opacity: 0,
4233
+ x: -STUDIO_SIDEBAR_CONTENT_NUDGE_PX,
4234
+ scale: 0.99
4235
+ },
4236
+ visible: {
4237
+ opacity: 1,
4238
+ x: 0,
4239
+ scale: 1,
4240
+ transition: {
4241
+ duration: STUDIO_SIDEBAR_ENTRY_ITEM_IN_S,
4242
+ ease: STUDIO_SIDEBAR_EASE_ENTER
4243
+ }
4244
+ }
4245
+ };
4246
+ function studioSidebarEntriesTransition(visible, reduced) {
4247
+ if (reduced) return { duration: 0.01 };
4248
+ return visible ? { duration: 0.06, ease: STUDIO_SIDEBAR_EASE_ENTER } : { duration: STUDIO_SIDEBAR_ENTRIES_OUT_S, ease: STUDIO_SIDEBAR_EASE_EXIT };
4249
+ }
4250
+ function studioSidebarWidthTransition(reduced, direction = "collapse") {
4251
+ if (reduced) return { duration: 0.01 };
4252
+ return {
4253
+ duration: direction === "expand" ? STUDIO_SIDEBAR_WIDTH_S : STUDIO_SIDEBAR_WIDTH_S * 0.94,
4254
+ ease: direction === "expand" ? STUDIO_SIDEBAR_EASE_ENTER : STUDIO_SIDEBAR_EASE_EXIT
4255
+ };
4256
+ }
4257
+ function studioSidebarDrawerTransition(reduced) {
4258
+ if (reduced) return { duration: 0.01 };
4259
+ return {
4260
+ duration: 0.22,
4261
+ ease: STUDIO_SIDEBAR_EASE
4262
+ };
4263
+ }
4264
+ function studioSidebarBackdropTransition(reduced) {
4265
+ if (reduced) return { duration: 0.01 };
4266
+ return { duration: 0.16, ease: STUDIO_SIDEBAR_EASE_EXIT };
4267
+ }
4268
+
4269
+ // src/hooks/use-sidebar-collapse-phase.ts
4270
+ import { useCallback as useCallback4, useEffect as useEffect7, useRef as useRef3, useState as useState9 } from "react";
4271
+ var WIDTH_OVERLAP_FRAC = 0.7;
4272
+ function useSidebarCollapsePhase(collapsed, reducedMotion) {
4273
+ const [widthCollapsed, setWidthCollapsed] = useState9(collapsed);
4274
+ const [entriesVisible, setEntriesVisible] = useState9(true);
4275
+ const collapsedTarget = useRef3(collapsed);
4276
+ const isFirstRender = useRef3(true);
4277
+ const widthTimerRef = useRef3(null);
4278
+ const revealTimerRef = useRef3(null);
4279
+ useEffect7(() => {
4280
+ collapsedTarget.current = collapsed;
4281
+ }, [collapsed]);
4282
+ const clearWidthTimer = () => {
4283
+ if (widthTimerRef.current !== null) {
4284
+ clearTimeout(widthTimerRef.current);
4285
+ widthTimerRef.current = null;
4286
+ }
4287
+ };
4288
+ const clearRevealTimer = () => {
4289
+ if (revealTimerRef.current !== null) {
4290
+ clearTimeout(revealTimerRef.current);
4291
+ revealTimerRef.current = null;
4292
+ }
4293
+ };
4294
+ const applyWidthTarget = useCallback4(() => {
4295
+ const willExpand = !collapsedTarget.current;
4296
+ setWidthCollapsed(collapsedTarget.current);
4297
+ clearRevealTimer();
4298
+ if (willExpand && !reducedMotion) {
4299
+ revealTimerRef.current = setTimeout(
4300
+ () => setEntriesVisible(true),
4301
+ STUDIO_SIDEBAR_WIDTH_S * 1e3 * STUDIO_SIDEBAR_EXPAND_REVEAL_FRAC
4302
+ );
4303
+ }
4304
+ }, [reducedMotion]);
4305
+ useEffect7(() => {
4306
+ clearWidthTimer();
4307
+ clearRevealTimer();
4308
+ if (reducedMotion) {
4309
+ setWidthCollapsed(collapsed);
4310
+ setEntriesVisible(true);
4311
+ return;
4312
+ }
4313
+ if (isFirstRender.current) {
4314
+ isFirstRender.current = false;
4315
+ setWidthCollapsed(collapsed);
4316
+ setEntriesVisible(true);
4317
+ return;
4318
+ }
4319
+ setEntriesVisible(false);
4320
+ widthTimerRef.current = setTimeout(
4321
+ applyWidthTarget,
4322
+ STUDIO_SIDEBAR_ENTRIES_OUT_S * 1e3 * WIDTH_OVERLAP_FRAC
4323
+ );
4324
+ return () => {
4325
+ clearWidthTimer();
4326
+ clearRevealTimer();
4327
+ };
4328
+ }, [collapsed, reducedMotion, applyWidthTarget]);
4329
+ const onEntriesBlurOutComplete = useCallback4(() => {
4330
+ applyWidthTarget();
4331
+ }, [applyWidthTarget]);
4332
+ const onPanelWidthComplete = useCallback4(() => {
4333
+ clearRevealTimer();
4334
+ setEntriesVisible(true);
4335
+ }, []);
4336
+ const isCollapsedRail = widthCollapsed;
4337
+ return {
4338
+ widthCollapsed,
4339
+ isCollapsedRail,
4340
+ entriesVisible,
4341
+ onEntriesBlurOutComplete,
4342
+ onPanelWidthComplete
4343
+ };
4344
+ }
4345
+
4346
+ // src/components/studio/sidebar-backdrop.tsx
4347
+ import { AnimatePresence as AnimatePresence2, motion as motion5, useReducedMotion as useReducedMotion2 } from "motion/react";
4348
+ import { jsx as jsx31 } from "react/jsx-runtime";
4349
+ var StudioSidebarBackdrop = ({
4350
+ open,
4351
+ onClose
4352
+ }) => {
4353
+ const reducedMotion = useReducedMotion2();
4354
+ return /* @__PURE__ */ jsx31(AnimatePresence2, { children: open ? /* @__PURE__ */ jsx31(
4355
+ motion5.button,
4356
+ {
4357
+ type: "button",
4358
+ className: "fixed inset-0 z-40 bg-foreground/30 backdrop-blur-[2px] md:hidden",
4359
+ "aria-label": "Close menu",
4360
+ initial: { opacity: 0 },
4361
+ animate: { opacity: 1 },
4362
+ exit: { opacity: 0 },
4363
+ transition: studioSidebarBackdropTransition(!!reducedMotion),
4364
+ onClick: onClose
4365
+ }
4366
+ ) : null });
4367
+ };
4368
+
4369
+ // src/components/studio/sidebar-context.tsx
4370
+ import { createContext as createContext4, useContext as useContext4 } from "react";
4371
+ var StudioSidebarContext = createContext4({
4372
+ collapsed: false,
4373
+ isMobile: false,
4374
+ isCollapsedRail: false,
4375
+ iconOnlyLayout: false
4376
+ });
4377
+ function useStudioSidebarLayout() {
4378
+ return useContext4(StudioSidebarContext);
4379
+ }
4380
+
4381
+ // src/components/studio/sidebar.tsx
4382
+ import {
4383
+ useCallback as useCallback6,
4384
+ useEffect as useEffect9,
4385
+ useMemo as useMemo8,
4386
+ useState as useState11
4387
+ } from "react";
4388
+ import { motion as motion8, useReducedMotion as useReducedMotion5 } from "motion/react";
4389
+
4390
+ // src/components/studio/sidebar-entries.tsx
4391
+ import { motion as motion6, useReducedMotion as useReducedMotion3 } from "motion/react";
4392
+ import { jsx as jsx32 } from "react/jsx-runtime";
4393
+ var StudioSidebarEntries = ({
4394
+ visible,
4395
+ onBlurOutComplete,
4396
+ children,
4397
+ className
4398
+ }) => {
4399
+ const reducedMotion = useReducedMotion3();
4400
+ if (reducedMotion) {
4401
+ return visible ? /* @__PURE__ */ jsx32("div", { className: cn("flex min-h-0 flex-1 flex-col", className), children }) : null;
4402
+ }
4403
+ return /* @__PURE__ */ jsx32(
4404
+ motion6.div,
4405
+ {
4406
+ className: cn("flex min-h-0 flex-1 flex-col", className),
4407
+ initial: false,
4408
+ variants: studioSidebarEntriesContainerVariants,
4409
+ animate: visible ? "visible" : "hidden",
4410
+ transition: studioSidebarEntriesTransition(visible, false),
4411
+ onAnimationComplete: (definition) => {
4412
+ if (definition === "hidden") {
4413
+ onBlurOutComplete();
4414
+ }
4415
+ },
4416
+ style: { pointerEvents: visible ? "auto" : "none" },
4417
+ "aria-hidden": !visible,
4418
+ children
4419
+ }
4420
+ );
4421
+ };
4422
+
4423
+ // src/components/studio/sidebar-footer.tsx
4424
+ import { LogOut } from "lucide-react";
4425
+
3652
4426
  // src/auth/provider.tsx
3653
4427
  import {
3654
- createContext as createContext4,
3655
- useCallback as useCallback3,
3656
- useContext as useContext4,
3657
- useEffect as useEffect6,
3658
- useState as useState9
4428
+ createContext as createContext5,
4429
+ useCallback as useCallback5,
4430
+ useContext as useContext5,
4431
+ useEffect as useEffect8,
4432
+ useState as useState10
3659
4433
  } from "react";
3660
- import { jsx as jsx29 } from "react/jsx-runtime";
4434
+ import { jsx as jsx33 } from "react/jsx-runtime";
3661
4435
  function isInsideIframe() {
3662
4436
  try {
3663
4437
  return typeof window !== "undefined" && window.self !== window.top;
@@ -3665,22 +4439,26 @@ function isInsideIframe() {
3665
4439
  return true;
3666
4440
  }
3667
4441
  }
3668
- var SessionContext = createContext4(void 0);
4442
+ var SessionContext = createContext5(void 0);
3669
4443
  var useSession = () => {
3670
- const context = useContext4(SessionContext);
4444
+ const context = useContext5(SessionContext);
3671
4445
  if (context === void 0) {
3672
4446
  throw new Error("useSession must be used within a SessionProvider");
3673
4447
  }
3674
4448
  return context;
3675
4449
  };
4450
+ var useOptionalSession = () => {
4451
+ const context = useContext5(SessionContext);
4452
+ return context ?? null;
4453
+ };
3676
4454
  var SessionProvider = ({
3677
4455
  children,
3678
4456
  enabled = true
3679
4457
  }) => {
3680
- const [user, setUser] = useState9(null);
3681
- const [loading, setLoading] = useState9(enabled);
3682
- const [embedded] = useState9(isInsideIframe);
3683
- useEffect6(() => {
4458
+ const [user, setUser] = useState10(null);
4459
+ const [loading, setLoading] = useState10(enabled);
4460
+ const [embedded] = useState10(isInsideIframe);
4461
+ useEffect8(() => {
3684
4462
  if (!enabled) {
3685
4463
  setLoading(false);
3686
4464
  return;
@@ -3741,7 +4519,7 @@ var SessionProvider = ({
3741
4519
  messageCleanup?.();
3742
4520
  };
3743
4521
  }, [enabled, embedded]);
3744
- const logout = useCallback3(() => {
4522
+ const logout = useCallback5(() => {
3745
4523
  clearTokens();
3746
4524
  setUser(null);
3747
4525
  const returnTo = encodeURIComponent(
@@ -3751,7 +4529,7 @@ var SessionProvider = ({
3751
4529
  () => window.location.href = `/api/auth/login?return_to=${returnTo}`
3752
4530
  );
3753
4531
  }, []);
3754
- return /* @__PURE__ */ jsx29(
4532
+ return /* @__PURE__ */ jsx33(
3755
4533
  SessionContext.Provider,
3756
4534
  {
3757
4535
  value: {
@@ -3766,30 +4544,1169 @@ var SessionProvider = ({
3766
4544
  );
3767
4545
  };
3768
4546
 
3769
- // src/auth/guard.tsx
3770
- import { Loader2 } from "lucide-react";
3771
- import { jsx as jsx30 } from "react/jsx-runtime";
3772
- var AuthGuard = ({
4547
+ // src/components/studio/sidebar-layout.ts
4548
+ function studioSidebarIconOnlyLayout(isMobile, isCollapsedRail) {
4549
+ if (isMobile) return false;
4550
+ return isCollapsedRail;
4551
+ }
4552
+ var studioSidebarCollapsedRailInsetClass = "box-border w-full px-1.5";
4553
+ var studioSidebarCollapsedRailChipRowClass = "flex w-full justify-center";
4554
+ function studioSidebarNavItemClasses(iconOnly, isActive) {
4555
+ if (iconOnly) {
4556
+ return cn(
4557
+ studioSidebarNavItemClass,
4558
+ studioSidebarNavItemLayout(true),
4559
+ isActive ? studioSidebarCollapsedRailItemActiveClass : studioSidebarCollapsedRailItemIdleClass
4560
+ );
4561
+ }
4562
+ return cn(
4563
+ studioSidebarNavItemClass,
4564
+ studioSidebarNavItemLayout(false),
4565
+ isActive ? studioSidebarNavItemActiveClass : studioSidebarNavItemIdleClass
4566
+ );
4567
+ }
4568
+
4569
+ // src/components/studio/sidebar-entry-motion.tsx
4570
+ import { motion as motion7, useReducedMotion as useReducedMotion4 } from "motion/react";
4571
+ import { jsx as jsx34 } from "react/jsx-runtime";
4572
+ var StudioSidebarEntryMotion = ({
3773
4573
  children,
3774
- requireAuth = false,
3775
- enabled = true
4574
+ className
3776
4575
  }) => {
3777
- const { isAuthenticated, loading, isEmbedded } = useSession();
3778
- if (!enabled) {
3779
- return children;
3780
- }
3781
- if (loading) {
3782
- return /* @__PURE__ */ jsx30("div", { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsx30(Loader2, { className: "w-8 h-8 animate-spin" }) });
3783
- }
3784
- if (requireAuth && !isAuthenticated && !isEmbedded) {
3785
- const returnTo = encodeURIComponent(
3786
- window.location.pathname + window.location.search
3787
- );
3788
- window.location.href = `/api/auth/login?return_to=${returnTo}`;
3789
- return null;
4576
+ const reducedMotion = useReducedMotion4();
4577
+ if (reducedMotion) {
4578
+ return /* @__PURE__ */ jsx34("div", { className, children });
3790
4579
  }
3791
- return children;
4580
+ return /* @__PURE__ */ jsx34(motion7.div, { variants: studioSidebarEntryItemVariants, className: cn(className), children });
4581
+ };
4582
+
4583
+ // src/components/studio/sidebar-tooltip.tsx
4584
+ import { Fragment as Fragment3, jsx as jsx35, jsxs as jsxs18 } from "react/jsx-runtime";
4585
+ var StudioSidebarTooltip = ({
4586
+ label,
4587
+ enabled,
4588
+ children
4589
+ }) => {
4590
+ if (!enabled) return /* @__PURE__ */ jsx35(Fragment3, { children });
4591
+ return /* @__PURE__ */ jsxs18(Tooltip, { children: [
4592
+ /* @__PURE__ */ jsx35(TooltipTrigger, { asChild: true, children }),
4593
+ /* @__PURE__ */ jsx35(TooltipContent, { side: "right", className: "text-xs", children: label })
4594
+ ] });
3792
4595
  };
4596
+
4597
+ // src/components/studio/sidebar-footer.tsx
4598
+ import { jsx as jsx36, jsxs as jsxs19 } from "react/jsx-runtime";
4599
+ function userInitials(name, email) {
4600
+ const fromName = name.trim().split(/\s+/).map((part) => part.charAt(0)).join("").slice(0, 2).toUpperCase();
4601
+ if (fromName) return fromName;
4602
+ return email.charAt(0).toUpperCase() || "?";
4603
+ }
4604
+ var StudioSidebarFooter = ({
4605
+ iconOnlyLayout,
4606
+ showTooltips,
4607
+ onSignOut,
4608
+ emptyCaption = null
4609
+ }) => {
4610
+ const session = useOptionalSession();
4611
+ const user = session?.user ?? null;
4612
+ const handleSignOut = () => {
4613
+ session?.logout();
4614
+ onSignOut?.();
4615
+ };
4616
+ return /* @__PURE__ */ jsx36(StudioSidebarEntryMotion, { children: /* @__PURE__ */ jsx36(
4617
+ "footer",
4618
+ {
4619
+ className: cn(
4620
+ "mt-auto w-full shrink-0 py-2.5",
4621
+ iconOnlyLayout ? studioSidebarCollapsedRailInsetClass : "px-2.5"
4622
+ ),
4623
+ children: user ? /* @__PURE__ */ jsxs19("div", { className: "flex flex-col gap-2", children: [
4624
+ iconOnlyLayout ? /* @__PURE__ */ jsx36("div", { className: studioSidebarCollapsedRailChipRowClass, children: /* @__PURE__ */ jsxs19(Avatar, { size: "sm", className: "size-8", children: [
4625
+ user.user_photo_url ? /* @__PURE__ */ jsx36(AvatarImage, { src: user.user_photo_url, alt: user.user_name }) : null,
4626
+ /* @__PURE__ */ jsx36(AvatarFallback, { className: "text-[10px]", children: userInitials(user.user_name, user.user_email) })
4627
+ ] }) }) : /* @__PURE__ */ jsxs19("div", { className: "flex min-w-0 items-center gap-2.5", children: [
4628
+ /* @__PURE__ */ jsxs19(Avatar, { size: "sm", children: [
4629
+ user.user_photo_url ? /* @__PURE__ */ jsx36(AvatarImage, { src: user.user_photo_url, alt: user.user_name }) : null,
4630
+ /* @__PURE__ */ jsx36(AvatarFallback, { children: userInitials(user.user_name, user.user_email) })
4631
+ ] }),
4632
+ /* @__PURE__ */ jsxs19("div", { className: "min-w-0 flex-1", children: [
4633
+ /* @__PURE__ */ jsx36("p", { className: "truncate text-sm font-medium text-foreground", children: user.user_name }),
4634
+ /* @__PURE__ */ jsx36("p", { className: "truncate text-xs text-muted-foreground", children: user.user_email })
4635
+ ] })
4636
+ ] }),
4637
+ /* @__PURE__ */ jsx36(
4638
+ "div",
4639
+ {
4640
+ className: iconOnlyLayout ? studioSidebarCollapsedRailChipRowClass : void 0,
4641
+ children: /* @__PURE__ */ jsx36(StudioSidebarTooltip, { label: "Sign out", enabled: showTooltips, children: /* @__PURE__ */ jsxs19(
4642
+ "button",
4643
+ {
4644
+ type: "button",
4645
+ onClick: handleSignOut,
4646
+ className: cn(
4647
+ studioSidebarNavItemClasses(iconOnlyLayout, false),
4648
+ iconOnlyLayout && "inline-flex"
4649
+ ),
4650
+ "aria-label": "Sign out",
4651
+ children: [
4652
+ /* @__PURE__ */ jsx36(LogOut, { className: "size-3.5 shrink-0" }),
4653
+ !iconOnlyLayout ? "Sign out" : null
4654
+ ]
4655
+ }
4656
+ ) })
4657
+ }
4658
+ )
4659
+ ] }) : !iconOnlyLayout && emptyCaption ? /* @__PURE__ */ jsx36("p", { className: "px-1 text-xs text-muted-foreground", children: emptyCaption }) : null
4660
+ }
4661
+ ) });
4662
+ };
4663
+
4664
+ // src/components/studio/sidebar-header.tsx
4665
+ import { ChevronLeft, ChevronRight, X } from "lucide-react";
4666
+ import { jsx as jsx37, jsxs as jsxs20 } from "react/jsx-runtime";
4667
+ var sidebarHeaderClass = "flex h-12 shrink-0 items-center px-2";
4668
+ var toggleButtonClass = cn(
4669
+ "flex shrink-0 items-center justify-center rounded-lg text-muted-foreground transition-colors",
4670
+ "hover:bg-muted hover:text-foreground",
4671
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15"
4672
+ );
4673
+ var SidebarToggleButton = ({ ariaLabel, expanded, onClick, children }) => /* @__PURE__ */ jsx37(
4674
+ "button",
4675
+ {
4676
+ type: "button",
4677
+ onClick,
4678
+ className: cn(toggleButtonClass, "size-7"),
4679
+ "aria-label": ariaLabel,
4680
+ "aria-expanded": expanded,
4681
+ children
4682
+ }
4683
+ );
4684
+ var CollapsedBrandToggle = ({
4685
+ onExpand,
4686
+ brand
4687
+ }) => /* @__PURE__ */ jsx37("div", { className: studioSidebarCollapsedRailChipRowClass, children: /* @__PURE__ */ jsx37(StudioSidebarTooltip, { label: "Expand sidebar", enabled: true, children: /* @__PURE__ */ jsxs20(
4688
+ "button",
4689
+ {
4690
+ type: "button",
4691
+ onClick: onExpand,
4692
+ "aria-label": "Expand sidebar",
4693
+ "aria-expanded": false,
4694
+ className: cn(
4695
+ toggleButtonClass,
4696
+ "group relative inline-flex size-8 items-center justify-center overflow-hidden rounded-lg"
4697
+ ),
4698
+ children: [
4699
+ /* @__PURE__ */ jsx37(
4700
+ "span",
4701
+ {
4702
+ "aria-hidden": true,
4703
+ className: cn(
4704
+ "pointer-events-none flex items-center justify-center",
4705
+ "transition-[opacity,transform] duration-200 ease-out",
4706
+ "group-hover:scale-90 group-hover:opacity-0"
4707
+ ),
4708
+ children: brand
4709
+ }
4710
+ ),
4711
+ /* @__PURE__ */ jsx37(
4712
+ ChevronRight,
4713
+ {
4714
+ "aria-hidden": true,
4715
+ className: cn(
4716
+ "pointer-events-none absolute inset-0 m-auto size-4",
4717
+ "opacity-0 transition-[opacity,transform] duration-200 ease-out",
4718
+ "group-hover:opacity-100"
4719
+ )
4720
+ }
4721
+ )
4722
+ ]
4723
+ }
4724
+ ) }) });
4725
+ var StudioSidebarHeader = ({
4726
+ isCollapsedRail,
4727
+ isMobile,
4728
+ mobileOpen,
4729
+ onToggle,
4730
+ brand
4731
+ }) => {
4732
+ if (isMobile) {
4733
+ return /* @__PURE__ */ jsxs20("header", { className: cn(sidebarHeaderClass, "justify-between gap-2 pr-2"), children: [
4734
+ brand,
4735
+ /* @__PURE__ */ jsx37(
4736
+ SidebarToggleButton,
4737
+ {
4738
+ ariaLabel: "Close menu",
4739
+ expanded: mobileOpen,
4740
+ onClick: onToggle,
4741
+ children: /* @__PURE__ */ jsx37(X, { className: "size-3.5" })
4742
+ }
4743
+ )
4744
+ ] });
4745
+ }
4746
+ if (isCollapsedRail) {
4747
+ return /* @__PURE__ */ jsx37(
4748
+ "header",
4749
+ {
4750
+ className: cn(
4751
+ "flex h-12 shrink-0 items-center",
4752
+ studioSidebarCollapsedRailInsetClass
4753
+ ),
4754
+ children: /* @__PURE__ */ jsx37(CollapsedBrandToggle, { onExpand: onToggle, brand })
4755
+ }
4756
+ );
4757
+ }
4758
+ return /* @__PURE__ */ jsxs20("header", { className: cn(sidebarHeaderClass, "justify-between gap-1 pr-2"), children: [
4759
+ brand,
4760
+ /* @__PURE__ */ jsx37(
4761
+ SidebarToggleButton,
4762
+ {
4763
+ ariaLabel: "Collapse sidebar",
4764
+ expanded: true,
4765
+ onClick: onToggle,
4766
+ children: /* @__PURE__ */ jsx37(ChevronLeft, { className: "size-4" })
4767
+ }
4768
+ )
4769
+ ] });
4770
+ };
4771
+
4772
+ // src/components/studio/sidebar-workforce.ts
4773
+ function workforceItemId(w) {
4774
+ return w.id ?? w.uid ?? w.name ?? "";
4775
+ }
4776
+ function workforceItemLabel(w) {
4777
+ return w.name ?? workforceItemId(w);
4778
+ }
4779
+ function workforceItemInitial(w) {
4780
+ const label = workforceItemLabel(w);
4781
+ return label.charAt(0).toUpperCase() || "?";
4782
+ }
4783
+
4784
+ // src/components/studio/sidebar-nav.tsx
4785
+ import { jsx as jsx38 } from "react/jsx-runtime";
4786
+ var StudioSidebarNav = ({
4787
+ workforces,
4788
+ selectedId,
4789
+ onSelect,
4790
+ iconOnlyLayout,
4791
+ showTooltips
4792
+ }) => {
4793
+ if (workforces.length === 0) return null;
4794
+ return /* @__PURE__ */ jsx38(
4795
+ "nav",
4796
+ {
4797
+ className: cn(
4798
+ "flex min-h-0 flex-1 flex-col overflow-y-auto py-1",
4799
+ iconOnlyLayout ? cn(studioSidebarCollapsedRailInsetClass, "gap-1") : "gap-0.5 px-2"
4800
+ ),
4801
+ "aria-label": "Agents",
4802
+ children: workforces.map((w) => {
4803
+ const id = workforceItemId(w);
4804
+ const isActive = id === selectedId;
4805
+ const label = workforceItemLabel(w);
4806
+ return /* @__PURE__ */ jsx38(
4807
+ StudioSidebarEntryMotion,
4808
+ {
4809
+ className: iconOnlyLayout ? studioSidebarCollapsedRailChipRowClass : void 0,
4810
+ children: /* @__PURE__ */ jsx38(StudioSidebarTooltip, { label, enabled: showTooltips, children: /* @__PURE__ */ jsx38(
4811
+ "button",
4812
+ {
4813
+ type: "button",
4814
+ onClick: () => onSelect(id),
4815
+ "aria-pressed": isActive,
4816
+ "aria-label": label,
4817
+ className: cn(
4818
+ studioSidebarNavItemClasses(iconOnlyLayout, isActive),
4819
+ iconOnlyLayout && "inline-flex"
4820
+ ),
4821
+ children: iconOnlyLayout ? /* @__PURE__ */ jsx38("span", { className: "text-xs font-semibold leading-none", children: workforceItemInitial(w) }) : /* @__PURE__ */ jsx38("span", { className: "min-w-0 truncate", children: label })
4822
+ }
4823
+ ) })
4824
+ },
4825
+ id
4826
+ );
4827
+ })
4828
+ }
4829
+ );
4830
+ };
4831
+
4832
+ // src/components/studio/timbal-mark.tsx
4833
+ import { LiquidMetal } from "@paper-design/shaders-react";
4834
+ import { jsx as jsx39 } from "react/jsx-runtime";
4835
+ var DEFAULT_SIZE = 64;
4836
+ var TRANSPARENT_BACK = "#00000000";
4837
+ var TIMBAL_SYMBOL_DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAACWCAYAAAA8AXHiAAAH6ElEQVR4Aeyci7EcNRBFFxIBIgEygUiASCATyASIBOa4rFrZntmPPq1W93218o5nZyTd26dbmnX5fX3TjxyY4IDAmmCqurzdBJYomOKAwJpiqzoVWGJgigMCa4qt6lRgZWHAWKfAMjY8y3ACK0ukjXUKLGPDswwnsLJE2linwDI2PMtwAitLpI11Cixjw+/DxT4SWLHju0ydwHrP+p/euzzE1T8cKr492lsvgfWaXZj733Hp90fL9Pr1EPvn0QTWYcLoF8bS6Pdf/kjQAAnNv7RqVcW6do4q9ffxMe/HW5oXy323boF1zkvzEnDe3RZnS5X6fcRsBdbdRY6oTl1LAJ1s2NDdXaVq3QLr7kapUph8Pxv/iESiDVUqsG63sgQ0b1Rve/6QQEOrVG1DdrCGbFRrQzc5Zh9FlSKppkw5M1gYi8FTjHXaaalSJNTUKWYEC3P5spP3qeY667zsIadVqVpvNrCoUrTag+jHgIRm0z2ka7AGRpzqNG2jOnCeo7uiSi3RnQEszCVjydzRgfPaH1rRbFqlajMig0WVWmpubbThMRvzJVWq1hgVrFKlgKvWG/2YRHLxpBsNrOVLwCJySSBXT7qRwHKxBCwAiypFWzD09ZARwCpVysUScG318E+oUsv3Uleq3gPrqpd1512bO9GWsockqSYO0971zmBR/mnt6ve7k0RC87KvEV61bEewMNftEvCq8Q3XlSqF/obbbW/ZDSz2UWSs2yVgQvjQimb3VarWvgtYZCnm8uRXzz/6MXq3rM47gLXVEjCI9FKlqNCDurTtxjNYxdytloAB4aM6r65S3TK8grXtEtAZEZZ7Wmc362/3BlapUtsuAY0hDVGlau2ewApnbm30g+OyhySpHly210dewKL80/Zyr2+2JBKaQ+4hV4OFudtvVBv4KlUK/Q23+79lJVjso8jYUEvAk5CjFc0hq1StfQVYZCnm8uRXzyX6MXqbq/Nu5liDFX4JOAGgVCkq9MnHMU9ZgVXMDb8EfIYJ1TlNlaq1W4BFlcpoLss9rfY7zfFMsFSl0mD0pdBZYKXaqFa2so+iSpFU1el8hzPAwlgMzuQmeyl0k1ARdf/zrqiRYGGuq/+C9K4Zb11/v5g9JFCh/342+dEosDCWlslOljs0Z3vSfSnGvWCRpRmf+KhSGXW/BBUX9YCFuWQsmUtfGdo3h0g0q0odRjx6tYAFSFnNZXNOlX7kqT47HGgB67jtJnNv+nnkQCtYj/rUZ3LgFggsRdOTAwLLUzQCzUVgBQqmJykCy1M0As1FYAUKpicpAstTNALNRWAFCqYnKTPB8qRTczF2QGAZG55lOIGVJdLGOgWWseFZhhNYWSJtrFNgGRueZTiBlSXSM3We9C2wTkzRqX4HBFa/h+rhxAGBdWKKTvU7ILD6PVQPJw4IrBNTdKrfAYHV76F6OHFAYJ2Ysv+p9QoE1voYhJyBwAoZ1vWiBNb6GIScgcAKGdb1ogTW+hiEnIHAChnW9aIElk0M0o0isNKF3EawwLLxOd0oAitdyN8W/Ndxx9LfmnyMH/aFuX+EVXct7Lfjox+P9vZLFeu5ZcXcf59fGuYKKhRA8Xtmm0QJrGvbqFJd5l537foTKvN3xwzRf7y1vdKC9cSuUqW6zH0yhsePSaSfR0xMYH3qYvcS8Gl32/yNBPrqmC3vx1v/S2DdPRyyBNy72+aIKkUbOuEWsPg970MnsbizUqWGLAGLtbwzPNWpey91NWALWFd97Xh+qrmODSl7SJJqyjQzg0X5p00x1mmnJBKam79GeFVXRrAwd9oS8KrxC64rVQr904f3A9Z0qR8GYB9Fxk5bAj6M4usPtKJ5epWqZWcBiyzFXJ78av3Rj9G7pDpnAMt0CXBCaqlSVOglU4oMVjHXdAlYEsVPB6U6L6lS9TSigkWVWm5ubbTRMcs9zWi462GigaUqdR1r008igbVso2oasS8HYx9FlSKpvvx00ZkHYC2aUduwGIvBbXfveRd7KXSTUO4U7A4W5g79V3l3ETqfEHtIoEL/+RWLz+4MFsbSFltoOjzLHZrdP+nuCBZZmvGJjyVvG927gVWWADLXtFQsHAytVKmt9pC7gEWVwlz3S8BgANG9TZWqte8AVqlSmFzPPfoxiUSbrnPGAJ7BKkuAqtSMyE/u0ytYW21UB8aIfRRViqQa2K19Vx7BwlgMtndj3Ygs8+gmodbNYuDInsDCXH3ZOTC4K7vyAhbZSlvphfXYLHdoDrmHXA0WVWrLx+lOCnnSDa17JViYS8aSuZ1x2uZ2tKL5cZXaRs71RFeARZVKYe5ntrMxD12lar3WYJUqBVz1PKIfk0ipnnStwEqzBHyWISRQxifdmwVYqZaACiyqFK06ledwJlilSqVaAg50qFJp9lKH3tPXLLCymlv2kCTVqeFZTs4Ai/JPC+ThUykkEprDf43w1ImPF4wEC3MzLgGlSqH/o616GwUW+ygyNtMSgFY0q0qd5FEvWGQp5vLkd9J92FPozVidXw5oD1hZlwASiQr9sskZL2wBK+sSgO6UX3a2JEYrWCyBLePtfA/L387zN517C1imE7wYTKedOyCwnAdo1+kJrF0j53zeAst5gHadnsDaNXLO5y2wnAdo1+kJrF0j53zew8ByrlPTM3ZAYBkbnmU4gZUl0sY6BZax4VmGE1hZIm2sU2AZG55lOIGVJdLDdL7W0f8AAAD//x3VUCQAAAAGSURBVAMArsj7LTb9pqMAAAAASUVORK5CYII=";
4838
+ function TimbalMark({
4839
+ className,
4840
+ size = DEFAULT_SIZE,
4841
+ src = TIMBAL_SYMBOL_DATA_URI
4842
+ }) {
4843
+ return /* @__PURE__ */ jsx39(
4844
+ "div",
4845
+ {
4846
+ className: cn("relative shrink-0 bg-transparent", className),
4847
+ style: { width: size, height: size },
4848
+ role: "img",
4849
+ "aria-label": "Timbal",
4850
+ children: /* @__PURE__ */ jsx39(
4851
+ LiquidMetal,
4852
+ {
4853
+ width: size,
4854
+ height: size,
4855
+ image: src,
4856
+ colorBack: TRANSPARENT_BACK,
4857
+ colorTint: "#ffffff",
4858
+ shape: "none",
4859
+ repetition: 2,
4860
+ softness: 0.1,
4861
+ shiftRed: 0.3,
4862
+ shiftBlue: 0.3,
4863
+ distortion: 0.07,
4864
+ contour: 0.4,
4865
+ angle: 70,
4866
+ speed: 1,
4867
+ scale: 0.6,
4868
+ fit: "contain",
4869
+ className: "size-full bg-transparent",
4870
+ style: { background: "transparent" },
4871
+ webGlContextAttributes: {
4872
+ alpha: true,
4873
+ premultipliedAlpha: false
4874
+ }
4875
+ }
4876
+ )
4877
+ }
4878
+ );
4879
+ }
4880
+
4881
+ // src/components/studio/sidebar.tsx
4882
+ import { jsx as jsx40, jsxs as jsxs21 } from "react/jsx-runtime";
4883
+ var DEFAULT_BREAKPOINT_PX = 768;
4884
+ function readPersistedCollapsed(key) {
4885
+ if (!key || typeof window === "undefined") return false;
4886
+ try {
4887
+ return window.localStorage.getItem(key) === "1";
4888
+ } catch {
4889
+ return false;
4890
+ }
4891
+ }
4892
+ function writePersistedCollapsed(key, collapsed) {
4893
+ if (!key || typeof window === "undefined") return;
4894
+ try {
4895
+ window.localStorage.setItem(key, collapsed ? "1" : "0");
4896
+ } catch {
4897
+ }
4898
+ }
4899
+ var StudioSidebarPanel = ({
4900
+ workforces,
4901
+ selectedId,
4902
+ onSelect,
4903
+ collapsed,
4904
+ onCollapsedChange,
4905
+ isMobile,
4906
+ mobileOpen,
4907
+ onMobileOpenChange,
4908
+ widthCollapsed,
4909
+ entriesVisible,
4910
+ onEntriesBlurOutComplete,
4911
+ onPanelWidthComplete,
4912
+ brand,
4913
+ emptyCaption = null
4914
+ }) => {
4915
+ const reducedMotion = useReducedMotion5();
4916
+ const isCollapsedRail = widthCollapsed && !isMobile;
4917
+ const iconOnlyLayout = studioSidebarIconOnlyLayout(isMobile, isCollapsedRail);
4918
+ const isDrawerOpen = isMobile && mobileOpen;
4919
+ const widthDirection = widthCollapsed ? "collapse" : "expand";
4920
+ const widthTransition = studioSidebarWidthTransition(
4921
+ !!reducedMotion,
4922
+ widthDirection
4923
+ );
4924
+ const handleToggle = () => {
4925
+ if (isMobile) {
4926
+ onMobileOpenChange(false);
4927
+ return;
4928
+ }
4929
+ onCollapsedChange(!collapsed);
4930
+ };
4931
+ const panelWidthPx = isMobile ? SIDEBAR_MOBILE_PX : widthCollapsed ? SIDEBAR_WIDTH_COLLAPSED_PX : SIDEBAR_WIDTH_PX;
4932
+ const brandNode = brand ?? /* @__PURE__ */ jsx40(TimbalMark, { size: 32 });
4933
+ const panel = /* @__PURE__ */ jsxs21(
4934
+ motion8.div,
4935
+ {
4936
+ "data-sidebar-collapsed": isCollapsedRail ? "" : void 0,
4937
+ className: cn(
4938
+ "flex h-full flex-col overflow-hidden",
4939
+ studioSidebarPanelClass,
4940
+ isMobile ? "rounded-none rounded-r-2xl" : "rounded-2xl"
4941
+ ),
4942
+ initial: false,
4943
+ animate: { width: panelWidthPx },
4944
+ transition: widthTransition,
4945
+ style: { willChange: entriesVisible ? void 0 : "width" },
4946
+ onAnimationComplete: isMobile || entriesVisible ? void 0 : () => onPanelWidthComplete(),
4947
+ children: [
4948
+ /* @__PURE__ */ jsx40(
4949
+ StudioSidebarHeader,
4950
+ {
4951
+ isCollapsedRail,
4952
+ isMobile,
4953
+ mobileOpen,
4954
+ onToggle: handleToggle,
4955
+ brand: brandNode
4956
+ }
4957
+ ),
4958
+ /* @__PURE__ */ jsxs21(
4959
+ StudioSidebarEntries,
4960
+ {
4961
+ visible: entriesVisible,
4962
+ onBlurOutComplete: onEntriesBlurOutComplete,
4963
+ children: [
4964
+ /* @__PURE__ */ jsx40(
4965
+ "div",
4966
+ {
4967
+ id: DOM_IDS.sidebarRuntimeAnchor,
4968
+ className: cn(
4969
+ "min-h-0 shrink-0 empty:hidden",
4970
+ iconOnlyLayout ? "px-1.5 pt-1.5" : "px-2 pt-1.5"
4971
+ )
4972
+ }
4973
+ ),
4974
+ /* @__PURE__ */ jsx40(
4975
+ StudioSidebarNav,
4976
+ {
4977
+ workforces,
4978
+ selectedId,
4979
+ onSelect,
4980
+ iconOnlyLayout,
4981
+ showTooltips: isCollapsedRail
4982
+ }
4983
+ ),
4984
+ workforces.length === 0 ? /* @__PURE__ */ jsx40("div", { className: "min-h-0 flex-1" }) : null,
4985
+ /* @__PURE__ */ jsx40(
4986
+ StudioSidebarFooter,
4987
+ {
4988
+ iconOnlyLayout,
4989
+ showTooltips: isCollapsedRail,
4990
+ onSignOut: isMobile ? () => onMobileOpenChange(false) : void 0,
4991
+ emptyCaption
4992
+ }
4993
+ )
4994
+ ]
4995
+ }
4996
+ )
4997
+ ]
4998
+ }
4999
+ );
5000
+ if (isMobile) {
5001
+ return /* @__PURE__ */ jsx40(
5002
+ motion8.aside,
5003
+ {
5004
+ className: "fixed inset-y-0 left-0 z-[60] flex",
5005
+ "aria-label": "Studio navigation",
5006
+ "aria-hidden": !mobileOpen,
5007
+ initial: false,
5008
+ animate: {
5009
+ x: isDrawerOpen ? 0 : -(SIDEBAR_MOBILE_PX + 32)
5010
+ },
5011
+ transition: studioSidebarDrawerTransition(!!reducedMotion),
5012
+ style: { pointerEvents: isDrawerOpen ? "auto" : "none" },
5013
+ children: panel
5014
+ }
5015
+ );
5016
+ }
5017
+ return /* @__PURE__ */ jsx40(
5018
+ "aside",
5019
+ {
5020
+ className: "absolute inset-y-0 left-0 z-[60] flex py-[var(--studio-sidebar-gap)] pl-[var(--studio-sidebar-gap)]",
5021
+ "aria-label": "Studio navigation",
5022
+ children: panel
5023
+ }
5024
+ );
5025
+ };
5026
+ var StudioSidebar = ({
5027
+ workforces: workforcesProp,
5028
+ selectedId: selectedIdProp,
5029
+ onSelect,
5030
+ defaultCollapsed = false,
5031
+ persistKey = STORAGE_KEYS.sidebarCollapsed,
5032
+ mobileBreakpointPx = DEFAULT_BREAKPOINT_PX,
5033
+ brand,
5034
+ emptyCaption,
5035
+ mobileOpen: mobileOpenProp,
5036
+ onMobileOpenChange: onMobileOpenChangeProp
5037
+ }) => {
5038
+ const reducedMotion = useReducedMotion5();
5039
+ const fetched = useWorkforces({ enabled: workforcesProp === void 0 });
5040
+ const workforces = workforcesProp ?? fetched.workforces;
5041
+ const [internalSelected, setInternalSelected] = useState11(
5042
+ selectedIdProp ?? ""
5043
+ );
5044
+ useEffect9(() => {
5045
+ if (selectedIdProp !== void 0) return;
5046
+ if (internalSelected) return;
5047
+ const first = workforces[0]?.id ?? workforces[0]?.uid ?? workforces[0]?.name;
5048
+ if (first) setInternalSelected(first);
5049
+ }, [workforces, selectedIdProp, internalSelected]);
5050
+ const selectedId = selectedIdProp ?? internalSelected ?? workforces[0]?.id ?? workforces[0]?.uid ?? workforces[0]?.name ?? "";
5051
+ const handleSelect = useCallback6(
5052
+ (id) => {
5053
+ if (selectedIdProp === void 0) setInternalSelected(id);
5054
+ onSelect?.(id);
5055
+ },
5056
+ [selectedIdProp, onSelect]
5057
+ );
5058
+ const [collapsed, setCollapsed] = useState11(() => {
5059
+ const persisted = readPersistedCollapsed(persistKey);
5060
+ return persisted || defaultCollapsed;
5061
+ });
5062
+ const handleCollapsedChange = useCallback6(
5063
+ (next) => {
5064
+ setCollapsed(next);
5065
+ writePersistedCollapsed(persistKey, next);
5066
+ },
5067
+ [persistKey]
5068
+ );
5069
+ const [isMobile, setIsMobile] = useState11(() => {
5070
+ if (typeof window === "undefined") return false;
5071
+ return window.innerWidth < mobileBreakpointPx;
5072
+ });
5073
+ useEffect9(() => {
5074
+ if (typeof window === "undefined") return;
5075
+ const onResize = () => setIsMobile(window.innerWidth < mobileBreakpointPx);
5076
+ onResize();
5077
+ window.addEventListener("resize", onResize);
5078
+ return () => window.removeEventListener("resize", onResize);
5079
+ }, [mobileBreakpointPx]);
5080
+ const [internalMobileOpen, setInternalMobileOpen] = useState11(false);
5081
+ const mobileOpen = mobileOpenProp ?? internalMobileOpen;
5082
+ const setMobileOpen = useCallback6(
5083
+ (next) => {
5084
+ if (mobileOpenProp === void 0) setInternalMobileOpen(next);
5085
+ onMobileOpenChangeProp?.(next);
5086
+ },
5087
+ [mobileOpenProp, onMobileOpenChangeProp]
5088
+ );
5089
+ const effectiveCollapsed = isMobile ? false : collapsed;
5090
+ const {
5091
+ widthCollapsed,
5092
+ entriesVisible: phaseEntriesVisible,
5093
+ onEntriesBlurOutComplete,
5094
+ onPanelWidthComplete
5095
+ } = useSidebarCollapsePhase(effectiveCollapsed, !!reducedMotion);
5096
+ const entriesVisible = isMobile || phaseEntriesVisible;
5097
+ const isCollapsedRail = widthCollapsed && !isMobile;
5098
+ const iconOnlyLayout = studioSidebarIconOnlyLayout(isMobile, isCollapsedRail);
5099
+ const contextValue = useMemo8(
5100
+ () => ({
5101
+ collapsed: effectiveCollapsed,
5102
+ isMobile,
5103
+ isCollapsedRail,
5104
+ iconOnlyLayout
5105
+ }),
5106
+ [effectiveCollapsed, isMobile, isCollapsedRail, iconOnlyLayout]
5107
+ );
5108
+ return /* @__PURE__ */ jsx40(StudioSidebarContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx40(
5109
+ StudioSidebarPanel,
5110
+ {
5111
+ workforces,
5112
+ selectedId,
5113
+ onSelect: handleSelect,
5114
+ collapsed: effectiveCollapsed,
5115
+ onCollapsedChange: handleCollapsedChange,
5116
+ isMobile,
5117
+ mobileOpen,
5118
+ onMobileOpenChange: setMobileOpen,
5119
+ widthCollapsed,
5120
+ entriesVisible,
5121
+ onEntriesBlurOutComplete,
5122
+ onPanelWidthComplete,
5123
+ brand,
5124
+ emptyCaption
5125
+ }
5126
+ ) });
5127
+ };
5128
+
5129
+ // src/components/studio/sidebar-runtime-portal.tsx
5130
+ import { useCallback as useCallback7, useLayoutEffect, useState as useState12 } from "react";
5131
+ import { createPortal } from "react-dom";
5132
+ import { MessageSquarePlus } from "lucide-react";
5133
+ import { useThread as useThread2 } from "@assistant-ui/react";
5134
+ import { jsx as jsx41, jsxs as jsxs22 } from "react/jsx-runtime";
5135
+ var StudioSidebarRuntimePortal = ({
5136
+ label = "New chat"
5137
+ }) => {
5138
+ const { iconOnlyLayout } = useStudioSidebarLayout();
5139
+ const hasMessages = useThread2((s) => s.messages.length > 0);
5140
+ const { clear } = useTimbalRuntime();
5141
+ const [anchor, setAnchor] = useState12(null);
5142
+ const startNewChat = useCallback7(() => {
5143
+ clear();
5144
+ }, [clear]);
5145
+ useLayoutEffect(() => {
5146
+ setAnchor(document.getElementById(DOM_IDS.sidebarRuntimeAnchor));
5147
+ }, []);
5148
+ if (!anchor || !hasMessages) return null;
5149
+ const button = /* @__PURE__ */ jsxs22(
5150
+ "button",
5151
+ {
5152
+ type: "button",
5153
+ onClick: startNewChat,
5154
+ "aria-label": label,
5155
+ className: studioSidebarNavItemClasses(iconOnlyLayout, false),
5156
+ children: [
5157
+ /* @__PURE__ */ jsx41(MessageSquarePlus, { className: "size-3.5 shrink-0" }),
5158
+ !iconOnlyLayout ? /* @__PURE__ */ jsx41("span", { className: "min-w-0 truncate", children: label }) : null
5159
+ ]
5160
+ }
5161
+ );
5162
+ return createPortal(
5163
+ iconOnlyLayout ? /* @__PURE__ */ jsxs22(Tooltip, { children: [
5164
+ /* @__PURE__ */ jsx41(TooltipTrigger, { asChild: true, children: button }),
5165
+ /* @__PURE__ */ jsx41(TooltipContent, { side: "right", className: "text-xs", children: label })
5166
+ ] }) : button,
5167
+ anchor
5168
+ );
5169
+ };
5170
+
5171
+ // src/components/studio/welcome.tsx
5172
+ import { motion as motion9 } from "motion/react";
5173
+ import { useThread as useThread3 } from "@assistant-ui/react";
5174
+ import { jsx as jsx42, jsxs as jsxs23 } from "react/jsx-runtime";
5175
+ var luxuryEase2 = [0.16, 1, 0.3, 1];
5176
+ var welcomeStagger2 = {
5177
+ initial: {},
5178
+ animate: {
5179
+ transition: { staggerChildren: 0.16, delayChildren: 0.18 }
5180
+ }
5181
+ };
5182
+ var welcomeItem2 = {
5183
+ initial: { opacity: 0, y: 14 },
5184
+ animate: {
5185
+ opacity: 1,
5186
+ y: 0,
5187
+ transition: { duration: 0.9, ease: luxuryEase2 }
5188
+ }
5189
+ };
5190
+ var welcomeIcon2 = {
5191
+ initial: { opacity: 0, y: 10, scale: 0.96 },
5192
+ animate: {
5193
+ opacity: 1,
5194
+ y: 0,
5195
+ scale: 1,
5196
+ transition: { duration: 1.1, ease: luxuryEase2 }
5197
+ }
5198
+ };
5199
+ var StudioWelcome = ({ config, icon }) => {
5200
+ const isEmpty = useThread3((s) => s.messages.length === 0);
5201
+ if (!isEmpty) return null;
5202
+ const iconNode = icon ?? /* @__PURE__ */ jsx42(
5203
+ TimbalMark,
5204
+ {
5205
+ size: 112,
5206
+ className: "max-md:scale-[0.58] max-md:origin-center"
5207
+ }
5208
+ );
5209
+ return /* @__PURE__ */ jsx42("div", { className: "aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col", children: /* @__PURE__ */ jsx42("div", { className: "aui-thread-welcome-center flex w-full grow flex-col items-center justify-center", children: /* @__PURE__ */ jsxs23(
5210
+ motion9.div,
5211
+ {
5212
+ className: "aui-thread-welcome-message flex flex-col items-center justify-center px-2 text-center sm:px-4",
5213
+ variants: welcomeStagger2,
5214
+ initial: "initial",
5215
+ animate: "animate",
5216
+ children: [
5217
+ /* @__PURE__ */ jsx42(motion9.div, { variants: welcomeIcon2, className: "mb-4 md:mb-5", children: iconNode }),
5218
+ /* @__PURE__ */ jsx42(
5219
+ motion9.h1,
5220
+ {
5221
+ variants: welcomeItem2,
5222
+ className: "aui-thread-welcome-message-inner text-xl font-semibold sm:text-2xl",
5223
+ children: config?.heading ?? "How can I help you today?"
5224
+ }
5225
+ ),
5226
+ /* @__PURE__ */ jsx42(
5227
+ motion9.p,
5228
+ {
5229
+ variants: welcomeItem2,
5230
+ className: "aui-thread-welcome-message-inner mt-2 text-muted-foreground",
5231
+ children: config?.subheading ?? "Send a message to start a conversation."
5232
+ }
5233
+ )
5234
+ ]
5235
+ }
5236
+ ) }) });
5237
+ };
5238
+
5239
+ // src/components/studio/studio-shell.tsx
5240
+ import { Fragment as Fragment4, jsx as jsx43, jsxs as jsxs24 } from "react/jsx-runtime";
5241
+ import { createElement } from "react";
5242
+ var DEFAULT_BREAKPOINT_PX2 = 768;
5243
+ function readPersistedCollapsed2(key) {
5244
+ if (!key || typeof window === "undefined") return false;
5245
+ try {
5246
+ return window.localStorage.getItem(key) === "1";
5247
+ } catch {
5248
+ return false;
5249
+ }
5250
+ }
5251
+ function writePersistedCollapsed2(key, collapsed) {
5252
+ if (!key || typeof window === "undefined") return;
5253
+ try {
5254
+ window.localStorage.setItem(key, collapsed ? "1" : "0");
5255
+ } catch {
5256
+ }
5257
+ }
5258
+ function makeComposerWithPortal(BaseComposer) {
5259
+ const Resolved = BaseComposer ?? Composer;
5260
+ return function StudioComposerWithSidebar(props) {
5261
+ return /* @__PURE__ */ jsxs24(Fragment4, { children: [
5262
+ /* @__PURE__ */ jsx43(StudioSidebarRuntimePortal, {}),
5263
+ /* @__PURE__ */ jsx43(Resolved, { ...props })
5264
+ ] });
5265
+ };
5266
+ }
5267
+ var TimbalStudioShell = ({
5268
+ workforceId,
5269
+ workforces: workforcesProp,
5270
+ workforcesFetch,
5271
+ workforcesBaseUrl,
5272
+ brand,
5273
+ headerActions,
5274
+ headerStart,
5275
+ defaultCollapsed = false,
5276
+ persistKey = STORAGE_KEYS.sidebarCollapsed,
5277
+ mobileBreakpointPx = DEFAULT_BREAKPOINT_PX2,
5278
+ sidebarEmptyCaption = null,
5279
+ welcome,
5280
+ components,
5281
+ ...chatProps
5282
+ }) => {
5283
+ const reducedMotion = useReducedMotion6();
5284
+ const shouldFetchWorkforces = !workforceId && workforcesProp === void 0;
5285
+ const fetched = useWorkforces({
5286
+ enabled: shouldFetchWorkforces,
5287
+ fetch: workforcesFetch,
5288
+ baseUrl: workforcesBaseUrl
5289
+ });
5290
+ const workforces = workforcesProp ?? fetched.workforces;
5291
+ const [internalSelected, setInternalSelected] = useState13(
5292
+ workforceId ?? ""
5293
+ );
5294
+ useEffect10(() => {
5295
+ if (workforceId) return;
5296
+ if (internalSelected) return;
5297
+ const first = workforces[0]?.id ?? workforces[0]?.uid ?? workforces[0]?.name;
5298
+ if (first) setInternalSelected(first);
5299
+ }, [workforces, workforceId, internalSelected]);
5300
+ const activeWorkforceId = workforceId ?? internalSelected ?? fetched.selectedId ?? "";
5301
+ const [collapsed, setCollapsed] = useState13(() => {
5302
+ const persisted = readPersistedCollapsed2(persistKey);
5303
+ return persisted || defaultCollapsed;
5304
+ });
5305
+ const [isMobile, setIsMobile] = useState13(() => {
5306
+ if (typeof window === "undefined") return false;
5307
+ return window.innerWidth < mobileBreakpointPx;
5308
+ });
5309
+ const [mobileSidebarOpen, setMobileSidebarOpen] = useState13(false);
5310
+ useEffect10(() => {
5311
+ if (typeof window === "undefined") return;
5312
+ const onResize = () => setIsMobile(window.innerWidth < mobileBreakpointPx);
5313
+ onResize();
5314
+ window.addEventListener("resize", onResize);
5315
+ return () => window.removeEventListener("resize", onResize);
5316
+ }, [mobileBreakpointPx]);
5317
+ useEffect10(() => {
5318
+ if (!isMobile) setMobileSidebarOpen(false);
5319
+ }, [isMobile]);
5320
+ useEffect10(() => {
5321
+ if (!mobileSidebarOpen) return;
5322
+ const onKeyDown = (e) => {
5323
+ if (e.key === "Escape") setMobileSidebarOpen(false);
5324
+ };
5325
+ window.addEventListener("keydown", onKeyDown);
5326
+ return () => window.removeEventListener("keydown", onKeyDown);
5327
+ }, [mobileSidebarOpen]);
5328
+ const effectiveCollapsed = isMobile ? false : collapsed;
5329
+ const {
5330
+ widthCollapsed,
5331
+ entriesVisible: phaseEntriesVisible,
5332
+ onEntriesBlurOutComplete,
5333
+ onPanelWidthComplete
5334
+ } = useSidebarCollapsePhase(effectiveCollapsed, !!reducedMotion);
5335
+ const entriesVisible = isMobile || phaseEntriesVisible;
5336
+ const isCollapsedRail = widthCollapsed && !isMobile;
5337
+ const iconOnlyLayout = studioSidebarIconOnlyLayout(isMobile, isCollapsedRail);
5338
+ const layoutDirection = widthCollapsed ? "collapse" : "expand";
5339
+ const layoutTransition = studioSidebarWidthTransition(
5340
+ !!reducedMotion,
5341
+ layoutDirection
5342
+ );
5343
+ const desktopInsetPx = widthCollapsed ? SIDEBAR_INSET_PX_COLLAPSED : SIDEBAR_INSET_PX_EXPANDED;
5344
+ const onCollapsedChange = useCallback8(
5345
+ (next) => {
5346
+ setCollapsed(next);
5347
+ writePersistedCollapsed2(persistKey, next);
5348
+ },
5349
+ [persistKey]
5350
+ );
5351
+ const handleSelectWorkforce = useCallback8(
5352
+ (id) => {
5353
+ if (!workforceId) setInternalSelected(id);
5354
+ if (isMobile) setMobileSidebarOpen(false);
5355
+ },
5356
+ [workforceId, isMobile]
5357
+ );
5358
+ const sidebarContext = useMemo9(
5359
+ () => ({
5360
+ collapsed: effectiveCollapsed,
5361
+ isMobile,
5362
+ isCollapsedRail,
5363
+ iconOnlyLayout
5364
+ }),
5365
+ [effectiveCollapsed, isMobile, isCollapsedRail, iconOnlyLayout]
5366
+ );
5367
+ const resolvedComponents = useMemo9(() => {
5368
+ const next = { Welcome: StudioWelcome, ...components };
5369
+ next.Composer = makeComposerWithPortal(components?.Composer);
5370
+ return next;
5371
+ }, [components]);
5372
+ return /* @__PURE__ */ jsx43(StudioSidebarContext.Provider, { value: sidebarContext, children: /* @__PURE__ */ jsxs24(
5373
+ "div",
5374
+ {
5375
+ className: cn(
5376
+ "relative h-dvh overflow-hidden bg-background",
5377
+ isMobile && mobileSidebarOpen && "max-md:overflow-hidden"
5378
+ ),
5379
+ style: studioChromeShellStyle,
5380
+ children: [
5381
+ /* @__PURE__ */ jsx43(
5382
+ "div",
5383
+ {
5384
+ className: "pointer-events-none absolute inset-0 z-0 bg-background",
5385
+ "aria-hidden": true
5386
+ }
5387
+ ),
5388
+ /* @__PURE__ */ jsx43(
5389
+ "div",
5390
+ {
5391
+ className: cn(
5392
+ "pointer-events-none absolute inset-0 z-0",
5393
+ studioPlaygroundGradientClass
5394
+ ),
5395
+ "aria-hidden": true
5396
+ }
5397
+ ),
5398
+ /* @__PURE__ */ jsx43(
5399
+ StudioSidebarBackdrop,
5400
+ {
5401
+ open: isMobile && mobileSidebarOpen,
5402
+ onClose: () => setMobileSidebarOpen(false)
5403
+ }
5404
+ ),
5405
+ /* @__PURE__ */ jsx43(
5406
+ StudioSidebarPanel,
5407
+ {
5408
+ workforces,
5409
+ selectedId: activeWorkforceId,
5410
+ onSelect: handleSelectWorkforce,
5411
+ collapsed: effectiveCollapsed,
5412
+ onCollapsedChange,
5413
+ isMobile,
5414
+ mobileOpen: mobileSidebarOpen,
5415
+ onMobileOpenChange: setMobileSidebarOpen,
5416
+ widthCollapsed,
5417
+ entriesVisible,
5418
+ onEntriesBlurOutComplete,
5419
+ onPanelWidthComplete,
5420
+ brand,
5421
+ emptyCaption: sidebarEmptyCaption
5422
+ }
5423
+ ),
5424
+ /* @__PURE__ */ jsxs24(
5425
+ motion10.header,
5426
+ {
5427
+ className: cn(
5428
+ "absolute top-0 right-0 z-40 flex items-start justify-between gap-2",
5429
+ "px-3 pt-[var(--studio-topbar-gap)] md:px-4",
5430
+ "left-0"
5431
+ ),
5432
+ initial: false,
5433
+ animate: { left: isMobile ? 0 : desktopInsetPx },
5434
+ transition: layoutTransition,
5435
+ children: [
5436
+ /* @__PURE__ */ jsxs24(
5437
+ "div",
5438
+ {
5439
+ className: cn(
5440
+ "flex min-w-0 flex-1 items-center gap-2",
5441
+ studioTopbarPillHeightClass
5442
+ ),
5443
+ children: [
5444
+ isMobile && !mobileSidebarOpen ? /* @__PURE__ */ jsx43(
5445
+ TimbalV2Button,
5446
+ {
5447
+ variant: "secondary",
5448
+ size: "sm",
5449
+ isIconOnly: true,
5450
+ className: studioTopbarIconPillClass,
5451
+ onClick: () => setMobileSidebarOpen(true),
5452
+ "aria-label": "Open menu",
5453
+ "aria-expanded": false,
5454
+ children: /* @__PURE__ */ jsx43(Menu, { className: "size-4" })
5455
+ }
5456
+ ) : null,
5457
+ headerStart
5458
+ ]
5459
+ }
5460
+ ),
5461
+ headerActions ? /* @__PURE__ */ jsx43("div", { className: "flex shrink-0 items-center gap-1", children: headerActions }) : null
5462
+ ]
5463
+ }
5464
+ ),
5465
+ /* @__PURE__ */ jsx43(
5466
+ motion10.main,
5467
+ {
5468
+ className: cn(
5469
+ "relative z-10 flex h-full min-w-0 flex-col",
5470
+ "pt-[var(--studio-inset-top)]",
5471
+ "px-3 md:px-0"
5472
+ ),
5473
+ initial: false,
5474
+ animate: { paddingLeft: isMobile ? 12 : desktopInsetPx },
5475
+ transition: layoutTransition,
5476
+ children: activeWorkforceId ? /* @__PURE__ */ createElement(
5477
+ TimbalChat,
5478
+ {
5479
+ ...chatProps,
5480
+ workforceId: activeWorkforceId,
5481
+ key: activeWorkforceId,
5482
+ welcome,
5483
+ components: resolvedComponents,
5484
+ className: cn("min-h-0 flex-1 bg-transparent", chatProps.className)
5485
+ }
5486
+ ) : null
5487
+ }
5488
+ )
5489
+ ]
5490
+ }
5491
+ ) });
5492
+ };
5493
+
5494
+ // src/components/studio/mode-toggle.tsx
5495
+ import { useCallback as useCallback9, useEffect as useEffect11, useState as useState14 } from "react";
5496
+ import { Moon, Sun } from "lucide-react";
5497
+ import { jsx as jsx44, jsxs as jsxs25 } from "react/jsx-runtime";
5498
+ var ModeToggle = ({
5499
+ theme,
5500
+ setTheme,
5501
+ className,
5502
+ label = "Toggle theme"
5503
+ }) => {
5504
+ const isControlled = theme !== void 0;
5505
+ const [internalIsDark, setInternalIsDark] = useState14(false);
5506
+ useEffect11(() => {
5507
+ if (isControlled) return;
5508
+ if (typeof document === "undefined") return;
5509
+ setInternalIsDark(document.documentElement.classList.contains("dark"));
5510
+ }, [isControlled]);
5511
+ const isDark = isControlled ? theme === "dark" : internalIsDark;
5512
+ const onClick = useCallback9(() => {
5513
+ const next = isDark ? "light" : "dark";
5514
+ if (setTheme) {
5515
+ setTheme(next);
5516
+ return;
5517
+ }
5518
+ if (typeof document === "undefined") return;
5519
+ document.documentElement.classList.toggle("dark", next === "dark");
5520
+ setInternalIsDark(next === "dark");
5521
+ }, [isDark, setTheme]);
5522
+ return /* @__PURE__ */ jsxs25(
5523
+ TimbalV2Button,
5524
+ {
5525
+ variant: "secondary",
5526
+ size: "sm",
5527
+ isIconOnly: true,
5528
+ onClick,
5529
+ className: cn(
5530
+ studioTopbarPillHeightClass,
5531
+ studioTopbarIconPillClass,
5532
+ "relative",
5533
+ className
5534
+ ),
5535
+ "aria-label": label,
5536
+ title: label,
5537
+ children: [
5538
+ /* @__PURE__ */ jsx44(Sun, { className: "size-3.5 scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" }),
5539
+ /* @__PURE__ */ jsx44(Moon, { className: "absolute size-3.5 scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" }),
5540
+ /* @__PURE__ */ jsx44("span", { className: "sr-only", children: label })
5541
+ ]
5542
+ }
5543
+ );
5544
+ };
5545
+
5546
+ // src/artifacts/agent-instructions.ts
5547
+ var ARTIFACT_AGENT_INSTRUCTIONS = `
5548
+ ## Rich artifacts (Timbal chat UI)
5549
+
5550
+ When you need charts, tables, choice widgets, or interactive controls, return a **JSON artifact object** instead of plain prose. The chat UI renders these automatically.
5551
+
5552
+ ### Delivery channels (either works)
5553
+
5554
+ 1. **Tool result (preferred)** \u2014 return a single JSON object (or a JSON string) from a tool. The object must include a string \`type\` field.
5555
+ 2. **Inline markdown fence** \u2014 embed the same JSON inside a fenced block:
5556
+
5557
+ \`\`\`timbal-artifact
5558
+ {"type":"chart","data":[{"month":"Jan","sales":120}]}
5559
+ \`\`\`
5560
+
5561
+ The alias \`\`\`timbal\`\`\` is also accepted.
5562
+
5563
+ ### Built-in artifact types
5564
+
5565
+ | \`type\` | Use for |
5566
+ |---|---|
5567
+ | \`chart\` | Bar, line, area, or pie charts. Fields: \`data\`, optional \`chartType\`, \`xKey\`, \`dataKey\`, \`title\`, \`unit\`. |
5568
+ | \`table\` | Tabular data. Fields: \`rows\`, optional \`columns\`, \`title\`. |
5569
+ | \`question\` | In-thread multiple choice. Fields: \`options: [{ id, label, description? }]\`, optional \`prompt\`, \`multi\`. User replies are sent back as a normal user message. |
5570
+ | \`html\` | Custom HTML/CSS/JS in an iframe. Fields: \`content\` (HTML document or fragment), optional \`title\`, \`height\`, \`sandboxed\` (default \`true\`; set \`false\` for unrestricted scripts/CDN). |
5571
+ | \`json\` | Fallback structured view. Fields: \`data\`, optional \`title\`. |
5572
+ | \`ui\` | **Interactive UI** composed from a fixed node palette (hover, click, drag). See below. |
5573
+
5574
+ ### When to use \`type: "html"\`
5575
+
5576
+ Use \`html\` when the user wants a **rich visual or interactive page** that does not fit the \`ui\` palette \u2014 e.g. styled layouts, SVG graphics, CSS animations, canvas, small games, calculators, or multi-section mockups. Pass a full HTML document or a body fragment in \`content\`.
5577
+
5578
+ - Inline \`<style>\`, \`<script>\`, SVG, and canvas are supported.
5579
+ - Default \`sandboxed: true\` runs in an isolated iframe with scripts enabled.
5580
+ - Set \`sandboxed: false\` only for trusted content that needs external CDN scripts/styles or full DOM freedom.
5581
+ - Prefer \`ui\` when controls should send chat messages or host events; prefer \`html\` for self-contained mini-apps and visual demos.
5582
+
5583
+ ### When to use \`type: "ui"\`
5584
+
5585
+ Use a \`ui\` artifact when the user should **hover, click, drag, or adjust controls** in-thread and those actions should integrate with the chat runtime (messages, \`onArtifactEvent\`). For standalone visual/interactive HTML, use \`html\` instead.
5586
+
5587
+ Each \`ui\` artifact has:
5588
+
5589
+ - \`initialState\` \u2014 optional object seeding local state (per widget instance).
5590
+ - \`root\` \u2014 a single node tree (see node kinds below).
5591
+ - optional \`title\` \u2014 card heading.
5592
+
5593
+ **Bindings:** anywhere a primitive is accepted, you may use \`{ "$bind": "dotted.path" }\` to read from \`initialState\` / local state (e.g. \`{ "$bind": "qty" }\`).
5594
+
5595
+ **Actions:** nodes may attach \`onClick\`, \`onChange\`, or \`onDragEnd\` with one action or an array:
5596
+
5597
+ | Action | Shape | Effect |
5598
+ |---|---|---|
5599
+ | User message | \`{ "kind": "message", "text": "..." }\` or \`{ "kind": "message", "text": { "$bind": "path" } }\` | Sends text as the next user message. |
5600
+ | Set state | \`{ "kind": "set", "path": "foo", "value": 1 }\` | Writes local widget state. |
5601
+ | Toggle boolean | \`{ "kind": "toggle", "path": "enabled" }\` | Flips a boolean at \`path\`. |
5602
+ | Host event | \`{ "kind": "emit", "name": "event-name", "payload": { ... } }\` | Bubbles to the host app (\`onArtifactEvent\` on \`<Thread>\`). |
5603
+
5604
+ ### \`ui\` node palette (\`root.kind\`)
5605
+
5606
+ | \`kind\` | Purpose | Key fields |
5607
+ |---|---|---|
5608
+ | \`box\` | Layout container | \`children\`, \`direction\` (\`row\`/\`col\`), \`gap\`, \`padding\`, \`align\`, \`justify\`, \`wrap\` |
5609
+ | \`text\` | Body text | \`value\`, optional \`muted\`, \`size\`, \`weight\` |
5610
+ | \`heading\` | Heading | \`value\`, optional \`level\` (1\u20134) |
5611
+ | \`badge\` | Pill label | \`value\`, optional \`tone\` (\`default\`, \`primary\`, \`success\`, \`warn\`, \`danger\`) |
5612
+ | \`button\` | Clickable button | \`label\`, optional \`variant\`, \`size\`, \`disabled\`, \`onClick\` |
5613
+ | \`toggle\` | Boolean switch | \`binding\` (state path), optional \`label\`, \`onChange\` |
5614
+ | \`slider\` | Numeric range | \`binding\`, optional \`min\`, \`max\`, \`step\`, \`label\`, \`showValue\`, \`onChange\` |
5615
+ | \`tooltip\` | Hover tooltip | \`content\`, \`child\` (single node), optional \`side\` |
5616
+ | \`draggable\` | Drag gesture | \`child\`, optional \`axis\` (\`x\`/\`y\`/\`both\`), \`snapBack\`, \`onDragEnd\` |
5617
+ | \`custom\` | Host-registered widget | \`name\`, optional \`props\`, \`children\` \u2014 only if the app registered that name |
5618
+
5619
+ ### Example \`ui\` artifact
5620
+
5621
+ \`\`\`json
5622
+ {
5623
+ "type": "ui",
5624
+ "title": "Configure plan",
5625
+ "initialState": { "qty": 1, "premium": false },
5626
+ "root": {
5627
+ "kind": "box",
5628
+ "direction": "col",
5629
+ "gap": 3,
5630
+ "children": [
5631
+ { "kind": "heading", "value": "Choose quantity", "level": 3 },
5632
+ {
5633
+ "kind": "tooltip",
5634
+ "content": "Drag to adjust quantity",
5635
+ "child": {
5636
+ "kind": "slider",
5637
+ "binding": "qty",
5638
+ "min": 1,
5639
+ "max": 50,
5640
+ "label": "Quantity",
5641
+ "onChange": { "kind": "emit", "name": "qty-changed" }
5642
+ }
5643
+ },
5644
+ { "kind": "toggle", "binding": "premium", "label": "Premium support" },
5645
+ {
5646
+ "kind": "button",
5647
+ "label": "Confirm",
5648
+ "onClick": { "kind": "message", "text": { "$bind": "qty" } }
5649
+ }
5650
+ ]
5651
+ }
5652
+ }
5653
+ \`\`\`
5654
+
5655
+ ### Rules
5656
+
5657
+ - Always set \`type\` to a built-in value above unless the app documented a custom type.
5658
+ - Prefer \`ui\` over \`html\` when actions must bubble to the host chat (\`message\`, \`emit\`).
5659
+ - Prefer \`question\` for simple A/B/C choices; use \`ui\` when you need sliders, toggles, drag, or multi-control layouts.
5660
+ - Keep \`data\` arrays reasonably small (charts/tables).
5661
+
5662
+ ### After calling an artifact tool (critical)
5663
+
5664
+ When you call a tool that returns an artifact (\`make_chart\`, \`ask_question\`, \`show_table\`, \`show_html\`, \`make_ui_demo\`, etc.):
5665
+
5666
+ 1. **Do not** paste, quote, paraphrase as JSON, or fence the tool return value in your assistant message. The chat UI already renders it from the tool result.
5667
+ 2. **Do not** emit a matching \`\`\`timbal-artifact\`\`\` block for the same payload \u2014 pick **one** channel (tool result only).
5668
+ 3. Your follow-up text should be **empty**, or at most **one short sentence** (e.g. "Pick an option above." / "Try the controls."). Never include \`type\`, \`options\`, \`data\`, or dict/JSON syntax.
5669
+ 4. Treat the widget as visible to the user; refer to it as "above" / "the chart" / "the choices" \u2014 never reproduce its contents.
5670
+ `.trim();
5671
+
5672
+ // src/auth/guard.tsx
5673
+ import { Loader2 } from "lucide-react";
5674
+ import { jsx as jsx45 } from "react/jsx-runtime";
5675
+ var AuthGuard = ({
5676
+ children,
5677
+ requireAuth = false,
5678
+ enabled = true
5679
+ }) => {
5680
+ const { isAuthenticated, loading, isEmbedded } = useSession();
5681
+ if (!enabled) {
5682
+ return children;
5683
+ }
5684
+ if (loading) {
5685
+ return /* @__PURE__ */ jsx45("div", { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsx45(Loader2, { className: "w-8 h-8 animate-spin" }) });
5686
+ }
5687
+ if (requireAuth && !isAuthenticated && !isEmbedded) {
5688
+ const returnTo = encodeURIComponent(
5689
+ window.location.pathname + window.location.search
5690
+ );
5691
+ window.location.href = `/api/auth/login?return_to=${returnTo}`;
5692
+ return null;
5693
+ }
5694
+ return children;
5695
+ };
5696
+
5697
+ // src/index.ts
5698
+ import {
5699
+ ThreadPrimitive as ThreadPrimitive2,
5700
+ MessagePrimitive as MessagePrimitive3,
5701
+ ComposerPrimitive as ComposerPrimitive4,
5702
+ ActionBarPrimitive as ActionBarPrimitive2,
5703
+ AuiIf as AuiIf3,
5704
+ AssistantRuntimeProvider as AssistantRuntimeProvider2,
5705
+ useThread as useThread4,
5706
+ useThreadRuntime as useThreadRuntime4,
5707
+ useMessageRuntime,
5708
+ useComposerRuntime as useComposerRuntime2
5709
+ } from "@assistant-ui/react";
3793
5710
  export {
3794
5711
  ARTIFACT_AGENT_INSTRUCTIONS,
3795
5712
  ARTIFACT_FENCE_LANGUAGES,
@@ -3798,6 +5715,7 @@ export {
3798
5715
  ArtifactRegistryProvider,
3799
5716
  ArtifactView,
3800
5717
  AssistantRuntimeProvider2 as AssistantRuntimeProvider,
5718
+ AuiIf3 as AuiIf,
3801
5719
  AuthGuard,
3802
5720
  Avatar,
3803
5721
  AvatarFallback,
@@ -3805,8 +5723,6 @@ export {
3805
5723
  Button,
3806
5724
  ChartArtifactView,
3807
5725
  Composer,
3808
- ComposerAddAttachment,
3809
- ComposerAttachments,
3810
5726
  ComposerPrimitive4 as ComposerPrimitive,
3811
5727
  DEFAULT_UPLOAD_ACCEPT,
3812
5728
  Dialog,
@@ -3820,17 +5736,21 @@ export {
3820
5736
  JsonArtifactView,
3821
5737
  MarkdownText,
3822
5738
  MessagePrimitive3 as MessagePrimitive,
5739
+ ModeToggle,
3823
5740
  QuestionArtifactView,
3824
5741
  SessionProvider,
3825
5742
  Shimmer,
5743
+ StudioSidebar,
5744
+ StudioWelcome,
3826
5745
  Suggestions,
3827
- syntax_highlighter_default as SyntaxHighlighter,
3828
5746
  TableArtifactView,
3829
5747
  Thread,
3830
5748
  ThreadPrimitive2 as ThreadPrimitive,
3831
5749
  TimbalChat,
3832
5750
  TimbalChatShell,
5751
+ TimbalMark,
3833
5752
  TimbalRuntimeProvider,
5753
+ TimbalStudioShell,
3834
5754
  ToolArtifactFallback,
3835
5755
  ToolFallback,
3836
5756
  Tooltip,
@@ -3842,10 +5762,8 @@ export {
3842
5762
  UiCustomNodeRegistryProvider,
3843
5763
  UiEventProvider,
3844
5764
  UiNodeView,
3845
- UserMessageAttachments,
3846
5765
  WorkforceSelector,
3847
5766
  authFetch,
3848
- buttonVariants,
3849
5767
  clearTokens,
3850
5768
  cn,
3851
5769
  createDefaultAttachmentAdapter,
@@ -3871,12 +5789,14 @@ export {
3871
5789
  useArtifactRegistry,
3872
5790
  useComposerRuntime2 as useComposerRuntime,
3873
5791
  useMessageRuntime,
5792
+ useOptionalSession,
3874
5793
  useResolvedSuggestions,
3875
5794
  useSession,
3876
- useThread,
5795
+ useThread4 as useThread,
3877
5796
  useThreadRuntime4 as useThreadRuntime,
3878
5797
  useTimbalRuntime,
3879
5798
  useTimbalStream,
5799
+ useToolRunning,
3880
5800
  useUiCustomNodeRegistry,
3881
5801
  useUiDispatch,
3882
5802
  useUiEventEmitter,