@tangle-network/agent-app 0.45.64 → 0.45.66

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.
@@ -12,9 +12,11 @@ import {
12
12
  POPOVER_OPTION_FOCUS,
13
13
  POPOVER_SURFACE_ATTR,
14
14
  PopoverSurface,
15
+ filterAcceptedFiles,
16
+ renamePastedImages,
15
17
  usePending,
16
18
  usePopover
17
- } from "./chunk-FZKNVQYP.js";
19
+ } from "./chunk-23J72UUA.js";
18
20
  import {
19
21
  AsyncView
20
22
  } from "./chunk-3UBAO3N5.js";
@@ -1575,6 +1577,12 @@ function FolderGlyph({ className }) {
1575
1577
  function CloseGlyph({ className }) {
1576
1578
  return /* @__PURE__ */ jsx7("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx7("path", { d: "M18 6 6 18M6 6l12 12" }) });
1577
1579
  }
1580
+ function RetryGlyph({ className }) {
1581
+ return /* @__PURE__ */ jsxs5("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
1582
+ /* @__PURE__ */ jsx7("path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }),
1583
+ /* @__PURE__ */ jsx7("path", { d: "M3 3v5h5" })
1584
+ ] });
1585
+ }
1578
1586
  function UploadGlyph({ className }) {
1579
1587
  return /* @__PURE__ */ jsx7("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx7("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12" }) });
1580
1588
  }
@@ -1584,7 +1592,9 @@ function MicGlyph({ className }) {
1584
1592
  /* @__PURE__ */ jsx7("path", { d: "M5 10v1a7 7 0 0 0 14 0v-1M12 18v4" })
1585
1593
  ] });
1586
1594
  }
1587
- var MAX_HEIGHT = 168;
1595
+ var DEFAULT_MAX_HEIGHT = 168;
1596
+ var LINE_HEIGHT = 24;
1597
+ var TEXTAREA_PADDING_Y = 8;
1588
1598
  var DEFAULT_SEND_FAILURE = "Message not sent. Your draft is still here \u2014 try again.";
1589
1599
  function isRejectedOutcome(outcome) {
1590
1600
  return typeof outcome === "object" && outcome !== null && outcome.ok === false;
@@ -1622,9 +1632,19 @@ function ChatComposer({
1622
1632
  onAttachFolder,
1623
1633
  pendingFiles = [],
1624
1634
  onRemoveFile,
1635
+ onRetryFile,
1625
1636
  accept,
1637
+ onRejectFiles,
1626
1638
  dropTitle = "Drop files to add context",
1627
1639
  dropDescription = "They attach to your next message.",
1640
+ contextItems = [],
1641
+ canSubmitAttachmentsOnly = false,
1642
+ attachmentsNotReadyMessage,
1643
+ canSubmitWhileBusy = false,
1644
+ autoFocus,
1645
+ minRows = 2,
1646
+ maxHeight = DEFAULT_MAX_HEIGHT,
1647
+ trailing,
1628
1648
  slashCommands,
1629
1649
  onDictate,
1630
1650
  onDictateError,
@@ -1644,6 +1664,7 @@ function ChatComposer({
1644
1664
  const folderInputRef = useRef6(null);
1645
1665
  const [dragOver, setDragOver] = useState8(false);
1646
1666
  const dragDepth = useRef6(0);
1667
+ const pastedImageCount = useRef6(0);
1647
1668
  const setText = useCallback3(
1648
1669
  (next) => {
1649
1670
  if (!isControlled) setInternal(next);
@@ -1671,8 +1692,8 @@ function ChatComposer({
1671
1692
  const el = textareaRef.current;
1672
1693
  if (!el) return;
1673
1694
  el.style.height = "auto";
1674
- el.style.height = `${Math.min(el.scrollHeight, MAX_HEIGHT)}px`;
1675
- }, [text]);
1695
+ el.style.height = `${Math.min(el.scrollHeight, maxHeight)}px`;
1696
+ }, [text, maxHeight, minRows]);
1676
1697
  const prevSeedRef = useRef6(null);
1677
1698
  const pendingCaretRef = useRef6(null);
1678
1699
  useEffect6(() => {
@@ -1721,9 +1742,10 @@ function ChatComposer({
1721
1742
  document.addEventListener("keydown", onKeyDown);
1722
1743
  return () => document.removeEventListener("keydown", onKeyDown);
1723
1744
  }, [focusShortcut, disabled]);
1724
- const readyFileCount = pendingFiles.filter((f) => f.status === "ready").length;
1725
- const hasSendable = text.trim().length > 0 || readyFileCount > 0;
1726
- const canSend = hasSendable && !isStreaming && !disabled;
1745
+ const sendableFiles = canSubmitAttachmentsOnly ? pendingFiles : pendingFiles.filter((f) => f.status === "ready");
1746
+ const hasSendable = text.trim().length > 0 || sendableFiles.length > 0;
1747
+ const sendBlockedByStream = isStreaming && !canSubmitWhileBusy;
1748
+ const canSend = hasSendable && !sendBlockedByStream && !disabled;
1727
1749
  const [failedSend, setFailedSend] = useState8(null);
1728
1750
  const failSend = useCallback3(
1729
1751
  (error, draft, trimmed, parts, caret) => {
@@ -1768,9 +1790,15 @@ function ChatComposer({
1768
1790
  );
1769
1791
  const send = useCallback3(() => {
1770
1792
  const trimmed = text.trim();
1771
- if (isStreaming || disabled) return;
1793
+ if (sendBlockedByStream || disabled) return;
1772
1794
  const readyFiles = pendingFiles.filter((f) => f.status === "ready");
1773
- if (!trimmed && readyFiles.length === 0) return;
1795
+ const sendable = canSubmitAttachmentsOnly ? pendingFiles : readyFiles;
1796
+ if (!trimmed && sendable.length === 0) return;
1797
+ if (!trimmed && readyFiles.length === 0) {
1798
+ const message = attachmentsNotReadyMessage ?? (pendingFiles.some((f) => f.status === "error") ? "Retry or remove the failed attachment before sending." : "Wait for the attachment to finish uploading.");
1799
+ setFailedSend({ message, text: "", trimmed: "", parts: [], restored: true });
1800
+ return;
1801
+ }
1774
1802
  const parts = onSendParts ? readyFiles.filter((f) => f.part).map((f) => f.part) : [];
1775
1803
  const el = textareaRef.current;
1776
1804
  const caret = { start: el?.selectionStart ?? text.length, end: el?.selectionEnd ?? text.length };
@@ -1778,14 +1806,24 @@ function ChatComposer({
1778
1806
  setText("");
1779
1807
  textRef.current = "";
1780
1808
  dispatchSend(text, trimmed, parts, caret);
1781
- }, [text, isStreaming, disabled, onSendParts, pendingFiles, setText, dispatchSend]);
1809
+ }, [
1810
+ text,
1811
+ sendBlockedByStream,
1812
+ disabled,
1813
+ canSubmitAttachmentsOnly,
1814
+ attachmentsNotReadyMessage,
1815
+ onSendParts,
1816
+ pendingFiles,
1817
+ setText,
1818
+ dispatchSend
1819
+ ]);
1782
1820
  const retryFailedSend = useCallback3(() => {
1783
1821
  const failure = failedSend;
1784
- if (!failure || isStreaming || disabled) return;
1822
+ if (!failure || sendBlockedByStream || disabled) return;
1785
1823
  setFailedSend(null);
1786
1824
  const caret = { start: failure.text.length, end: failure.text.length };
1787
1825
  dispatchSend(failure.text, failure.trimmed, failure.parts, caret);
1788
- }, [failedSend, isStreaming, disabled, dispatchSend]);
1826
+ }, [failedSend, sendBlockedByStream, disabled, dispatchSend]);
1789
1827
  const slashPanelRef = useRef6(null);
1790
1828
  const cardRef = useRef6(null);
1791
1829
  const slashListId = useId();
@@ -1868,10 +1906,40 @@ function ChatComposer({
1868
1906
  send();
1869
1907
  }
1870
1908
  };
1909
+ const deliverFiles = useCallback3(
1910
+ (files, original) => {
1911
+ if (!onAttach || files.length === 0) return;
1912
+ const { accepted, rejected } = filterAcceptedFiles(files, accept);
1913
+ if (rejected.length > 0) onRejectFiles?.(rejected);
1914
+ if (accepted.length === 0) return;
1915
+ const unchanged = accepted.length === original.length && accepted.every((file, i) => file === original[i]);
1916
+ if (unchanged) {
1917
+ onAttach(original);
1918
+ return;
1919
+ }
1920
+ const transfer = new DataTransfer();
1921
+ for (const file of accepted) transfer.items.add(file);
1922
+ onAttach(transfer.files);
1923
+ },
1924
+ [onAttach, onRejectFiles, accept]
1925
+ );
1871
1926
  const handleFileChange = (e) => {
1872
- if (e.target.files?.length) onAttach?.(e.target.files);
1927
+ if (e.target.files?.length) deliverFiles(Array.from(e.target.files), e.target.files);
1873
1928
  e.target.value = "";
1874
1929
  };
1930
+ const handlePaste = (e) => {
1931
+ if (!onAttach) return;
1932
+ const clipboardFiles = e.clipboardData?.files;
1933
+ if (!clipboardFiles || clipboardFiles.length === 0) return;
1934
+ e.preventDefault();
1935
+ const { files, nextIndex } = renamePastedImages(
1936
+ Array.from(clipboardFiles),
1937
+ pastedImageCount.current,
1938
+ pendingFiles.map((f) => f.name)
1939
+ );
1940
+ pastedImageCount.current = nextIndex;
1941
+ deliverFiles(files, clipboardFiles);
1942
+ };
1875
1943
  const handleFolderChange = (e) => {
1876
1944
  if (e.target.files?.length) (onAttachFolder ?? onAttach)?.(e.target.files);
1877
1945
  e.target.value = "";
@@ -1903,9 +1971,9 @@ function ChatComposer({
1903
1971
  dragDepth.current = 0;
1904
1972
  setDragOver(false);
1905
1973
  const files = e.dataTransfer?.files;
1906
- if (files?.length) onAttach?.(files);
1974
+ if (files?.length) deliverFiles(Array.from(files), files);
1907
1975
  },
1908
- [onAttach]
1976
+ [deliverFiles]
1909
1977
  );
1910
1978
  const folderChips = pendingFiles.filter((f) => f.kind === "folder");
1911
1979
  const fileChips = pendingFiles.filter((f) => f.kind !== "folder");
@@ -1982,7 +2050,7 @@ function ChatComposer({
1982
2050
  type: "button",
1983
2051
  "aria-label": "Retry sending the unsent message",
1984
2052
  onClick: retryFailedSend,
1985
- disabled: isStreaming || disabled,
2053
+ disabled: sendBlockedByStream || disabled,
1986
2054
  className: "mt-1.5 font-medium underline-offset-2 hover:underline disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-destructive/50",
1987
2055
  children: "Retry"
1988
2056
  }
@@ -1991,33 +2059,69 @@ function ChatComposer({
1991
2059
  ]
1992
2060
  }
1993
2061
  ),
1994
- pendingFiles.length > 0 && /* @__PURE__ */ jsx7("div", { className: "mb-2 flex flex-wrap gap-1.5", children: [...folderChips, ...fileChips].map((f) => /* @__PURE__ */ jsxs5(
2062
+ contextItems.length > 0 && /* @__PURE__ */ jsx7("div", { "aria-label": "Message context", className: "mb-2 flex min-w-0 flex-wrap gap-1.5", children: contextItems.map((item) => /* @__PURE__ */ jsxs5(
1995
2063
  "span",
1996
2064
  {
1997
- className: `inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs ${f.status === "error" ? "border-destructive/40 text-destructive" : "border-border bg-secondary text-foreground"}`,
2065
+ className: "inline-flex min-w-0 max-w-full items-center gap-1.5 rounded-md border border-primary/30 bg-primary/10 px-2.5 py-1 text-xs text-primary",
1998
2066
  children: [
1999
- f.kind === "folder" ? /* @__PURE__ */ jsx7(FolderGlyph, { className: "h-3 w-3 shrink-0" }) : /* @__PURE__ */ jsx7(PaperclipGlyph, { className: "h-3 w-3 shrink-0" }),
2000
- /* @__PURE__ */ jsx7("span", { className: "max-w-[150px] truncate", children: f.name }),
2001
- f.fileCount !== void 0 && /* @__PURE__ */ jsxs5("span", { className: "text-muted-foreground", children: [
2002
- "(",
2003
- f.fileCount,
2004
- ")"
2005
- ] }),
2006
- f.status === "uploading" && /* @__PURE__ */ jsx7("span", { className: "h-3 w-3 animate-spin rounded-full border-2 border-primary border-t-transparent" }),
2007
- onRemoveFile && /* @__PURE__ */ jsx7(
2067
+ item.icon && /* @__PURE__ */ jsx7("span", { className: "shrink-0", "aria-hidden": true, children: item.icon }),
2068
+ /* @__PURE__ */ jsx7("span", { className: "min-w-0 truncate", children: item.label }),
2069
+ item.onRemove && /* @__PURE__ */ jsx7(
2008
2070
  "button",
2009
2071
  {
2010
2072
  type: "button",
2011
- "aria-label": `Remove ${f.name}`,
2012
- onClick: () => onRemoveFile(f.id),
2013
- className: "rounded p-0.5 text-muted-foreground transition hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
2073
+ "aria-label": `Remove context ${item.label}`,
2074
+ onClick: item.onRemove,
2075
+ className: "shrink-0 rounded p-0.5 text-primary/70 transition hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
2014
2076
  children: /* @__PURE__ */ jsx7(CloseGlyph, { className: "h-3 w-3" })
2015
2077
  }
2016
2078
  )
2017
2079
  ]
2018
2080
  },
2019
- f.id
2081
+ item.id
2020
2082
  )) }),
2083
+ pendingFiles.length > 0 && /* @__PURE__ */ jsx7("div", { className: "mb-2 flex flex-wrap gap-1.5", children: [...folderChips, ...fileChips].map((f) => {
2084
+ const isError = f.status === "error";
2085
+ return /* @__PURE__ */ jsxs5(
2086
+ "span",
2087
+ {
2088
+ title: isError ? f.errorMessage : void 0,
2089
+ className: `inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs ${isError ? "border-destructive/40 text-destructive" : "border-border bg-secondary text-foreground"} ${f.status === "pending" ? "opacity-60" : ""}`,
2090
+ children: [
2091
+ f.kind !== "folder" && f.previewUrl ? /* @__PURE__ */ jsx7("img", { src: f.previewUrl, alt: "", className: "h-8 w-8 shrink-0 rounded object-cover" }) : f.kind === "folder" ? /* @__PURE__ */ jsx7(FolderGlyph, { className: "h-3 w-3 shrink-0" }) : /* @__PURE__ */ jsx7(PaperclipGlyph, { className: "h-3 w-3 shrink-0" }),
2092
+ /* @__PURE__ */ jsx7("span", { className: "max-w-[150px] truncate", children: f.name }),
2093
+ f.fileCount !== void 0 && /* @__PURE__ */ jsxs5("span", { className: "text-muted-foreground", children: [
2094
+ "(",
2095
+ f.fileCount,
2096
+ ")"
2097
+ ] }),
2098
+ f.status === "uploading" && /* @__PURE__ */ jsx7("span", { className: "h-3 w-3 animate-spin rounded-full border-2 border-primary border-t-transparent" }),
2099
+ isError && f.errorMessage && /* @__PURE__ */ jsx7("span", { className: "max-w-[150px] truncate text-destructive/80", children: f.errorMessage }),
2100
+ isError && onRetryFile && /* @__PURE__ */ jsx7(
2101
+ "button",
2102
+ {
2103
+ type: "button",
2104
+ "aria-label": `Retry upload ${f.name}`,
2105
+ onClick: () => onRetryFile(f.id),
2106
+ className: "rounded p-0.5 text-muted-foreground transition hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
2107
+ children: /* @__PURE__ */ jsx7(RetryGlyph, { className: "h-3 w-3" })
2108
+ }
2109
+ ),
2110
+ onRemoveFile && /* @__PURE__ */ jsx7(
2111
+ "button",
2112
+ {
2113
+ type: "button",
2114
+ "aria-label": `Remove ${f.name}`,
2115
+ onClick: () => onRemoveFile(f.id),
2116
+ className: "rounded p-0.5 text-muted-foreground transition hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
2117
+ children: /* @__PURE__ */ jsx7(CloseGlyph, { className: "h-3 w-3" })
2118
+ }
2119
+ )
2120
+ ]
2121
+ },
2122
+ f.id
2123
+ );
2124
+ }) }),
2021
2125
  /* @__PURE__ */ jsxs5(
2022
2126
  "div",
2023
2127
  {
@@ -2032,11 +2136,14 @@ function ChatComposer({
2032
2136
  value: text,
2033
2137
  onChange: (e) => setText(e.target.value),
2034
2138
  onKeyDown: handleKeyDown,
2139
+ onPaste: onAttach ? handlePaste : void 0,
2035
2140
  placeholder,
2036
2141
  disabled,
2037
- rows: 2,
2142
+ autoFocus,
2143
+ rows: minRows,
2144
+ style: { minHeight: minRows * LINE_HEIGHT + TEXTAREA_PADDING_Y, maxHeight },
2038
2145
  "aria-label": "Message input",
2039
- className: "max-h-[168px] min-h-[56px] w-full resize-none bg-transparent px-1.5 py-1 text-base leading-6 text-foreground outline-none placeholder:text-muted-foreground disabled:opacity-50"
2146
+ className: "w-full resize-none bg-transparent px-1.5 py-1 text-base leading-6 text-foreground outline-none placeholder:text-muted-foreground disabled:opacity-50"
2040
2147
  }
2041
2148
  ),
2042
2149
  /* @__PURE__ */ jsxs5("div", { className: "flex items-end gap-2", children: [
@@ -2088,6 +2195,7 @@ function ChatComposer({
2088
2195
  children: showInline && controls
2089
2196
  }
2090
2197
  ),
2198
+ trailing && /* @__PURE__ */ jsx7("div", { "data-testid": "composer-trailing", className: "flex shrink-0 items-center gap-1.5", children: trailing }),
2091
2199
  onDictate && dictation.supported ? dictation.recording ? /* @__PURE__ */ jsxs5("div", { className: "flex shrink-0 items-center gap-1.5", children: [
2092
2200
  /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", className: "h-2 w-2 animate-pulse rounded-full bg-destructive" }),
2093
2201
  /* @__PURE__ */ jsx7(
@@ -7569,4 +7677,4 @@ export {
7569
7677
  useThinkingSeconds,
7570
7678
  ChatMessages
7571
7679
  };
7572
- //# sourceMappingURL=chunk-OMGV3CX2.js.map
7680
+ //# sourceMappingURL=chunk-AWM4H5XR.js.map