@parallelworks/ui 0.51.0 → 0.52.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.
Files changed (227) hide show
  1. package/README.md +155 -14
  2. package/dist/ai/agent/toolStrings.d.ts +2 -0
  3. package/dist/ai/agent/toolStrings.js +20 -18
  4. package/dist/ai/ai.css +12 -0
  5. package/dist/ai/components/AllocationSelector.js +5 -1
  6. package/dist/ai/components/AttachmentManager.js +51 -42
  7. package/dist/ai/components/AttachmentUpload.d.ts +4 -2
  8. package/dist/ai/components/AttachmentUpload.js +9 -9
  9. package/dist/ai/components/BlockedGroupBanner.js +8 -14
  10. package/dist/ai/components/BranchNavigator.js +11 -12
  11. package/dist/ai/components/ChatEmptyState.js +12 -19
  12. package/dist/ai/components/ChatInput.d.ts +4 -3
  13. package/dist/ai/components/ChatInput.js +27 -13
  14. package/dist/ai/components/ChatMessage.js +45 -121
  15. package/dist/ai/components/ChatMessageList.d.ts +5 -2
  16. package/dist/ai/components/ChatMessageList.js +89 -51
  17. package/dist/ai/components/ChatSidebar.js +11 -7
  18. package/dist/ai/components/ChatThread.js +1 -13
  19. package/dist/ai/components/DragOverlay.js +3 -3
  20. package/dist/ai/components/MessageAttachments.d.ts +10 -0
  21. package/dist/ai/components/MessageAttachments.js +78 -0
  22. package/dist/ai/components/ModelSelector.js +3 -3
  23. package/dist/ai/components/PastedText.js +2 -2
  24. package/dist/ai/components/ProviderIssueBanner.js +19 -23
  25. package/dist/ai/components/ShareDialog.js +2 -2
  26. package/dist/ai/components/agent/ActivityLine.d.ts +0 -2
  27. package/dist/ai/components/agent/ActivityLine.js +8 -51
  28. package/dist/ai/components/agent/AgentMessageParts.js +114 -53
  29. package/dist/ai/core/ChatProvider.d.ts +16 -5
  30. package/dist/ai/core/ChatProvider.js +41 -8
  31. package/dist/ai/core/config.js +4 -3
  32. package/dist/ai/index.d.ts +1 -1
  33. package/dist/ai/index.js +3 -3
  34. package/dist/ai/strings.d.ts +29 -3
  35. package/dist/ai/strings.js +81 -26
  36. package/dist/ai/types.d.ts +4 -0
  37. package/dist/ai/ui/Notice.d.ts +2 -2
  38. package/dist/ai/ui/Notice.js +29 -62
  39. package/dist/ai/ui/sidebar.d.ts +5 -3
  40. package/dist/ai/ui/sidebar.js +21 -9
  41. package/dist/components/ActionMenu.d.ts +1 -0
  42. package/dist/components/ActionMenu.js +6 -1
  43. package/dist/components/AppLink.d.ts +26 -0
  44. package/dist/components/AppLink.js +51 -0
  45. package/dist/components/Avatar.d.ts +2 -1
  46. package/dist/components/Avatar.js +4 -2
  47. package/dist/components/Button.d.ts +15 -5
  48. package/dist/components/Button.js +47 -40
  49. package/dist/components/Callout.d.ts +31 -7
  50. package/dist/components/Callout.js +50 -19
  51. package/dist/components/Card.d.ts +11 -6
  52. package/dist/components/Card.js +14 -8
  53. package/dist/components/Checkbox.js +2 -1
  54. package/dist/components/ColorInput.js +5 -3
  55. package/dist/components/Combobox.js +49 -19
  56. package/dist/components/ConfirmModal.d.ts +19 -8
  57. package/dist/components/ConfirmModal.js +31 -53
  58. package/dist/components/CopyButton.d.ts +38 -0
  59. package/dist/components/CopyButton.js +68 -0
  60. package/dist/components/CreateModal.js +118 -111
  61. package/dist/components/DatePicker.js +12 -8
  62. package/dist/components/DescriptionList.d.ts +25 -5
  63. package/dist/components/DescriptionList.js +56 -22
  64. package/dist/components/Disclosure.js +1 -1
  65. package/dist/components/Drawer.js +10 -16
  66. package/dist/components/Field.d.ts +15 -5
  67. package/dist/components/Field.js +40 -8
  68. package/dist/components/IconButton.d.ts +14 -5
  69. package/dist/components/IconButton.js +25 -14
  70. package/dist/components/InfoTip.d.ts +4 -1
  71. package/dist/components/InfoTip.js +2 -1
  72. package/dist/components/Input.d.ts +14 -8
  73. package/dist/components/Input.js +46 -31
  74. package/dist/components/Loader.js +1 -1
  75. package/dist/components/MenuEntries.d.ts +4 -0
  76. package/dist/components/MenuEntries.js +11 -12
  77. package/dist/components/Meter.js +5 -4
  78. package/dist/components/Modal.d.ts +28 -4
  79. package/dist/components/Modal.js +37 -14
  80. package/dist/components/Overview.d.ts +20 -12
  81. package/dist/components/Overview.js +14 -9
  82. package/dist/components/PageHeader.d.ts +47 -65
  83. package/dist/components/PageHeader.js +275 -153
  84. package/dist/components/PageHeaderAction.d.ts +67 -0
  85. package/dist/components/PageHeaderAction.js +107 -0
  86. package/dist/components/Pagination.js +6 -16
  87. package/dist/components/Popover.js +3 -2
  88. package/dist/components/Provider.d.ts +77 -19
  89. package/dist/components/Provider.js +139 -130
  90. package/dist/components/Radio.js +2 -1
  91. package/dist/components/RefreshButton.js +7 -8
  92. package/dist/components/RequiredMark.js +1 -1
  93. package/dist/components/Section.js +1 -1
  94. package/dist/components/Select.d.ts +4 -9
  95. package/dist/components/Select.js +24 -11
  96. package/dist/components/SettingModeOption.d.ts +1 -1
  97. package/dist/components/SettingModeOption.js +7 -1
  98. package/dist/components/SettingsCard.d.ts +5 -3
  99. package/dist/components/SettingsCard.js +27 -20
  100. package/dist/components/Slider.js +1 -1
  101. package/dist/components/StatusBadge.js +2 -2
  102. package/dist/components/Switch.js +5 -4
  103. package/dist/components/Table.d.ts +30 -20
  104. package/dist/components/Table.js +50 -73
  105. package/dist/components/Tabs.d.ts +3 -7
  106. package/dist/components/Tabs.js +6 -10
  107. package/dist/components/ThemeEditor.js +18 -8
  108. package/dist/components/Toast.d.ts +11 -3
  109. package/dist/components/Toast.js +45 -20
  110. package/dist/components/buttonClasses.js +1 -1
  111. package/dist/components/comboboxItems.d.ts +4 -0
  112. package/dist/components/comboboxItems.js +6 -1
  113. package/dist/components/dismiss.d.ts +12 -0
  114. package/dist/components/dismiss.js +19 -0
  115. package/dist/components/fieldBox.d.ts +4 -0
  116. package/dist/components/fieldBox.js +7 -3
  117. package/dist/components/floating.d.ts +3 -3
  118. package/dist/components/floating.js +1 -1
  119. package/dist/components/markdownCodeBreaks.js +3 -4
  120. package/dist/components/pageHeaderStore.d.ts +41 -0
  121. package/dist/components/pageHeaderStore.js +67 -0
  122. package/dist/components/pathBreaks.d.ts +8 -0
  123. package/dist/components/pathBreaks.js +28 -0
  124. package/dist/components/segmented.js +1 -1
  125. package/dist/components/shortcutKey.d.ts +2 -1
  126. package/dist/components/shortcutKey.js +19 -2
  127. package/dist/components/splitter.js +1 -1
  128. package/dist/components/useCssIsDark.js +40 -18
  129. package/dist/components/useTheme.js +9 -10
  130. package/dist/dev.d.ts +9 -0
  131. package/dist/dev.js +14 -0
  132. package/dist/editor/Editor.d.ts +3 -2
  133. package/dist/editor/Editor.js +11 -2
  134. package/dist/editor/EditorField.d.ts +2 -1
  135. package/dist/editor/EditorField.js +11 -29
  136. package/dist/editor/FormCodePanel.d.ts +7 -3
  137. package/dist/editor/FormCodePanel.js +14 -7
  138. package/dist/editor/Monaco.d.ts +14 -5
  139. package/dist/editor/Monaco.js +163 -112
  140. package/dist/editor/index.d.ts +5 -5
  141. package/dist/editor/index.js +3 -3
  142. package/dist/editor/themes.d.ts +8 -5
  143. package/dist/editor/themes.js +58 -30
  144. package/dist/editor/yaml.d.ts +6 -2
  145. package/dist/editor/yaml.js +11 -2
  146. package/dist/file-explorer/FileExplorer.js +143 -124
  147. package/dist/file-explorer/FileExplorerContext.js +1 -1
  148. package/dist/file-explorer/FilePreview/StreamedBadge.js +3 -1
  149. package/dist/file-explorer/FilePreview/Viewer.js +2 -2
  150. package/dist/file-explorer/FilePreview/index.js +4 -4
  151. package/dist/file-explorer/FileRow.js +1 -1
  152. package/dist/file-explorer/FileRowsWindow.js +34 -3
  153. package/dist/file-explorer/FileTable.js +3 -1
  154. package/dist/file-explorer/TreeView.js +37 -14
  155. package/dist/file-explorer/helpers.d.ts +2 -1
  156. package/dist/file-explorer/helpers.js +21 -20
  157. package/dist/file-explorer/toasts.js +27 -19
  158. package/dist/file-explorer/treeRows.d.ts +6 -0
  159. package/dist/file-explorer/treeRows.js +19 -1
  160. package/dist/file-explorer/useNearEnd.js +3 -6
  161. package/dist/form/Fields/CheckboxGroupField.js +12 -5
  162. package/dist/form/Fields/DurationField.js +1 -1
  163. package/dist/form/Fields/LabelField.js +1 -1
  164. package/dist/form/Fields/ObjectField.d.ts +1 -1
  165. package/dist/form/Fields/ObjectField.js +3 -2
  166. package/dist/form/Fields/StringField.js +16 -29
  167. package/dist/form/Fields/TextAreaField.js +3 -4
  168. package/dist/form/Form.js +3 -8
  169. package/dist/form/Wizard/WizardNavigation.js +0 -3
  170. package/dist/form/Wizard/utils.js +1 -1
  171. package/dist/form/fieldFrame.d.ts +1 -1
  172. package/dist/form/fieldFrame.js +2 -2
  173. package/dist/form/types/fieldComponentTypes.d.ts +2 -1
  174. package/dist/graph/AnnotationBanner.d.ts +1 -2
  175. package/dist/graph/AnnotationBanner.js +30 -32
  176. package/dist/graph/Connectors.js +4 -1
  177. package/dist/graph/DependencyGraph.js +2 -2
  178. package/dist/graph/GraphEditor.js +11 -9
  179. package/dist/graph/GraphEditorDialogs.js +1 -1
  180. package/dist/graph/GraphNode.js +7 -4
  181. package/dist/graph/InputDialog.js +1 -1
  182. package/dist/graph/InputsEditor.js +14 -11
  183. package/dist/graph/MatrixGroup.js +9 -3
  184. package/dist/graph/Reveal.js +18 -10
  185. package/dist/graph/ShortcutsButton.d.ts +11 -2
  186. package/dist/graph/ShortcutsButton.js +14 -3
  187. package/dist/graph/TreeView.js +35 -18
  188. package/dist/graph/WorkflowSettingsDialog.js +1 -1
  189. package/dist/graph/editorFields.js +1 -1
  190. package/dist/graph/editorRows.js +16 -10
  191. package/dist/graph/editorStrings.d.ts +7 -0
  192. package/dist/graph/editorStrings.js +8 -1
  193. package/dist/graph/inputRefs.js +1 -1
  194. package/dist/graph/settingsViews.js +11 -18
  195. package/dist/graph/stepWith.js +1 -1
  196. package/dist/icons.d.ts +2 -0
  197. package/dist/icons.js +4 -2
  198. package/dist/index.d.ts +12 -12
  199. package/dist/index.js +16 -16
  200. package/dist/list/ActionToolbar.d.ts +42 -6
  201. package/dist/list/ActionToolbar.js +297 -101
  202. package/dist/list/ListPage.d.ts +15 -23
  203. package/dist/list/ListPage.js +42 -43
  204. package/dist/list/ListTable.d.ts +32 -7
  205. package/dist/list/ListTable.js +48 -12
  206. package/dist/list/ListViewControls.js +1 -1
  207. package/dist/list/UserHoverCard.js +7 -15
  208. package/dist/list/index.d.ts +2 -2
  209. package/dist/list/index.js +2 -2
  210. package/dist/list/listSearch.js +12 -10
  211. package/dist/list/listView.d.ts +10 -1
  212. package/dist/list/listView.js +15 -1
  213. package/dist/logviewer/LogViewer.d.ts +4 -2
  214. package/dist/logviewer/LogViewer.js +87 -69
  215. package/dist/logviewer/logviewer.css +3 -2
  216. package/dist/reducedMotion.d.ts +5 -0
  217. package/dist/reducedMotion.js +20 -0
  218. package/dist/safeUrl.d.ts +1 -1
  219. package/dist/safeUrl.js +4 -2
  220. package/dist/styles.css +1 -1
  221. package/package.json +1 -1
  222. package/dist/components/Breadcrumbs.d.ts +0 -37
  223. package/dist/components/Breadcrumbs.js +0 -90
  224. package/dist/components/CopyToClipboard.d.ts +0 -17
  225. package/dist/components/CopyToClipboard.js +0 -66
  226. package/dist/components/Sort.d.ts +0 -9
  227. package/dist/components/Sort.js +0 -42
@@ -1,4 +1,5 @@
1
1
  import { ChevronLeftIcon, ChevronRightIcon } from "../../icons.js";
2
+ import { IconButton } from "../../components/IconButton.js";
2
3
  import { useChatConfig } from "../core/config.js";
3
4
  import { jsx, jsxs } from "react/jsx-runtime";
4
5
  import { useMemo } from "react";
@@ -26,14 +27,13 @@ function BranchNavigator({ messages, currentMessageId, onNavigate }) {
26
27
  return /* @__PURE__ */ jsxs("div", {
27
28
  className: "inline-flex items-center text-xs theme-muted-text",
28
29
  children: [
29
- /* @__PURE__ */ jsx("button", {
30
- type: "button",
30
+ /* @__PURE__ */ jsx(IconButton, {
31
+ label: t.previous,
31
32
  onClick: handlePrev,
32
33
  disabled: !hasPrev,
33
- className: "p-1.5 rounded-md transition-colors enabled:hover:chat-tint enabled:hover:theme-text disabled:opacity-30 disabled:cursor-not-allowed",
34
- title: t.previous,
35
- "aria-label": t.previous,
36
- children: /* @__PURE__ */ jsx(ChevronLeftIcon, { className: "w-3.5 h-3.5" })
34
+ size: "md",
35
+ className: "size-7 hover:not-disabled:not-aria-disabled:chat-tint disabled:opacity-30",
36
+ icon: /* @__PURE__ */ jsx(ChevronLeftIcon, {})
37
37
  }),
38
38
  /* @__PURE__ */ jsxs("span", {
39
39
  className: "min-w-[3ch] text-center tabular-nums",
@@ -43,14 +43,13 @@ function BranchNavigator({ messages, currentMessageId, onNavigate }) {
43
43
  siblings.length
44
44
  ]
45
45
  }),
46
- /* @__PURE__ */ jsx("button", {
47
- type: "button",
46
+ /* @__PURE__ */ jsx(IconButton, {
47
+ label: t.next,
48
48
  onClick: handleNext,
49
49
  disabled: !hasNext,
50
- className: "p-1.5 rounded-md transition-colors enabled:hover:chat-tint enabled:hover:theme-text disabled:opacity-30 disabled:cursor-not-allowed",
51
- title: t.next,
52
- "aria-label": t.next,
53
- children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: "w-3.5 h-3.5" })
50
+ size: "md",
51
+ className: "size-7 hover:not-disabled:not-aria-disabled:chat-tint disabled:opacity-30",
52
+ icon: /* @__PURE__ */ jsx(ChevronRightIcon, {})
54
53
  })
55
54
  ]
56
55
  });
@@ -1,12 +1,13 @@
1
1
  import { AddIcon, CloudIcon } from "../../icons.js";
2
2
  import { keyedByContent } from "../../components/keys.js";
3
3
  import { useStrings } from "../../components/Provider.js";
4
+ import { Button } from "../../components/Button.js";
5
+ import { Callout } from "../../components/Callout.js";
4
6
  import { useChatConfig } from "../core/config.js";
5
7
  import { useChat } from "../core/ChatProvider.js";
6
8
  import BlockedGroupBanner from "./BlockedGroupBanner.js";
7
9
  import { getGreeting } from "../core/greeting.js";
8
10
  import useDragDrop from "../core/useDragDrop.js";
9
- import { NoticeCard } from "../ui/Notice.js";
10
11
  import { ComposerControls, ConnectToolsLink } from "./ComposerChrome.js";
11
12
  import DragOverlay from "./DragOverlay.js";
12
13
  import ChatInput from "./ChatInput.js";
@@ -79,26 +80,18 @@ function ChatEmptyState({ targetSession }) {
79
80
  /* @__PURE__ */ jsx(ProviderIssueBanner, {}),
80
81
  sessionUnreachable && !sessionBannerDismissed && /* @__PURE__ */ jsx("div", {
81
82
  className: "mx-4 mt-2",
82
- children: /* @__PURE__ */ jsxs(NoticeCard, {
83
+ children: /* @__PURE__ */ jsx(Callout, {
83
84
  tone: "warning",
85
+ size: "sm",
86
+ title: t.sessionUnreachableTitle(targetSession?.split(":").pop() ?? ""),
87
+ action: /* @__PURE__ */ jsx(Button, {
88
+ size: "sm",
89
+ variant: "outline",
90
+ onClick: () => refreshModels(),
91
+ children: common.retry
92
+ }),
84
93
  onDismiss: () => setSessionBannerDismissed(true),
85
- dismissLabel: common.dismiss,
86
- children: [
87
- /* @__PURE__ */ jsx("span", {
88
- className: "block font-medium",
89
- children: t.sessionUnreachableTitle(targetSession?.split(":").pop() ?? "")
90
- }),
91
- /* @__PURE__ */ jsx("span", {
92
- className: "mt-0.5 block",
93
- children: t.sessionUnreachableBody
94
- }),
95
- /* @__PURE__ */ jsx("button", {
96
- type: "button",
97
- onClick: () => refreshModels(),
98
- className: "mt-1 font-medium underline-offset-2 hover:underline",
99
- children: common.retry
100
- })
101
- ]
94
+ children: t.sessionUnreachableBody
102
95
  })
103
96
  }),
104
97
  /* @__PURE__ */ jsxs("div", {
@@ -1,4 +1,4 @@
1
- import { type ReactNode } from 'react';
1
+ import { type ReactNode, type Ref } from 'react';
2
2
  import type { AttachmentMeta, MessagePaste } from '../types.js';
3
3
  import { type SlashMenuConfig } from './SlashMenu.js';
4
4
  import { type ComposerPastes } from './usePasteCards.js';
@@ -43,10 +43,11 @@ interface ChatInputProps {
43
43
  stopping?: boolean;
44
44
  };
45
45
  pastes?: ComposerPastes | undefined;
46
+ ref?: Ref<ChatInputHandle> | undefined;
46
47
  }
47
48
  export interface ChatInputHandle {
48
49
  addFiles: (files: FileList | File[]) => void;
49
50
  focus: () => void;
50
51
  }
51
- declare const ChatInput: import("react").ForwardRefExoticComponent<ChatInputProps & import("react").RefAttributes<ChatInputHandle>>;
52
- export default ChatInput;
52
+ export default function ChatInput({ onSend, disabled, placeholder, conversationId, onEditLastMessage, allowEmpty, attachments: attachmentsWanted, requireModel, inputLabel, sendLabel, sendTestId, slash, notices, activity, context, reserveContext, settingsLeft, settingsRight, hint, turn, pastes, ref, }: ChatInputProps): import("react").JSX.Element;
53
+ export {};
@@ -11,7 +11,7 @@ import { PasteFileCard } from "./PastedText.js";
11
11
  import { SlashMenu, useSlashMenu } from "./SlashMenu.js";
12
12
  import { usePasteCards } from "./usePasteCards.js";
13
13
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
14
- import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react";
14
+ import { useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
15
15
  //#region src/ai/components/ChatInput.tsx
16
16
  /** An optional box that is still empty should not wear the filled look of
17
17
  * one waiting to send something. */
@@ -19,7 +19,7 @@ function sendability({ hasText, canFlushQueue, allowEmpty, disabled, modelReady,
19
19
  const canSend = (hasText || canFlushQueue || allowEmpty) && !disabled && modelReady && pendingCount === 0;
20
20
  return {
21
21
  canSend,
22
- emphasised: canSend && (hasText || !allowEmpty)
22
+ emphasized: canSend && (hasText || !allowEmpty)
23
23
  };
24
24
  }
25
25
  function keyAction(e, empty, canEditLast) {
@@ -39,13 +39,13 @@ function AttachButton({ open, disabled, label, onClick }) {
39
39
  children: /* @__PURE__ */ jsx(AddIcon, { className: cx("h-5 w-5 transition-transform duration-200", open && "rotate-45") })
40
40
  });
41
41
  }
42
- function SendButton({ onClick, enabled, emphasised, label, testId }) {
42
+ function SendButton({ onClick, enabled, emphasized, label, testId }) {
43
43
  return /* @__PURE__ */ jsx("button", {
44
44
  type: "button",
45
45
  onClick,
46
46
  disabled: !enabled,
47
47
  "data-testid": testId,
48
- className: cx("flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full transition-[background-color,color,transform] duration-150 active:scale-95", emphasised ? "theme-accent hover:opacity-90" : "chat-send-idle", !enabled && "cursor-not-allowed active:scale-100"),
48
+ className: cx("flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full transition-[background-color,color,transform] duration-150 active:scale-95", emphasized ? "theme-accent hover:opacity-90" : "chat-send-idle", !enabled && "cursor-not-allowed active:scale-100"),
49
49
  "aria-label": label,
50
50
  title: label,
51
51
  children: /* @__PURE__ */ jsx(ArrowUpIcon, { className: "h-4 w-4" })
@@ -64,8 +64,12 @@ function streamControls(chat, turn) {
64
64
  queuedCount: chat.queuedMessages.length
65
65
  };
66
66
  }
67
+ function fitToContent(el) {
68
+ el.style.height = "auto";
69
+ el.style.height = `${el.scrollHeight}px`;
70
+ }
67
71
  var draftsSwept = false;
68
- var ChatInput = forwardRef(function ChatInput({ onSend, disabled = false, placeholder, conversationId, onEditLastMessage, allowEmpty = false, attachments: attachmentsWanted = true, requireModel = true, inputLabel, sendLabel, sendTestId, slash, notices, activity, context, reserveContext = false, settingsLeft, settingsRight = /* @__PURE__ */ jsx(ComposerControls, {}), hint, turn, pastes }, ref) {
72
+ function ChatInput({ onSend, disabled = false, placeholder, conversationId, onEditLastMessage, allowEmpty = false, attachments: attachmentsWanted = true, requireModel = true, inputLabel, sendLabel, sendTestId, slash, notices, activity, context, reserveContext = false, settingsLeft, settingsRight = /* @__PURE__ */ jsx(ComposerControls, {}), hint, turn, pastes, ref }) {
69
73
  const [input, setInput] = useState(() => readDraft(conversationId));
70
74
  useEffect(() => {
71
75
  if (!draftsSwept) {
@@ -98,12 +102,22 @@ var ChatInput = forwardRef(function ChatInput({ onSend, disabled = false, placeh
98
102
  textareaRef.current?.focus();
99
103
  }
100
104
  }), []);
101
- useEffect(() => {
102
- if (textareaRef.current) {
103
- textareaRef.current.style.height = "auto";
104
- textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
105
- }
105
+ useLayoutEffect(() => {
106
+ if (textareaRef.current) fitToContent(textareaRef.current);
106
107
  }, [input]);
108
+ useLayoutEffect(() => {
109
+ const el = textareaRef.current;
110
+ if (!el || typeof ResizeObserver === "undefined") return;
111
+ let width = el.clientWidth;
112
+ const observer = new ResizeObserver(() => {
113
+ if (el.clientWidth !== width) {
114
+ width = el.clientWidth;
115
+ fitToContent(el);
116
+ }
117
+ });
118
+ observer.observe(el);
119
+ return () => observer.disconnect();
120
+ }, []);
107
121
  const previousConversationRef = useRef(conversationId);
108
122
  useEffect(() => {
109
123
  if (previousConversationRef.current !== conversationId) {
@@ -119,7 +133,7 @@ var ChatInput = forwardRef(function ChatInput({ onSend, disabled = false, placeh
119
133
  const uploadedCount = attachments.filter((a) => a.uploaded).length;
120
134
  const pendingCount = attachments.length - uploadedCount;
121
135
  const canFlushQueue = !isStreaming && queuedCount > 0 && !disabled;
122
- const { canSend, emphasised } = sendability({
136
+ const { canSend, emphasized } = sendability({
123
137
  hasText: input.trim().length > 0 || cards.length > 0,
124
138
  canFlushQueue,
125
139
  allowEmpty,
@@ -195,7 +209,7 @@ var ChatInput = forwardRef(function ChatInput({ onSend, disabled = false, placeh
195
209
  }) : /* @__PURE__ */ jsx(SendButton, {
196
210
  onClick: handleSubmit,
197
211
  enabled: canSend,
198
- emphasised,
212
+ emphasized,
199
213
  label: sendLabel ?? t.sendMessage,
200
214
  testId: sendTestId
201
215
  });
@@ -297,6 +311,6 @@ var ChatInput = forwardRef(function ChatInput({ onSend, disabled = false, placeh
297
311
  ] })]
298
312
  })]
299
313
  });
300
- });
314
+ }
301
315
  //#endregion
302
316
  export { ChatInput as default };
@@ -1,10 +1,12 @@
1
1
  import { cx } from "../../cx.js";
2
- import { CheckIcon, CopyIcon, DownloadIcon, EditIcon, ErrorIcon, FileIcon, RetryIcon } from "../../icons.js";
3
- import { safeUrl } from "../../safeUrl.js";
2
+ import { EditIcon, RetryIcon } from "../../icons.js";
4
3
  import { useStrings } from "../../components/Provider.js";
5
- import { useCopy } from "../../components/useCopy.js";
6
4
  import { Avatar } from "../../components/Avatar.js";
7
- import { formatBytes, formatDuration } from "../../format.js";
5
+ import { Button } from "../../components/Button.js";
6
+ import { IconButton } from "../../components/IconButton.js";
7
+ import { Callout } from "../../components/Callout.js";
8
+ import { CopyButton } from "../../components/CopyButton.js";
9
+ import { formatDuration } from "../../format.js";
8
10
  import { RelativeTime } from "../../components/relativeTime.js";
9
11
  import { useChatConfig } from "../core/config.js";
10
12
  import Markdown from "../ui/Markdown.js";
@@ -13,26 +15,19 @@ import { Reasoning } from "./Reasoning.js";
13
15
  import AgentMessageParts from "./agent/AgentMessageParts.js";
14
16
  import BranchNavigator from "./BranchNavigator.js";
15
17
  import { UserMessageText } from "./PastedText.js";
18
+ import MessageAttachments from "./MessageAttachments.js";
16
19
  import { jsx, jsxs } from "react/jsx-runtime";
17
20
  import { memo, useEffect, useRef, useState } from "react";
18
21
  import { DateTime } from "luxon";
19
22
  //#region src/ai/components/ChatMessage.tsx
23
+ /** A message action: the chat's square, tinted from the ink on hover. */
24
+ var actionClasses = "size-7 hover:not-disabled:not-aria-disabled:chat-tint";
20
25
  var COLLAPSE_THRESHOLD = 680;
21
26
  function parseTimestamp(iso) {
22
27
  if (!iso) return null;
23
28
  const dt = DateTime.fromISO(iso);
24
29
  return dt.isValid ? dt : null;
25
30
  }
26
- function triggerDownload(url, filename) {
27
- const href = safeUrl(url);
28
- if (!href) return;
29
- const a = document.createElement("a");
30
- a.href = href;
31
- a.download = filename;
32
- document.body.appendChild(a);
33
- a.click();
34
- document.body.removeChild(a);
35
- }
36
31
  function ChatMessage({ message, allMessages, isStreaming = false, latest = false, isEditing = false, showAuthor = false, currentUsername, attachmentDownloadUrl, onOpenAttachment, onNavigateBranch, onEdit, onSaveEdit, onCancelEdit, onRegenerate, onRetry, onOpenReasoning, onApprovalAnswer, flush }) {
37
32
  const isUser = message.role === "user";
38
33
  const isTool = message.role === "tool";
@@ -47,8 +42,6 @@ function ChatMessage({ message, allMessages, isStreaming = false, latest = false
47
42
  const showTurnDurationLabel = !isUser && !isStreaming && !hasReasoning && (!!message.reasoningDuration || toolCallCount > 0);
48
43
  const { messageMeta: t, thinking: tThinking } = useChatConfig().strings;
49
44
  const { common } = useStrings();
50
- const { copy, copied } = useCopy();
51
- const [showActions, setShowActions] = useState(false);
52
45
  const [editContent, setEditContent] = useState(message.content || "");
53
46
  const textareaRef = useRef(null);
54
47
  const contentRef = useRef(null);
@@ -106,16 +99,9 @@ function ChatMessage({ message, allMessages, isStreaming = false, latest = false
106
99
  const timestamp = parseTimestamp(message.timestamp);
107
100
  const totalTokens = message.tokensUsed?.total_tokens;
108
101
  const collapsed = !isUser && collapsible && !expanded && !isStreaming && !latest;
109
- const copyToClipboard = async () => {
110
- if (message.content) await copy(message.content, { quiet: true });
111
- };
112
102
  return /* @__PURE__ */ jsx("div", {
113
103
  role: "none",
114
104
  className: cx("group w-full", isUser ? "pt-8 pb-5" : "py-3", !flush && "px-4"),
115
- onMouseEnter: () => setShowActions(true),
116
- onMouseLeave: () => setShowActions(false),
117
- onFocus: () => setShowActions(true),
118
- onBlur: () => setShowActions(false),
119
105
  children: isEditing ? /* @__PURE__ */ jsxs("div", {
120
106
  className: "w-full",
121
107
  children: [/* @__PURE__ */ jsx("textarea", {
@@ -124,7 +110,8 @@ function ChatMessage({ message, allMessages, isStreaming = false, latest = false
124
110
  onChange: handleTextareaChange,
125
111
  onKeyDown: handleKeyDown,
126
112
  className: "chat-composer block w-full min-h-[100px] max-h-[200px] overflow-y-auto px-4 py-3.5 rounded-[1.25rem] bg-(--theme-panel-bg) theme-text text-[1rem] leading-relaxed resize-none focus:outline-none",
127
- placeholder: t.editPlaceholder
113
+ placeholder: t.editPlaceholder,
114
+ "aria-label": t.edit
128
115
  }), /* @__PURE__ */ jsxs("div", {
129
116
  className: "flex items-center justify-end gap-2 mt-2.5",
130
117
  children: [/* @__PURE__ */ jsx("button", {
@@ -212,110 +199,47 @@ function ChatMessage({ message, allMessages, isStreaming = false, latest = false
212
199
  className: "inline-flex items-center mt-1 text-[11px] italic theme-muted-text",
213
200
  children: t.stopped
214
201
  }),
215
- hasError && /* @__PURE__ */ jsxs("div", {
216
- className: "flex items-center gap-2 mt-2 p-3 rounded-lg bg-red-500/10 border border-red-500/30",
217
- children: [
218
- /* @__PURE__ */ jsx(ErrorIcon, { className: "w-4 h-4 text-red-600 flex-shrink-0" }),
219
- /* @__PURE__ */ jsx("span", {
220
- className: "text-sm text-red-700 flex-1",
221
- children: message.error
222
- }),
223
- onRetry && /* @__PURE__ */ jsxs("button", {
224
- type: "button",
225
- onClick: () => onRetry(message.id),
226
- className: "flex items-center gap-1 px-2 py-1 text-xs font-medium text-red-700 hover:bg-red-500/15 rounded transition-colors",
227
- children: [/* @__PURE__ */ jsx(RetryIcon, { className: "w-3 h-3" }), common.retry]
228
- })
229
- ]
202
+ hasError && /* @__PURE__ */ jsx(Callout, {
203
+ tone: "error",
204
+ size: "sm",
205
+ className: "mt-2",
206
+ action: onRetry && /* @__PURE__ */ jsxs(Button, {
207
+ size: "sm",
208
+ variant: "outline",
209
+ onClick: () => onRetry(message.id),
210
+ children: [/* @__PURE__ */ jsx(RetryIcon, {}), common.retry]
211
+ }),
212
+ children: message.error
230
213
  }),
231
- message.attachments && message.attachments.length > 0 && /* @__PURE__ */ jsx("div", {
232
- className: "mt-3 flex flex-wrap gap-2",
233
- children: message.attachments.map((att) => {
234
- const isImage = att.contentType?.startsWith("image/");
235
- const downloadUrl = att.id && attachmentDownloadUrl ? attachmentDownloadUrl(att.id) : void 0;
236
- if (isImage && downloadUrl) return /* @__PURE__ */ jsx("div", {
237
- className: "block rounded-lg overflow-hidden border theme-border",
238
- ...onOpenAttachment ? {
239
- onClick: () => onOpenAttachment(att),
240
- role: "button",
241
- style: { cursor: "pointer" }
242
- } : {},
243
- children: /* @__PURE__ */ jsx("img", {
244
- src: downloadUrl,
245
- alt: att.filename || "Image attachment",
246
- className: "max-w-[200px] max-h-[200px] object-cover",
247
- loading: "lazy"
248
- })
249
- }, att.id);
250
- if (downloadUrl) return /* @__PURE__ */ jsxs("button", {
251
- type: "button",
252
- onClick: () => onOpenAttachment ? onOpenAttachment(att) : triggerDownload(downloadUrl, att.filename || "File"),
253
- className: "inline-flex items-center gap-1.5 text-xs px-2.5 py-1.5 rounded-lg bg-(--theme-muted-panel-bg) hover:theme-hover transition-colors cursor-pointer",
254
- children: [
255
- /* @__PURE__ */ jsx(FileIcon, { className: "w-3.5 h-3.5 flex-shrink-0" }),
256
- /* @__PURE__ */ jsx("span", {
257
- className: "truncate max-w-[150px]",
258
- children: att.filename || "File"
259
- }),
260
- att.size ? /* @__PURE__ */ jsxs("span", {
261
- className: "theme-muted-text",
262
- children: [
263
- "(",
264
- formatBytes(att.size),
265
- ")"
266
- ]
267
- }) : null,
268
- /* @__PURE__ */ jsx(DownloadIcon, { className: "w-3 h-3 flex-shrink-0" })
269
- ]
270
- }, att.id);
271
- return /* @__PURE__ */ jsxs("div", {
272
- className: "inline-flex items-center gap-1.5 text-xs px-2.5 py-1.5 rounded-lg bg-(--theme-muted-panel-bg) theme-muted-text",
273
- ...onOpenAttachment ? {
274
- onClick: () => onOpenAttachment(att),
275
- role: "button",
276
- style: { cursor: "pointer" }
277
- } : {},
278
- children: [
279
- /* @__PURE__ */ jsx(FileIcon, { className: "w-3.5 h-3.5 flex-shrink-0" }),
280
- /* @__PURE__ */ jsx("span", {
281
- className: "truncate max-w-[150px]",
282
- children: att.filename || "File"
283
- }),
284
- att.size ? /* @__PURE__ */ jsxs("span", {
285
- className: "theme-muted-text",
286
- children: [
287
- "(",
288
- formatBytes(att.size),
289
- ")"
290
- ]
291
- }) : null
292
- ]
293
- }, att.id);
294
- })
214
+ message.attachments && message.attachments.length > 0 && /* @__PURE__ */ jsx(MessageAttachments, {
215
+ attachments: message.attachments,
216
+ downloadUrl: attachmentDownloadUrl,
217
+ onOpen: onOpenAttachment,
218
+ className: isRightAligned ? "justify-end" : void 0
295
219
  }),
296
220
  !isStreaming && !isEditing && /* @__PURE__ */ jsxs("div", {
297
- className: cx("chat-actions flex items-center gap-0.5 transition-opacity duration-150", isRightAligned ? "absolute right-0 top-full -mr-1.5 mt-0.5" : "-ml-1.5 mt-2", isRightAligned && (showActions ? "opacity-100" : "opacity-0 pointer-events-none")),
221
+ className: cx("chat-actions flex items-center gap-0.5 transition-opacity duration-150", isRightAligned ? "absolute right-0 top-full -mr-1.5 mt-0.5" : "-ml-1.5 mt-2", isRightAligned && "pointer-events-none opacity-0 group-focus-within:pointer-events-auto group-focus-within:opacity-100 group-hover:pointer-events-auto group-hover:opacity-100"),
298
222
  children: [
299
- /* @__PURE__ */ jsx("button", {
300
- type: "button",
301
- onClick: copyToClipboard,
302
- className: "p-1.5 rounded-md hover:chat-tint theme-muted-text hover:theme-text transition-colors",
303
- title: t.copy,
304
- children: copied ? /* @__PURE__ */ jsx(CheckIcon, { className: "w-4 h-4 text-green-500" }) : /* @__PURE__ */ jsx(CopyIcon, { className: "w-4 h-4" })
223
+ message.content && /* @__PURE__ */ jsx(CopyButton, {
224
+ text: message.content ?? "",
225
+ label: t.copyLabel,
226
+ quiet: true,
227
+ size: "md",
228
+ className: actionClasses
305
229
  }),
306
- isUser && !isOtherUser && onEdit && /* @__PURE__ */ jsx("button", {
307
- type: "button",
230
+ isUser && !isOtherUser && onEdit && /* @__PURE__ */ jsx(IconButton, {
231
+ label: t.edit,
308
232
  onClick: () => onEdit(message.id),
309
- className: "p-1.5 rounded-md hover:chat-tint theme-muted-text hover:theme-text transition-colors",
310
- title: t.edit,
311
- children: /* @__PURE__ */ jsx(EditIcon, { className: "w-4 h-4" })
233
+ size: "md",
234
+ className: actionClasses,
235
+ icon: /* @__PURE__ */ jsx(EditIcon, {})
312
236
  }),
313
- !isUser && onRegenerate && /* @__PURE__ */ jsx("button", {
314
- type: "button",
237
+ !isUser && onRegenerate && /* @__PURE__ */ jsx(IconButton, {
238
+ label: t.regenerate,
315
239
  onClick: () => onRegenerate(message.id),
316
- className: "p-1.5 rounded-md hover:chat-tint theme-muted-text hover:theme-text transition-colors",
317
- title: t.regenerate,
318
- children: /* @__PURE__ */ jsx(RetryIcon, { className: "w-4 h-4" })
240
+ size: "md",
241
+ className: actionClasses,
242
+ icon: /* @__PURE__ */ jsx(RetryIcon, {})
319
243
  }),
320
244
  !isUser && onNavigateBranch && /* @__PURE__ */ jsx(BranchNavigator, {
321
245
  messages: allMessages,
@@ -323,7 +247,7 @@ function ChatMessage({ message, allMessages, isStreaming = false, latest = false
323
247
  onNavigate: onNavigateBranch
324
248
  }),
325
249
  (timestamp || !isUser && (message.model || totalTokens)) && /* @__PURE__ */ jsxs("span", {
326
- className: cx("chat-meta flex items-center gap-2 ml-2 text-xs tabular-nums theme-muted-text transition-opacity duration-150 select-none", showActions ? "opacity-100" : "opacity-0"),
250
+ className: cx("chat-meta flex items-center gap-2 ml-2 text-xs tabular-nums theme-muted-text transition-opacity duration-150 select-none", "opacity-0 group-focus-within:opacity-100 group-hover:opacity-100"),
327
251
  children: [
328
252
  /* @__PURE__ */ jsx(RelativeTime, { value: timestamp }),
329
253
  !isUser && message.model && /* @__PURE__ */ jsx("span", { children: message.model }),
@@ -3,6 +3,8 @@ import type { ApprovalAnswerValue, ChatMessage as Message } from '../types.js';
3
3
  interface ChatMessageListProps {
4
4
  messages: Message[];
5
5
  allMessages: Message[];
6
+ /** The reply being written. Left unset, the list shows the chat provider's
7
+ * own stream; a host that streams elsewhere passes its reply, or null. */
6
8
  streamingMessage?: Message | null;
7
9
  queuedMessages?: Message[];
8
10
  onRemoveQueued?: (id: string) => void;
@@ -30,7 +32,8 @@ interface ChatMessageListProps {
30
32
  isStreaming?: boolean;
31
33
  /** Rows without their own side padding, for a host that pads the column. */
32
34
  flush?: boolean;
33
- /** Names the list as a live log for assistive technology. */
35
+ /** Names the list, a live log, for assistive technology. Defaults to the
36
+ * chat strings' `thread.label`. */
34
37
  label?: string;
35
38
  /** Rendered after the last message, inside the scrolling column. */
36
39
  footer?: ReactNode;
@@ -38,5 +41,5 @@ interface ChatMessageListProps {
38
41
  * for the first frame is not announced twice. */
39
42
  workingIndicator?: boolean;
40
43
  }
41
- export default function ChatMessageList({ messages, allMessages, streamingMessage, queuedMessages, onRemoveQueued, canRemoveQueued, queueDelivery, editingMessageId, showAuthor, currentUsername, onNavigateBranch, onEditMessage, onSaveEdit, onCancelEdit, onRegenerateMessage, onRetryMessage, onOpenReasoning, onApprovalAnswer, inputElement, isStreaming: isStreamingProp, flush, label, footer, workingIndicator, }: ChatMessageListProps): import("react").JSX.Element;
44
+ export default function ChatMessageList({ messages, allMessages, streamingMessage: streamingMessageProp, queuedMessages, onRemoveQueued, canRemoveQueued, queueDelivery, editingMessageId, showAuthor, currentUsername, onNavigateBranch, onEditMessage, onSaveEdit, onCancelEdit, onRegenerateMessage, onRetryMessage, onOpenReasoning, onApprovalAnswer, inputElement, isStreaming: isStreamingProp, flush, label, footer, workingIndicator, }: ChatMessageListProps): import("react").JSX.Element;
42
45
  export {};