@lukeashford/aurelius 4.1.0 → 4.3.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.mjs CHANGED
@@ -1320,42 +1320,84 @@ function getFileIcon(type) {
1320
1320
  }
1321
1321
  return File;
1322
1322
  }
1323
- var statusStyles = {
1323
+ var statusBorderClass = {
1324
1324
  pending: "border-silver/30",
1325
1325
  uploading: "border-gold/50",
1326
- complete: "border-success/50",
1327
- error: "border-error/50"
1326
+ uploaded: "border-info/50",
1327
+ analyzing: "border-info/50",
1328
+ analyzed: "border-success/50",
1329
+ upload_failed: "border-error/50",
1330
+ analysis_failed: "border-error/50"
1328
1331
  };
1332
+ var statusHoverLabel = {
1333
+ pending: null,
1334
+ uploading: "Uploading...",
1335
+ uploaded: "Upload complete. Analyzing...",
1336
+ analyzing: "Upload complete. Analyzing...",
1337
+ analyzed: null,
1338
+ upload_failed: "Upload failed. Remove and try again.",
1339
+ analysis_failed: "Analysis failed. Provide a description in your next message."
1340
+ };
1341
+ function isErrorStatus(status) {
1342
+ return status === "upload_failed" || status === "analysis_failed";
1343
+ }
1329
1344
  var FileChip = React15.forwardRef(
1330
1345
  ({
1331
1346
  name,
1332
1347
  size,
1333
1348
  type,
1334
- status = "complete",
1349
+ status = "analyzed",
1335
1350
  previewUrl,
1336
1351
  onRemove,
1337
1352
  removable = true,
1338
1353
  error,
1354
+ artifactId,
1355
+ onOpen,
1339
1356
  className,
1357
+ title,
1340
1358
  ...rest
1341
1359
  }, ref) => {
1342
1360
  const Icon = getFileIcon(type);
1343
1361
  const isImage = type?.startsWith("image/");
1344
1362
  const showPreview = isImage && previewUrl;
1363
+ const clickable = !!(artifactId && onOpen);
1364
+ const hoverLabel = statusHoverLabel[status];
1365
+ const tooltip = title ?? hoverLabel ?? name;
1366
+ const showError = isErrorStatus(status);
1367
+ const handleClick = () => {
1368
+ if (clickable) {
1369
+ onOpen(artifactId);
1370
+ }
1371
+ };
1372
+ const handleKeyDown = (e) => {
1373
+ if (!clickable) {
1374
+ return;
1375
+ }
1376
+ if (e.key === "Enter" || e.key === " ") {
1377
+ e.preventDefault();
1378
+ onOpen(artifactId);
1379
+ }
1380
+ };
1345
1381
  return /* @__PURE__ */ React15.createElement(
1346
1382
  "div",
1347
1383
  {
1384
+ ...rest,
1348
1385
  ref,
1349
1386
  className: cx(
1350
1387
  "group relative inline-flex items-center gap-2 px-2 py-1.5",
1351
1388
  "bg-charcoal border text-sm text-white",
1352
1389
  "transition-colors duration-150",
1353
- statusStyles[status],
1354
- status === "error" && "bg-error/10",
1390
+ statusBorderClass[status],
1391
+ showError && "bg-error/10",
1392
+ clickable && "cursor-pointer hover:bg-graphite",
1355
1393
  className
1356
1394
  ),
1357
- role: "listitem",
1358
- ...rest
1395
+ role: clickable ? "button" : "listitem",
1396
+ tabIndex: clickable ? 0 : void 0,
1397
+ onClick: clickable ? handleClick : void 0,
1398
+ onKeyDown: clickable ? handleKeyDown : void 0,
1399
+ title: tooltip,
1400
+ "aria-label": hoverLabel ? `${name}: ${hoverLabel}` : name
1359
1401
  },
1360
1402
  showPreview ? /* @__PURE__ */ React15.createElement("div", { className: "w-8 h-8 flex-shrink-0 overflow-hidden bg-slate" }, /* @__PURE__ */ React15.createElement(
1361
1403
  "img",
@@ -1366,10 +1408,11 @@ var FileChip = React15.forwardRef(
1366
1408
  }
1367
1409
  )) : /* @__PURE__ */ React15.createElement(Icon, { className: cx(
1368
1410
  "w-4 h-4 flex-shrink-0",
1369
- status === "error" ? "text-error" : "text-silver"
1411
+ showError ? "text-error" : "text-silver"
1370
1412
  ) }),
1371
- /* @__PURE__ */ React15.createElement("div", { className: "flex flex-col min-w-0 flex-1" }, /* @__PURE__ */ React15.createElement("span", { className: "truncate max-w-40", title: name }, name), size !== void 0 && status !== "error" && /* @__PURE__ */ React15.createElement("span", { className: "text-xs text-silver/60" }, formatBytes(size)), status === "error" && error && /* @__PURE__ */ React15.createElement("span", { className: "text-xs text-error truncate", title: error }, error)),
1413
+ /* @__PURE__ */ React15.createElement("div", { className: "flex flex-col min-w-0 flex-1" }, /* @__PURE__ */ React15.createElement("span", { className: "truncate max-w-40", title: name }, name), size !== void 0 && !showError && /* @__PURE__ */ React15.createElement("span", { className: "text-xs text-silver/60" }, formatBytes(size)), showError && error && /* @__PURE__ */ React15.createElement("span", { className: "text-xs text-error truncate", title: error }, error)),
1372
1414
  status === "uploading" && /* @__PURE__ */ React15.createElement(Loader2, { className: "w-3.5 h-3.5 text-gold animate-spin flex-shrink-0" }),
1415
+ (status === "uploaded" || status === "analyzing") && /* @__PURE__ */ React15.createElement(Loader2, { className: "w-3.5 h-3.5 text-info animate-spin flex-shrink-0" }),
1373
1416
  status === "pending" && /* @__PURE__ */ React15.createElement("div", { className: "w-2 h-2 rounded-full bg-silver/50 flex-shrink-0" }),
1374
1417
  removable && onRemove && /* @__PURE__ */ React15.createElement(
1375
1418
  "button",
@@ -1402,6 +1445,7 @@ var AttachmentPreview = React16.forwardRef(
1402
1445
  onRemove,
1403
1446
  removable = true,
1404
1447
  maxVisible,
1448
+ onOpen,
1405
1449
  className,
1406
1450
  ...rest
1407
1451
  }, ref) => {
@@ -1430,7 +1474,9 @@ var AttachmentPreview = React16.forwardRef(
1430
1474
  previewUrl: attachment.previewUrl,
1431
1475
  error: attachment.error,
1432
1476
  removable,
1433
- onRemove: onRemove ? () => onRemove(attachment.id) : void 0
1477
+ onRemove: onRemove ? () => onRemove(attachment.id) : void 0,
1478
+ artifactId: attachment.artifactId,
1479
+ onOpen
1434
1480
  }
1435
1481
  )),
1436
1482
  hiddenCount > 0 && /* @__PURE__ */ React16.createElement(
@@ -3712,12 +3758,14 @@ import { Check as Check3, ChevronLeft as ChevronLeft2, ChevronRight as ChevronRi
3712
3758
  import React54, { useMemo } from "react";
3713
3759
  import DOMPurify from "dompurify";
3714
3760
  import { marked } from "marked";
3715
- DOMPurify.addHook("afterSanitizeAttributes", (node) => {
3716
- if (node.tagName === "A") {
3717
- node.setAttribute("target", "_blank");
3718
- node.setAttribute("rel", "noopener noreferrer");
3719
- }
3720
- });
3761
+ if (typeof window !== "undefined" && typeof DOMPurify.addHook === "function") {
3762
+ DOMPurify.addHook("afterSanitizeAttributes", (node) => {
3763
+ if (node.tagName === "A") {
3764
+ node.setAttribute("target", "_blank");
3765
+ node.setAttribute("rel", "noopener noreferrer");
3766
+ }
3767
+ });
3768
+ }
3721
3769
  var DEFAULT_SANITIZE_CONFIG = {
3722
3770
  ALLOWED_TAGS: [
3723
3771
  "h1",
@@ -3794,6 +3842,9 @@ function injectStreamingCursor(html, cursorClassName) {
3794
3842
  CURSOR_BASE_CLASSES,
3795
3843
  cursorClassName
3796
3844
  )}" aria-hidden="true"></span>`;
3845
+ if (typeof DOMParser === "undefined") {
3846
+ return html + cursorHtml;
3847
+ }
3797
3848
  const parser = new DOMParser();
3798
3849
  const doc = parser.parseFromString(`<div>${html}</div>`, "text/html");
3799
3850
  const container = doc.body.firstChild;
@@ -3830,7 +3881,7 @@ var MarkdownContent = React54.forwardRef(
3830
3881
  } else {
3831
3882
  htmlContent = content;
3832
3883
  }
3833
- const sanitized = htmlContent ? DOMPurify.sanitize(htmlContent, config) : "";
3884
+ const sanitized = htmlContent && typeof DOMPurify.sanitize === "function" ? DOMPurify.sanitize(htmlContent, config) : htmlContent || "";
3834
3885
  if (isStreaming) {
3835
3886
  return injectStreamingCursor(sanitized, cursorClassName);
3836
3887
  }
@@ -3885,6 +3936,8 @@ var Message = React55.forwardRef(
3885
3936
  branchInfo,
3886
3937
  actions,
3887
3938
  hideActions,
3939
+ attachments,
3940
+ onAttachmentOpen,
3888
3941
  ...rest
3889
3942
  }, ref) => {
3890
3943
  const isUser = variant === "user";
@@ -3952,6 +4005,14 @@ var Message = React55.forwardRef(
3952
4005
  ),
3953
4006
  ...rest
3954
4007
  },
4008
+ attachments && attachments.length > 0 && /* @__PURE__ */ React55.createElement("div", { className: cx("mb-1.5", isUser ? "self-end" : "self-start") }, /* @__PURE__ */ React55.createElement(
4009
+ AttachmentPreview,
4010
+ {
4011
+ attachments,
4012
+ removable: false,
4013
+ onOpen: onAttachmentOpen
4014
+ }
4015
+ )),
3955
4016
  isUser && isEditing ? /* @__PURE__ */ React55.createElement("div", { className: "w-full max-w-11/12" }, /* @__PURE__ */ React55.createElement("div", { className: "relative bg-gold" }, /* @__PURE__ */ React55.createElement(
3956
4017
  "textarea",
3957
4018
  {
@@ -4202,6 +4263,7 @@ var ThinkingIndicator = React57.forwardRef(
4202
4263
  isVisible = true,
4203
4264
  phraseInterval = 2500,
4204
4265
  phrases = THINKING_PHRASES,
4266
+ manualLabel,
4205
4267
  className,
4206
4268
  ...rest
4207
4269
  }, ref) => {
@@ -4209,8 +4271,9 @@ var ThinkingIndicator = React57.forwardRef(
4209
4271
  () => Math.floor(Math.random() * phrases.length)
4210
4272
  );
4211
4273
  const [isTransitioning, setIsTransitioning] = useState13(false);
4274
+ const isManual = manualLabel !== void 0;
4212
4275
  useEffect8(() => {
4213
- if (!isVisible || phrases.length <= 1) {
4276
+ if (!isVisible || isManual || phrases.length <= 1) {
4214
4277
  return;
4215
4278
  }
4216
4279
  let fadeTimeout = null;
@@ -4228,7 +4291,7 @@ var ThinkingIndicator = React57.forwardRef(
4228
4291
  clearTimeout(fadeTimeout);
4229
4292
  }
4230
4293
  };
4231
- }, [isVisible, phrases.length, phraseInterval]);
4294
+ }, [isVisible, isManual, phrases.length, phraseInterval]);
4232
4295
  if (!isVisible) {
4233
4296
  return null;
4234
4297
  }
@@ -4265,7 +4328,7 @@ var ThinkingIndicator = React57.forwardRef(
4265
4328
  style: { animationDelay: "300ms" }
4266
4329
  }
4267
4330
  )),
4268
- /* @__PURE__ */ React57.createElement(
4331
+ isManual ? /* @__PURE__ */ React57.createElement("span", { className: "text-sm italic" }, manualLabel) : /* @__PURE__ */ React57.createElement(
4269
4332
  "span",
4270
4333
  {
4271
4334
  className: cx(
@@ -4289,19 +4352,22 @@ import {
4289
4352
  GitBranch as GitBranch2,
4290
4353
  GitCommitVertical,
4291
4354
  GitMerge,
4292
- PencilLine
4355
+ PencilLine,
4356
+ Upload
4293
4357
  } from "lucide-react";
4294
4358
  var KIND_ICONS = {
4295
4359
  task: GitBranch2,
4296
4360
  submit: GitMerge,
4297
4361
  rename: PencilLine,
4298
- init: GitCommitVertical
4362
+ init: GitCommitVertical,
4363
+ ingest: Upload
4299
4364
  };
4300
4365
  var KIND_ARIA_LABELS = {
4301
4366
  task: "Task checkpoint",
4302
4367
  submit: "Submit checkpoint",
4303
4368
  rename: "Rename checkpoint",
4304
- init: "Project head checkpoint"
4369
+ init: "Project head checkpoint",
4370
+ ingest: "Upload batch checkpoint"
4305
4371
  };
4306
4372
  var Checkpoint = React58.forwardRef(
4307
4373
  function Checkpoint2({ name, executionKind, status = "completed", isActive, muted, branchInfo, onJumpHere }, ref) {
@@ -4450,7 +4516,16 @@ GreyedDivider.displayName = "GreyedDivider";
4450
4516
 
4451
4517
  // src/components/chat/ChatView.tsx
4452
4518
  var ChatView = React60.forwardRef(
4453
- function ChatView2({ items, latestUserMessageIndex, isStreaming, isThinking, onScroll, className, ...rest }, ref) {
4519
+ function ChatView2({
4520
+ items,
4521
+ latestUserMessageIndex,
4522
+ isStreaming,
4523
+ isThinking,
4524
+ thinkingLabel,
4525
+ onScroll,
4526
+ className,
4527
+ ...rest
4528
+ }, ref) {
4454
4529
  const { containerRef, anchorRef, scrollToAnchor } = useScrollAnchor({
4455
4530
  behavior: "smooth",
4456
4531
  block: "start"
@@ -4533,7 +4608,7 @@ var ChatView = React60.forwardRef(
4533
4608
  }
4534
4609
  )
4535
4610
  );
4536
- }), showThinking && /* @__PURE__ */ React60.createElement(ThinkingIndicator, { isVisible: true })),
4611
+ }), showThinking && /* @__PURE__ */ React60.createElement(ThinkingIndicator, { isVisible: true, manualLabel: thinkingLabel })),
4537
4612
  /* @__PURE__ */ React60.createElement(
4538
4613
  "div",
4539
4614
  {
@@ -4718,8 +4793,8 @@ var ChatInput = React61.forwardRef(
4718
4793
  );
4719
4794
  const isCentered = position === "centered";
4720
4795
  const hasAttachments = attachments.length > 0;
4721
- const isUploading = attachments.some((a) => a.status === "uploading");
4722
- const canSubmit = value.trim() && !disabled && !isStreaming && !isUploading;
4796
+ const isUploadIncomplete = attachments.some((a) => a.status === "pending" || a.status === "uploading" || a.status === "upload_failed");
4797
+ const canSubmit = value.trim() && !disabled && !isStreaming && !isUploadIncomplete;
4723
4798
  return /* @__PURE__ */ React61.createElement(
4724
4799
  "div",
4725
4800
  {
@@ -4856,7 +4931,7 @@ var ChatInput = React61.forwardRef(
4856
4931
  ChatInput.displayName = "ChatInput";
4857
4932
 
4858
4933
  // src/components/chat/ArtifactsPanel.tsx
4859
- import React71, { useCallback as useCallback17, useEffect as useEffect12, useRef as useRef11, useState as useState17 } from "react";
4934
+ import React71, { useCallback as useCallback18, useEffect as useEffect13, useRef as useRef12, useState as useState18 } from "react";
4860
4935
  import { Image } from "lucide-react";
4861
4936
 
4862
4937
  // src/components/ArtifactCard.tsx
@@ -5514,10 +5589,69 @@ var ArtifactVariantStack = React70.forwardRef(
5514
5589
  );
5515
5590
  ArtifactVariantStack.displayName = "ArtifactVariantStack";
5516
5591
 
5592
+ // src/components/chat/hooks/useResizable.ts
5593
+ import { useCallback as useCallback16, useEffect as useEffect12, useRef as useRef11, useState as useState16 } from "react";
5594
+ function useResizable({
5595
+ initialWidthPercent,
5596
+ minWidthPercent,
5597
+ maxWidthPercent,
5598
+ direction
5599
+ }) {
5600
+ const [widthPercent, setWidthPercent] = useState16(initialWidthPercent);
5601
+ const [isResizing, setIsResizing] = useState16(false);
5602
+ const lastX = useRef11(null);
5603
+ const startResizing = useCallback16((e) => {
5604
+ e.preventDefault();
5605
+ setIsResizing(true);
5606
+ lastX.current = e.clientX;
5607
+ }, []);
5608
+ const stopResizing = useCallback16(() => {
5609
+ setIsResizing(false);
5610
+ lastX.current = null;
5611
+ }, []);
5612
+ const resize = useCallback16(
5613
+ (e) => {
5614
+ if (!isResizing || lastX.current === null) {
5615
+ return;
5616
+ }
5617
+ const deltaX = e.clientX - lastX.current;
5618
+ const factor = direction === "right" ? 1 : -1;
5619
+ const deltaPercent = deltaX / window.innerWidth * 100;
5620
+ setWidthPercent((prevPercent) => {
5621
+ const newPercent = prevPercent + deltaPercent * factor;
5622
+ return Math.min(Math.max(newPercent, minWidthPercent), maxWidthPercent);
5623
+ });
5624
+ lastX.current = e.clientX;
5625
+ },
5626
+ [isResizing, direction, minWidthPercent, maxWidthPercent]
5627
+ );
5628
+ useEffect12(() => {
5629
+ if (isResizing) {
5630
+ window.addEventListener("mousemove", resize);
5631
+ window.addEventListener("mouseup", stopResizing);
5632
+ document.body.style.cursor = "col-resize";
5633
+ document.body.style.userSelect = "none";
5634
+ } else {
5635
+ window.removeEventListener("mousemove", resize);
5636
+ window.removeEventListener("mouseup", stopResizing);
5637
+ document.body.style.cursor = "";
5638
+ document.body.style.userSelect = "";
5639
+ }
5640
+ return () => {
5641
+ window.removeEventListener("mousemove", resize);
5642
+ window.removeEventListener("mouseup", stopResizing);
5643
+ document.body.style.cursor = "";
5644
+ document.body.style.userSelect = "";
5645
+ };
5646
+ }, [isResizing, resize, stopResizing]);
5647
+ const width = `${widthPercent}vw`;
5648
+ return { width, widthPercent, isResizing, startResizing };
5649
+ }
5650
+
5517
5651
  // src/components/chat/hooks/useArtifactTreeNavigation.ts
5518
- import { useCallback as useCallback16, useMemo as useMemo2, useState as useState16 } from "react";
5652
+ import { useCallback as useCallback17, useMemo as useMemo2, useState as useState17 } from "react";
5519
5653
  function useArtifactTreeNavigation(rootNodes) {
5520
- const [stack, setStack] = useState16([]);
5654
+ const [stack, setStack] = useState17([]);
5521
5655
  const currentNodes = useMemo2(() => {
5522
5656
  if (stack.length === 0) return rootNodes;
5523
5657
  return stack[stack.length - 1].children;
@@ -5530,13 +5664,13 @@ function useArtifactTreeNavigation(rootNodes) {
5530
5664
  return entries;
5531
5665
  }, [stack]);
5532
5666
  const isAtRoot = stack.length === 0;
5533
- const navigateInto = useCallback16((node) => {
5667
+ const navigateInto = useCallback17((node) => {
5534
5668
  setStack((prev) => [...prev, node]);
5535
5669
  }, []);
5536
- const navigateTo = useCallback16((index) => {
5670
+ const navigateTo = useCallback17((index) => {
5537
5671
  setStack((prev) => prev.slice(0, index));
5538
5672
  }, []);
5539
- const navigateBack = useCallback16(() => {
5673
+ const navigateBack = useCallback17(() => {
5540
5674
  setStack((prev) => prev.slice(0, -1));
5541
5675
  }, []);
5542
5676
  return {
@@ -5556,7 +5690,7 @@ function ArtifactModal({
5556
5690
  onClose
5557
5691
  }) {
5558
5692
  useEscapeKey(onClose);
5559
- const handleBackdropClick = useCallback17((e) => {
5693
+ const handleBackdropClick = useCallback18((e) => {
5560
5694
  if (e.target === e.currentTarget) {
5561
5695
  onClose();
5562
5696
  }
@@ -5637,6 +5771,20 @@ function ArtifactModal({
5637
5771
  )
5638
5772
  );
5639
5773
  }
5774
+ function findArtifactInNodes(nodes, artifactId) {
5775
+ for (const node of nodes) {
5776
+ if (node.type === "ARTIFACT" && node.artifact?.id === artifactId) {
5777
+ return node.artifact;
5778
+ }
5779
+ if (node.children && node.children.length > 0) {
5780
+ const found = findArtifactInNodes(node.children, artifactId);
5781
+ if (found) {
5782
+ return found;
5783
+ }
5784
+ }
5785
+ }
5786
+ return null;
5787
+ }
5640
5788
  function NodeRenderer({
5641
5789
  node,
5642
5790
  loading,
@@ -5672,31 +5820,48 @@ var ArtifactsPanel = React71.forwardRef(
5672
5820
  ({
5673
5821
  nodes,
5674
5822
  loading,
5823
+ openArtifactId,
5824
+ onArtifactClosed,
5675
5825
  className,
5676
5826
  ...rest
5677
5827
  }, ref) => {
5678
- const [expandedArtifact, setExpandedArtifact] = useState17(null);
5679
- const [zoomIndex, setZoomIndex] = useState17(ZOOM_LEVELS.length - 1);
5828
+ const [expandedArtifact, setExpandedArtifact] = useState18(null);
5829
+ const [zoomIndex, setZoomIndex] = useState18(ZOOM_LEVELS.length - 1);
5680
5830
  const treeNav = useArtifactTreeNavigation(nodes || []);
5681
5831
  const hasNodes = !!nodes && nodes.length > 0;
5682
- const handleExpandArtifact = useCallback17((artifact) => {
5832
+ const handleExpandArtifact = useCallback18((artifact) => {
5683
5833
  setExpandedArtifact(artifact);
5684
5834
  }, []);
5685
- const handleGroupClick = useCallback17((node) => {
5835
+ const handleGroupClick = useCallback18((node) => {
5686
5836
  treeNav.navigateInto(node);
5687
5837
  }, [treeNav]);
5688
- const zoomIn = useCallback17(() => {
5838
+ useEffect13(() => {
5839
+ if (!openArtifactId || !nodes) {
5840
+ return;
5841
+ }
5842
+ const found = findArtifactInNodes(nodes, openArtifactId);
5843
+ if (found) {
5844
+ setExpandedArtifact(found);
5845
+ }
5846
+ }, [openArtifactId, nodes]);
5847
+ const handleModalClose = useCallback18(() => {
5848
+ setExpandedArtifact(null);
5849
+ onArtifactClosed?.();
5850
+ }, [onArtifactClosed]);
5851
+ const zoomIn = useCallback18(() => {
5689
5852
  setZoomIndex((prev) => Math.min(prev + 1, ZOOM_LEVELS.length - 1));
5690
5853
  }, []);
5691
- const zoomOut = useCallback17(() => {
5854
+ const zoomOut = useCallback18(() => {
5692
5855
  setZoomIndex((prev) => Math.max(prev - 1, 0));
5693
5856
  }, []);
5694
5857
  const currentZoom = ZOOM_LEVELS[zoomIndex];
5695
- const contentRef = useRef11(null);
5696
- const [contentHeight, setContentHeight] = useState17(void 0);
5697
- useEffect12(() => {
5858
+ const contentRef = useRef12(null);
5859
+ const [contentHeight, setContentHeight] = useState18(void 0);
5860
+ useEffect13(() => {
5698
5861
  const el = contentRef.current;
5699
- if (!el) return;
5862
+ if (!el) {
5863
+ return;
5864
+ }
5700
5865
  const observer = new ResizeObserver(([entry]) => {
5701
5866
  setContentHeight(entry.contentRect.height);
5702
5867
  });
@@ -5740,7 +5905,15 @@ var ArtifactsPanel = React71.forwardRef(
5740
5905
  },
5741
5906
  "\u2212"
5742
5907
  ),
5743
- /* @__PURE__ */ React71.createElement("span", { className: "text-xs text-silver w-8 text-center tabular-nums", "data-testid": "zoom-level" }, Math.round(currentZoom * 100), "%"),
5908
+ /* @__PURE__ */ React71.createElement(
5909
+ "span",
5910
+ {
5911
+ className: "text-xs text-silver w-8 text-center tabular-nums",
5912
+ "data-testid": "zoom-level"
5913
+ },
5914
+ Math.round(currentZoom * 100),
5915
+ "%"
5916
+ ),
5744
5917
  /* @__PURE__ */ React71.createElement(
5745
5918
  "button",
5746
5919
  {
@@ -5815,7 +5988,7 @@ var ArtifactsPanel = React71.forwardRef(
5815
5988
  ArtifactModal,
5816
5989
  {
5817
5990
  artifact: expandedArtifact,
5818
- onClose: () => setExpandedArtifact(null)
5991
+ onClose: handleModalClose
5819
5992
  }
5820
5993
  ));
5821
5994
  }
@@ -5852,7 +6025,7 @@ var ArtifactsPanelToggle = React71.forwardRef(({ artifactCount = 0, onExpand, cl
5852
6025
  ArtifactsPanelToggle.displayName = "ArtifactsPanelToggle";
5853
6026
 
5854
6027
  // src/components/chat/HistoryPanel.tsx
5855
- import React72, { useCallback as useCallback18, useEffect as useEffect13, useMemo as useMemo3, useRef as useRef12, useState as useState18 } from "react";
6028
+ import React72, { useCallback as useCallback19, useEffect as useEffect14, useMemo as useMemo3, useRef as useRef13, useState as useState19 } from "react";
5856
6029
  import { ChevronDown as ChevronDown2, Pencil as Pencil2 } from "lucide-react";
5857
6030
  function parseTimestamp(ts) {
5858
6031
  if (ts == null) {
@@ -5895,9 +6068,9 @@ function ProjectFilter({
5895
6068
  onChange,
5896
6069
  className
5897
6070
  }) {
5898
- const [open, setOpen] = useState18(false);
5899
- const ref = useRef12(null);
5900
- const closeFilter = useCallback18(() => setOpen(false), []);
6071
+ const [open, setOpen] = useState19(false);
6072
+ const ref = useRef13(null);
6073
+ const closeFilter = useCallback19(() => setOpen(false), []);
5901
6074
  useClickOutside(ref, closeFilter, open);
5902
6075
  const label = value ?? "All projects";
5903
6076
  return /* @__PURE__ */ React72.createElement("div", { className: cx("relative min-w-0", className), ref }, /* @__PURE__ */ React72.createElement(
@@ -5972,28 +6145,28 @@ function ConversationRow({
5972
6145
  onSelect,
5973
6146
  onRename
5974
6147
  }) {
5975
- const [isEditing, setIsEditing] = useState18(false);
5976
- const [draft, setDraft] = useState18(conversation.title);
5977
- const inputRef = useRef12(null);
5978
- useEffect13(() => {
6148
+ const [isEditing, setIsEditing] = useState19(false);
6149
+ const [draft, setDraft] = useState19(conversation.title);
6150
+ const inputRef = useRef13(null);
6151
+ useEffect14(() => {
5979
6152
  if (isEditing && inputRef.current) {
5980
6153
  inputRef.current.focus();
5981
6154
  inputRef.current.select();
5982
6155
  }
5983
6156
  }, [isEditing]);
5984
- const startEdit = useCallback18((e) => {
6157
+ const startEdit = useCallback19((e) => {
5985
6158
  e.stopPropagation();
5986
6159
  setDraft(conversation.title);
5987
6160
  setIsEditing(true);
5988
6161
  }, [conversation.title]);
5989
- const commit = useCallback18(() => {
6162
+ const commit = useCallback19(() => {
5990
6163
  const trimmed = draft.trim();
5991
6164
  if (trimmed && trimmed !== conversation.title) {
5992
6165
  onRename?.(conversation.id, trimmed);
5993
6166
  }
5994
6167
  setIsEditing(false);
5995
6168
  }, [draft, conversation.id, conversation.title, onRename]);
5996
- const cancel = useCallback18(() => {
6169
+ const cancel = useCallback19(() => {
5997
6170
  setDraft(conversation.title);
5998
6171
  setIsEditing(false);
5999
6172
  }, [conversation.title]);
@@ -6077,7 +6250,7 @@ function HistoryPanel({
6077
6250
  onNewChat,
6078
6251
  onRenameConversation
6079
6252
  }) {
6080
- const [projectFilter, setProjectFilter] = useState18(null);
6253
+ const [projectFilter, setProjectFilter] = useState19(null);
6081
6254
  const projects = useMemo3(() => {
6082
6255
  const set = /* @__PURE__ */ new Set();
6083
6256
  for (const c of conversations) {
@@ -6087,7 +6260,7 @@ function HistoryPanel({
6087
6260
  }
6088
6261
  return Array.from(set).sort((a, b) => a.localeCompare(b));
6089
6262
  }, [conversations]);
6090
- useEffect13(() => {
6263
+ useEffect14(() => {
6091
6264
  if (projectFilter && !projects.includes(projectFilter)) {
6092
6265
  setProjectFilter(null);
6093
6266
  }
@@ -6143,7 +6316,7 @@ function HistoryPanel({
6143
6316
  }
6144
6317
 
6145
6318
  // src/components/chat/TodosList.tsx
6146
- import React73, { useCallback as useCallback19, useMemo as useMemo4, useState as useState19 } from "react";
6319
+ import React73, { useCallback as useCallback20, useMemo as useMemo4, useState as useState20 } from "react";
6147
6320
  import { Loader2 as Loader22, Square as Square2 } from "lucide-react";
6148
6321
  var TASK_STATUSES = {
6149
6322
  PENDING: "pending",
@@ -6222,8 +6395,8 @@ function hasInProgressTask(tasks) {
6222
6395
  var TodosList = React73.forwardRef(
6223
6396
  ({ tasks, title = "Tasks", onStopAllTasks, className, ...rest }, ref) => {
6224
6397
  const sortedTasks = useMemo4(() => sortTasks(tasks), [tasks]);
6225
- const [isStopping, setIsStopping] = useState19(false);
6226
- const handleStopClick = useCallback19(async () => {
6398
+ const [isStopping, setIsStopping] = useState20(false);
6399
+ const handleStopClick = useCallback20(async () => {
6227
6400
  if (!onStopAllTasks || isStopping) {
6228
6401
  return;
6229
6402
  }
@@ -6348,7 +6521,7 @@ var ToolSidebar = React74.forwardRef(
6348
6521
  ToolSidebar.displayName = "ToolSidebar";
6349
6522
 
6350
6523
  // src/components/chat/ToolPanelContainer.tsx
6351
- import React75, { useCallback as useCallback20, useEffect as useEffect14, useRef as useRef13, useState as useState20 } from "react";
6524
+ import React75, { useCallback as useCallback21, useEffect as useEffect15, useRef as useRef14, useState as useState21 } from "react";
6352
6525
  var ToolPanelContainer = React75.forwardRef(
6353
6526
  ({
6354
6527
  topContent,
@@ -6360,21 +6533,21 @@ var ToolPanelContainer = React75.forwardRef(
6360
6533
  initialTopPercent = 60,
6361
6534
  ...rest
6362
6535
  }, ref) => {
6363
- const [topPercent, setTopPercent] = useState20(initialTopPercent);
6364
- const [isResizingHeight, setIsResizingHeight] = useState20(false);
6365
- const containerRef = useRef13(null);
6366
- const lastY = useRef13(null);
6536
+ const [topPercent, setTopPercent] = useState21(initialTopPercent);
6537
+ const [isResizingHeight, setIsResizingHeight] = useState21(false);
6538
+ const containerRef = useRef14(null);
6539
+ const lastY = useRef14(null);
6367
6540
  const hasBoth = topContent !== null && bottomContent !== null;
6368
- const startHeightResize = useCallback20((e) => {
6541
+ const startHeightResize = useCallback21((e) => {
6369
6542
  e.preventDefault();
6370
6543
  setIsResizingHeight(true);
6371
6544
  lastY.current = e.clientY;
6372
6545
  }, []);
6373
- const stopHeightResize = useCallback20(() => {
6546
+ const stopHeightResize = useCallback21(() => {
6374
6547
  setIsResizingHeight(false);
6375
6548
  lastY.current = null;
6376
6549
  }, []);
6377
- const resizeHeight = useCallback20(
6550
+ const resizeHeight = useCallback21(
6378
6551
  (e) => {
6379
6552
  if (!isResizingHeight || lastY.current === null || !containerRef.current) {
6380
6553
  return;
@@ -6393,7 +6566,7 @@ var ToolPanelContainer = React75.forwardRef(
6393
6566
  },
6394
6567
  [isResizingHeight]
6395
6568
  );
6396
- useEffect14(() => {
6569
+ useEffect15(() => {
6397
6570
  if (isResizingHeight) {
6398
6571
  window.addEventListener("mousemove", resizeHeight);
6399
6572
  window.addEventListener("mouseup", stopHeightResize);
@@ -6468,65 +6641,6 @@ var ToolPanelContainer = React75.forwardRef(
6468
6641
  );
6469
6642
  ToolPanelContainer.displayName = "ToolPanelContainer";
6470
6643
 
6471
- // src/components/chat/hooks/useResizable.ts
6472
- import { useCallback as useCallback21, useEffect as useEffect15, useRef as useRef14, useState as useState21 } from "react";
6473
- function useResizable({
6474
- initialWidthPercent,
6475
- minWidthPercent,
6476
- maxWidthPercent,
6477
- direction
6478
- }) {
6479
- const [widthPercent, setWidthPercent] = useState21(initialWidthPercent);
6480
- const [isResizing, setIsResizing] = useState21(false);
6481
- const lastX = useRef14(null);
6482
- const startResizing = useCallback21((e) => {
6483
- e.preventDefault();
6484
- setIsResizing(true);
6485
- lastX.current = e.clientX;
6486
- }, []);
6487
- const stopResizing = useCallback21(() => {
6488
- setIsResizing(false);
6489
- lastX.current = null;
6490
- }, []);
6491
- const resize = useCallback21(
6492
- (e) => {
6493
- if (!isResizing || lastX.current === null) {
6494
- return;
6495
- }
6496
- const deltaX = e.clientX - lastX.current;
6497
- const factor = direction === "right" ? 1 : -1;
6498
- const deltaPercent = deltaX / window.innerWidth * 100;
6499
- setWidthPercent((prevPercent) => {
6500
- const newPercent = prevPercent + deltaPercent * factor;
6501
- return Math.min(Math.max(newPercent, minWidthPercent), maxWidthPercent);
6502
- });
6503
- lastX.current = e.clientX;
6504
- },
6505
- [isResizing, direction, minWidthPercent, maxWidthPercent]
6506
- );
6507
- useEffect15(() => {
6508
- if (isResizing) {
6509
- window.addEventListener("mousemove", resize);
6510
- window.addEventListener("mouseup", stopResizing);
6511
- document.body.style.cursor = "col-resize";
6512
- document.body.style.userSelect = "none";
6513
- } else {
6514
- window.removeEventListener("mousemove", resize);
6515
- window.removeEventListener("mouseup", stopResizing);
6516
- document.body.style.cursor = "";
6517
- document.body.style.userSelect = "";
6518
- }
6519
- return () => {
6520
- window.removeEventListener("mousemove", resize);
6521
- window.removeEventListener("mouseup", stopResizing);
6522
- document.body.style.cursor = "";
6523
- document.body.style.userSelect = "";
6524
- };
6525
- }, [isResizing, resize, stopResizing]);
6526
- const width = `${widthPercent}vw`;
6527
- return { width, widthPercent, isResizing, startResizing };
6528
- }
6529
-
6530
6644
  // src/components/chat/tree.ts
6531
6645
  function createEmptyTree() {
6532
6646
  return { nodes: {}, rootIds: [], activeLeafId: null, lastLeafId: null };
@@ -6687,6 +6801,7 @@ var ChatInterface = React76.forwardRef(
6687
6801
  onRenameConversation,
6688
6802
  isStreaming = false,
6689
6803
  isThinking = false,
6804
+ thinkingLabel,
6690
6805
  placeholder = "Send a message...",
6691
6806
  emptyStateHelper = "Let's talk.",
6692
6807
  emptyState,
@@ -6695,6 +6810,7 @@ var ChatInterface = React76.forwardRef(
6695
6810
  attachments: propsAttachments,
6696
6811
  onAttachmentsChange,
6697
6812
  onAttachmentRemove,
6813
+ onAttachmentOpen,
6698
6814
  artifactNodes,
6699
6815
  isArtifactsPanelOpen,
6700
6816
  onArtifactsPanelOpenChange,
@@ -6711,6 +6827,14 @@ var ChatInterface = React76.forwardRef(
6711
6827
  }, ref) => {
6712
6828
  const prevArtifactNodesRef = useRef15([]);
6713
6829
  const prevTasksRef = useRef15([]);
6830
+ const [panelOpenArtifactId, setPanelOpenArtifactId] = useState22(null);
6831
+ const handleAttachmentOpen = useCallback22((artifactId) => {
6832
+ setPanelOpenArtifactId(artifactId);
6833
+ onAttachmentOpen?.(artifactId);
6834
+ }, [onAttachmentOpen]);
6835
+ const handleArtifactPanelClosed = useCallback22(() => {
6836
+ setPanelOpenArtifactId(null);
6837
+ }, []);
6714
6838
  const [internalTools, setInternalTools] = useState22({
6715
6839
  "top-left": "history",
6716
6840
  "bottom-left": null,
@@ -6898,7 +7022,15 @@ var ChatInterface = React76.forwardRef(
6898
7022
  isStreaming: node.isStreaming,
6899
7023
  muted: opts.muted,
6900
7024
  branchInfo,
6901
- actions
7025
+ actions,
7026
+ attachments: node.attachments ? node.attachments.map((a) => ({
7027
+ id: a.id,
7028
+ file: { name: a.name, size: a.size ?? 0, type: a.type },
7029
+ previewUrl: a.previewUrl,
7030
+ artifactId: a.artifactId,
7031
+ status: a.status ?? "analyzed"
7032
+ })) : void 0,
7033
+ onAttachmentOpen: handleAttachmentOpen
6902
7034
  };
6903
7035
  },
6904
7036
  [
@@ -6908,7 +7040,8 @@ var ChatInterface = React76.forwardRef(
6908
7040
  onEditMessage,
6909
7041
  onRetryMessage,
6910
7042
  handleBranchSwitch,
6911
- handleJumpToCheckpoint
7043
+ handleJumpToCheckpoint,
7044
+ handleAttachmentOpen
6912
7045
  ]
6913
7046
  );
6914
7047
  const displayItems = useMemo5(() => {
@@ -6977,6 +7110,8 @@ var ChatInterface = React76.forwardRef(
6977
7110
  ArtifactsPanel,
6978
7111
  {
6979
7112
  nodes: artifactNodes,
7113
+ openArtifactId: panelOpenArtifactId,
7114
+ onArtifactClosed: handleArtifactPanelClosed,
6980
7115
  className: "h-full"
6981
7116
  }
6982
7117
  );
@@ -7039,6 +7174,7 @@ var ChatInterface = React76.forwardRef(
7039
7174
  latestUserMessageIndex,
7040
7175
  isStreaming,
7041
7176
  isThinking,
7177
+ thinkingLabel,
7042
7178
  className: "flex-1"
7043
7179
  }
7044
7180
  )), /* @__PURE__ */ React76.createElement("div", { className: cx(