@parallelworks/ui 0.19.1 → 0.21.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 (172) hide show
  1. package/README.md +1 -1
  2. package/dist/ai/adapter/openai/createOpenAIStream.js +3 -2
  3. package/dist/ai/agent/diff.d.ts +0 -1
  4. package/dist/ai/agent/diff.js +3 -2
  5. package/dist/ai/agent/toolfmt.js +2 -3
  6. package/dist/ai/ai.css +5 -0
  7. package/dist/ai/components/AllocationSelector.js +3 -2
  8. package/dist/ai/components/AttachmentManager.js +5 -2
  9. package/dist/ai/components/BranchNavigator.js +4 -2
  10. package/dist/ai/components/ChatInput.d.ts +2 -5
  11. package/dist/ai/components/ChatInput.js +10 -7
  12. package/dist/ai/components/ChatMessage.js +11 -8
  13. package/dist/ai/components/ChatSidebar.d.ts +0 -1
  14. package/dist/ai/components/ChatSidebar.js +4 -6
  15. package/dist/ai/components/ChatThread.js +5 -8
  16. package/dist/ai/components/ModelSelector.js +3 -2
  17. package/dist/ai/components/ShareDialog.js +6 -3
  18. package/dist/ai/components/SlashMenu.d.ts +0 -1
  19. package/dist/ai/components/agent/ActivityLine.d.ts +0 -1
  20. package/dist/ai/components/agent/ActivityLine.js +2 -2
  21. package/dist/ai/components/agent/AgentMessageParts.d.ts +1 -26
  22. package/dist/ai/components/agent/AgentMessageParts.js +4 -5
  23. package/dist/ai/core/ChatProvider.d.ts +2 -6
  24. package/dist/ai/core/ChatProvider.js +42 -67
  25. package/dist/ai/core/KeyboardShortcutsProvider.js +1 -1
  26. package/dist/ai/core/chatReducer.d.ts +0 -6
  27. package/dist/ai/core/chatReducer.js +1 -15
  28. package/dist/ai/core/config.js +2 -1
  29. package/dist/ai/core/useKeyboardShortcuts.d.ts +3 -1
  30. package/dist/ai/core/useKeyboardShortcuts.js +9 -11
  31. package/dist/ai/core/useSSEStream.d.ts +0 -1
  32. package/dist/ai/core/useSSEStream.js +5 -17
  33. package/dist/ai/strings.d.ts +0 -1
  34. package/dist/ai/strings.js +0 -1
  35. package/dist/ai/types.d.ts +1 -1
  36. package/dist/ai/ui/Avatar.d.ts +1 -2
  37. package/dist/ai/ui/Avatar.js +7 -18
  38. package/dist/ai/ui/Markdown.d.ts +1 -3
  39. package/dist/ai/ui/Markdown.js +1 -2
  40. package/dist/ai/ui/sidebar.d.ts +0 -1
  41. package/dist/ai/ui/sidebar.js +3 -1
  42. package/dist/ai/utils.js +1 -1
  43. package/dist/assets/editor.worker-DfmPPyOL.js +60 -0
  44. package/dist/assets/json.worker-Dqgjpq9F.js +92 -0
  45. package/dist/assets/yaml.worker-DLx0whrM.js +1441 -0
  46. package/dist/components/Avatar.js +4 -2
  47. package/dist/components/BareModal.js +1 -5
  48. package/dist/components/Breadcrumbs.js +21 -14
  49. package/dist/components/CollapsiblePanel.d.ts +1 -3
  50. package/dist/components/CollapsiblePanel.js +5 -5
  51. package/dist/components/Dropdown.d.ts +0 -4
  52. package/dist/components/Dropdown.js +5 -6
  53. package/dist/components/Listbox.js +14 -18
  54. package/dist/components/MultiSelect.js +5 -8
  55. package/dist/components/Pagination.js +1 -1
  56. package/dist/components/Provider.d.ts +0 -8
  57. package/dist/components/Provider.js +12 -4
  58. package/dist/components/SwitchToggle.js +3 -5
  59. package/dist/components/Table.d.ts +1 -3
  60. package/dist/components/dropdownUtils.js +5 -6
  61. package/dist/components/relativeTime.d.ts +0 -7
  62. package/dist/components/relativeTime.js +1 -1
  63. package/dist/duration.js +1 -1
  64. package/dist/editor/Editor.d.ts +1 -1
  65. package/dist/editor/Editor.js +1 -1
  66. package/dist/editor/EditorField.d.ts +0 -1
  67. package/dist/editor/EditorField.js +8 -8
  68. package/dist/editor/FormCodePanel.js +14 -20
  69. package/dist/editor/Monaco.js +2 -25
  70. package/dist/editor/workers.d.ts +5 -0
  71. package/dist/editor/workers.js +14 -8
  72. package/dist/editor/yaml.d.ts +9 -1
  73. package/dist/editor/yaml.worker.d.ts +1 -0
  74. package/dist/editor/yamlLanguage.d.ts +6 -0
  75. package/dist/editor/yamlLanguage.js +312 -0
  76. package/dist/file-explorer/FileExplorer.js +60 -60
  77. package/dist/file-explorer/FileExplorerContext.d.ts +1 -2
  78. package/dist/file-explorer/FileExplorerContext.js +92 -125
  79. package/dist/file-explorer/FilePreview/Inline.d.ts +1 -2
  80. package/dist/file-explorer/FilePreview/Inline.js +2 -2
  81. package/dist/file-explorer/FilePreview/Viewer.js +0 -4
  82. package/dist/file-explorer/FilePreview/index.d.ts +1 -2
  83. package/dist/file-explorer/FilePreview/index.js +3 -3
  84. package/dist/file-explorer/FilePreview/parse.d.ts +2 -1
  85. package/dist/file-explorer/FilePreview/useObjectBlobUrl.d.ts +5 -4
  86. package/dist/file-explorer/FilePreview/useObjectBlobUrl.js +23 -36
  87. package/dist/file-explorer/FilePreview/useObjectText.d.ts +1 -2
  88. package/dist/file-explorer/FilePreview/useObjectText.js +5 -7
  89. package/dist/file-explorer/FileRow.js +1 -1
  90. package/dist/file-explorer/FileRowsWindow.d.ts +5 -12
  91. package/dist/file-explorer/FileTable.d.ts +2 -6
  92. package/dist/file-explorer/LoadMoreRow.d.ts +8 -0
  93. package/dist/file-explorer/TreeView.d.ts +0 -1
  94. package/dist/file-explorer/TreeView.js +6 -6
  95. package/dist/file-explorer/helpers.js +2 -4
  96. package/dist/file-explorer/lib/fileExplorer.d.ts +0 -2
  97. package/dist/file-explorer/lib/types.d.ts +0 -1
  98. package/dist/file-explorer/lib/utils.js +15 -20
  99. package/dist/file-explorer/toasts.js +36 -34
  100. package/dist/form/Fields/BooleanField.js +5 -4
  101. package/dist/form/Fields/CheckboxGroupField.js +5 -4
  102. package/dist/form/Fields/ColorField.js +5 -4
  103. package/dist/form/Fields/DropdownField.d.ts +0 -1
  104. package/dist/form/Fields/DropdownField.js +10 -13
  105. package/dist/form/Fields/DurationField.js +2 -2
  106. package/dist/form/Fields/LabelField.js +5 -5
  107. package/dist/form/Fields/MultiDropdownField.d.ts +1 -1
  108. package/dist/form/Fields/MultiDropdownField.js +8 -11
  109. package/dist/form/Fields/NumberField.js +6 -5
  110. package/dist/form/Fields/ObjectField.d.ts +1 -2
  111. package/dist/form/Fields/ObjectField.js +22 -21
  112. package/dist/form/Fields/RadioField.js +4 -3
  113. package/dist/form/Fields/RangeField.js +7 -6
  114. package/dist/form/Fields/StringField.js +2 -1
  115. package/dist/form/Fields/TextAreaField.js +2 -1
  116. package/dist/form/Form.d.ts +7 -8
  117. package/dist/form/Form.js +52 -78
  118. package/dist/form/SpecialTypes/DropdownWrapper.d.ts +0 -2
  119. package/dist/form/SpecialTypes/DropdownWrapper.js +1 -3
  120. package/dist/form/SpecialTypes/FormikCustomDropdown.d.ts +1 -3
  121. package/dist/form/SpecialTypes/FormikCustomDropdown.js +4 -7
  122. package/dist/form/SpecialTypes/FormikCustomInput.js +13 -11
  123. package/dist/form/Wizard/WizardContainer.js +8 -25
  124. package/dist/form/Wizard/WizardNavigation.d.ts +1 -1
  125. package/dist/form/Wizard/WizardNavigation.js +5 -6
  126. package/dist/form/Wizard/WizardStepContent.d.ts +1 -1
  127. package/dist/form/Wizard/WizardStepContent.js +2 -2
  128. package/dist/form/Wizard/WizardStepIndicator.js +7 -17
  129. package/dist/form/Wizard/types.d.ts +4 -46
  130. package/dist/form/Wizard/useWizardState.d.ts +2 -10
  131. package/dist/form/Wizard/useWizardState.js +42 -92
  132. package/dist/form/Wizard/utils.js +6 -11
  133. package/dist/form/lib.js +28 -54
  134. package/dist/form/types/fieldTypes.d.ts +4 -5
  135. package/dist/form/utils/dependentOptions.d.ts +6 -0
  136. package/dist/form/utils/dependentOptions.js +21 -0
  137. package/dist/form/utils/getParentValue.d.ts +2 -1
  138. package/dist/form/utils/getParentValue.js +10 -15
  139. package/dist/form/utils/getValueUsingPath.js +1 -1
  140. package/dist/graph/AnnotationBanner.d.ts +1 -1
  141. package/dist/graph/AnnotationBanner.js +1 -1
  142. package/dist/graph/Collapse.d.ts +3 -3
  143. package/dist/graph/Collapse.js +3 -8
  144. package/dist/graph/DependencyGraph.d.ts +5 -4
  145. package/dist/graph/DependencyGraph.js +172 -222
  146. package/dist/graph/JobSummary.d.ts +10 -8
  147. package/dist/graph/JobSummary.js +13 -13
  148. package/dist/graph/MatrixGroup.d.ts +5 -17
  149. package/dist/graph/MatrixGroup.js +11 -22
  150. package/dist/graph/TreeView.d.ts +1 -3
  151. package/dist/graph/TreeView.js +104 -491
  152. package/dist/graph/types.d.ts +3 -2
  153. package/dist/graph/util.d.ts +5 -0
  154. package/dist/graph/util.js +18 -0
  155. package/dist/icons.d.ts +5 -5
  156. package/dist/icons.js +6 -6
  157. package/dist/index.d.ts +1 -1
  158. package/dist/list/ListViewControls.js +17 -35
  159. package/dist/list/RowContextMenu.js +9 -8
  160. package/dist/list/UserHoverCard.js +5 -5
  161. package/dist/logviewer/LogViewer.js +7 -9
  162. package/dist/logviewer/segments.js +2 -2
  163. package/dist/safeUrl.d.ts +6 -0
  164. package/dist/safeUrl.js +22 -0
  165. package/dist/styles/theme.css +1 -0
  166. package/dist/styles.css +1 -1
  167. package/dist/theme/index.js +5 -5
  168. package/package.json +33 -37
  169. package/dist/ai/ui/buttons.d.ts +0 -1
  170. package/dist/assets/editor.worker-BmHUIVG4.js +0 -14807
  171. package/dist/assets/json.worker-D8bJ92nw.js +0 -22207
  172. package/dist/assets/yaml.worker-C4gyGAko.js +0 -50201
package/README.md CHANGED
@@ -11,7 +11,7 @@ so an application ships only what it imports:
11
11
  | `./list` | list pages, tables, row menus and list controls |
12
12
  | `./form` | `DynamicForm` and its field registry |
13
13
  | `./graph` | job dependency graph and tree view |
14
- | `./editor` | Monaco editor (`monaco-editor` and `monaco-yaml` are optional peers) |
14
+ | `./editor` | Monaco editor (`monaco-editor` is an optional peer); YAML validation, completion and hover run in a bundled worker |
15
15
  | `./logviewer` | log viewer with ANSI color and search |
16
16
  | `./file-explorer` | object storage explorer over a host-supplied provider |
17
17
  | `./ai`, `./ai/openai`, `./ai/mock` | chat interface and adapters |
@@ -5,8 +5,9 @@ import { processSSEStream } from "./sse.js";
5
5
  //#region src/ai/adapter/openai/createOpenAIStream.ts
6
6
  function createOpenAICompatibleStream(config) {
7
7
  return async (req, handlers, signal) => {
8
- const useResponses = req.useResponses === true && !!config.responsesUrl;
9
- const url = useResponses ? config.responsesUrl : config.completionsUrl;
8
+ const responsesUrl = req.useResponses === true ? config.responsesUrl : void 0;
9
+ const useResponses = !!responsesUrl;
10
+ const url = responsesUrl || config.completionsUrl;
10
11
  const body = useResponses ? buildResponsesRequestBody(req.model, req.messages) : {
11
12
  model: req.model,
12
13
  messages: req.messages,
@@ -1,6 +1,5 @@
1
1
  export declare const MAX_EDIT_DIFF_LINES = 100;
2
2
  export declare const MAX_WRITE_DIFF_LINES = 50;
3
- export declare const DIFF_CONTEXT = 3;
4
3
  export interface EditToolArgs {
5
4
  path?: string;
6
5
  old_string?: string;
@@ -2,6 +2,7 @@ import { sanitizeLabel } from "./toolfmt.js";
2
2
  //#region src/ai/agent/diff.ts
3
3
  var MAX_EDIT_DIFF_LINES = 100;
4
4
  var MAX_WRITE_DIFF_LINES = 50;
5
+ var DIFF_CONTEXT = 3;
5
6
  function isEditDiffTool(name) {
6
7
  return name === "EditFile" || name === "WriteFile";
7
8
  }
@@ -133,8 +134,8 @@ function computeEditDiff(oldString, newString, startLine = 0) {
133
134
  if (!op) continue;
134
135
  if (op.tag === "e") {
135
136
  const len = op.i2 - op.i1;
136
- const leading = k > 0 ? Math.min(3, len) : 0;
137
- const trailing = k < ops.length - 1 ? Math.min(3, len) : 0;
137
+ const leading = k > 0 ? Math.min(DIFF_CONTEXT, len) : 0;
138
+ const trailing = k < ops.length - 1 ? Math.min(DIFF_CONTEXT, len) : 0;
138
139
  const showAll = len <= leading + trailing;
139
140
  const emit = (offset) => {
140
141
  const oldNo = op.i1 + offset + 1;
@@ -53,7 +53,7 @@ function clip(s, max) {
53
53
  const r = [...s];
54
54
  if (r.length <= max) return s;
55
55
  if (max <= 3) return r.slice(0, max).join("");
56
- return r.slice(0, max - 3).join("") + "...";
56
+ return `${r.slice(0, max - 3).join("")}...`;
57
57
  }
58
58
  function sanitizeLabel(s) {
59
59
  let out = "";
@@ -211,8 +211,7 @@ function grepMatchPath(line, lineNums) {
211
211
  const i = line.indexOf(":");
212
212
  return i >= 0 ? line.slice(0, i) : null;
213
213
  }
214
- const m = GREP_CONTENT_PATH.exec(line);
215
- return m ? m[1] ?? null : null;
214
+ return GREP_CONTENT_PATH.exec(line)?.[1] ?? null;
216
215
  }
217
216
  function grepSearchSummary(argsJSON, body) {
218
217
  if (body === "No matches found." || body === "No results after applying offset.") return "No matches";
package/dist/ai/ai.css CHANGED
@@ -208,6 +208,7 @@
208
208
 
209
209
  /* Light mode: use the primary Shiki theme (--sdm-bg/--sdm-c) */
210
210
  .markdown [data-streamdown="code-block-body"] pre {
211
+ /* biome-ignore lint/complexity/noImportantStyles: the highlighter sets the background as an inline style */
211
212
  background-color: var(--sdm-bg, inherit) !important;
212
213
  color: var(--sdm-fg, inherit);
213
214
  }
@@ -217,6 +218,7 @@
217
218
 
218
219
  /* Dark mode: switch to --shiki-dark variants */
219
220
  .dark .markdown [data-streamdown="code-block-body"] pre {
221
+ /* biome-ignore lint/complexity/noImportantStyles: the highlighter sets the background as an inline style */
220
222
  background-color: var(--shiki-dark-bg, var(--sdm-bg, inherit)) !important;
221
223
  color: var(--shiki-dark, var(--sdm-fg, inherit));
222
224
  }
@@ -233,6 +235,7 @@
233
235
  /* Remove prose backtick pseudo-elements on inline code */
234
236
  .markdown code::before,
235
237
  .markdown code::after {
238
+ /* biome-ignore lint/complexity/noImportantStyles: the typography styles add these with a more specific selector */
236
239
  content: none !important;
237
240
  }
238
241
 
@@ -250,7 +253,9 @@
250
253
  @media (hover: none) {
251
254
  .chat-actions,
252
255
  .chat-meta {
256
+ /* biome-ignore lint/complexity/noImportantStyles: must win over the utility classes that hide these until hover */
253
257
  opacity: 1 !important;
258
+ /* biome-ignore lint/complexity/noImportantStyles: must win over the utility classes that hide these until hover */
254
259
  pointer-events: auto !important;
255
260
  }
256
261
  }
@@ -10,9 +10,10 @@ function AllocationSelector({ variant = "default" } = {}) {
10
10
  const { adapter, selectedProvider, selectedAllocation, setSelectedAllocation } = useChat();
11
11
  const isOrgProvider = isOrgModel(selectedProvider);
12
12
  const allocationsAdapter = adapter.allocations;
13
- const { data: allocations } = useSWR(isOrgProvider && allocationsAdapter ? ["ai-chat-allocations"] : null, () => allocationsAdapter.list());
13
+ const { data: allocations } = useSWR(isOrgProvider && allocationsAdapter ? ["ai-chat-allocations"] : null, () => allocationsAdapter?.list());
14
14
  useEffect(() => {
15
- if (isOrgProvider && allocations?.length && !selectedAllocation) setSelectedAllocation(allocations[0].name);
15
+ const first = allocations?.[0];
16
+ if (isOrgProvider && first && !selectedAllocation) setSelectedAllocation(first.name);
16
17
  }, [
17
18
  isOrgProvider,
18
19
  allocations,
@@ -1,3 +1,4 @@
1
+ import { safeUrl } from "../../safeUrl.js";
1
2
  import { DownloadFileIcon, FileIcon, TrashIcon } from "../../icons.js";
2
3
  import { useChatConfig } from "../core/config.js";
3
4
  import { useChat } from "../core/ChatProvider.js";
@@ -10,8 +11,10 @@ import { DateTime } from "luxon";
10
11
  import useSWRInfinite from "swr/infinite";
11
12
  //#region src/ai/components/AttachmentManager.tsx
12
13
  function triggerDownload(url, filename) {
14
+ const href = safeUrl(url);
15
+ if (!href) return;
13
16
  const a = document.createElement("a");
14
- a.href = url;
17
+ a.href = href;
15
18
  a.download = filename;
16
19
  document.body.appendChild(a);
17
20
  a.click();
@@ -29,7 +32,7 @@ function AttachmentManager() {
29
32
  [attachmentId]: state
30
33
  });
31
34
  }, []);
32
- const { data: pages, size, setSize, isLoading, isValidating, mutate } = useSWRInfinite(attachmentsAdapter ? attachmentsPageKey : () => null, ([, cursor]) => attachmentsAdapter.list({
35
+ const { data: pages, size, setSize, isLoading, isValidating, mutate } = useSWRInfinite(attachmentsAdapter ? attachmentsPageKey : () => null, ([, cursor]) => attachmentsAdapter?.list({
33
36
  limit: 24,
34
37
  cursor
35
38
  }));
@@ -16,10 +16,12 @@ function BranchNavigator({ messages, currentMessageId, onNavigate }) {
16
16
  const hasPrev = currentIndex > 0;
17
17
  const hasNext = currentIndex < siblings.length - 1;
18
18
  const handlePrev = () => {
19
- if (hasPrev) onNavigate(siblings[currentIndex - 1].id);
19
+ const prev = siblings[currentIndex - 1];
20
+ if (hasPrev && prev) onNavigate(prev.id);
20
21
  };
21
22
  const handleNext = () => {
22
- if (hasNext) onNavigate(siblings[currentIndex + 1].id);
23
+ const next = siblings[currentIndex + 1];
24
+ if (hasNext && next) onNavigate(next.id);
23
25
  };
24
26
  return /* @__PURE__ */ jsxs("div", {
25
27
  className: "inline-flex items-center gap-1 text-xs theme-muted-text",
@@ -1,11 +1,8 @@
1
1
  import { type ReactNode } from 'react';
2
+ import type { AttachmentMeta } from '../types.js';
2
3
  import { type SlashMenuConfig } from './SlashMenu.js';
3
4
  interface ChatInputProps {
4
- onSend: (content: string, attachmentIds?: string[], attachmentMeta?: Record<string, {
5
- filename: string;
6
- contentType: string;
7
- size: number;
8
- }>) => void;
5
+ onSend: (content: string, attachmentIds?: string[], attachmentMeta?: AttachmentMeta) => void;
9
6
  disabled?: boolean;
10
7
  placeholder?: string | undefined;
11
8
  conversationId?: string;
@@ -139,14 +139,17 @@ var ChatInput = forwardRef(function ChatInput({ onSend, disabled = false, placeh
139
139
  flushQueuedMessages();
140
140
  return;
141
141
  }
142
- const uploaded = attachments.filter((a) => a.uploaded && a.id);
143
- const attachmentIds = uploaded.map((a) => a.id);
142
+ const attachmentIds = [];
144
143
  const meta = {};
145
- for (const a of uploaded) meta[a.id] = {
146
- filename: a.filename,
147
- contentType: a.mimeType,
148
- size: a.size
149
- };
144
+ for (const a of attachments) {
145
+ if (!a.uploaded || !a.id) continue;
146
+ attachmentIds.push(a.id);
147
+ meta[a.id] = {
148
+ filename: a.filename,
149
+ contentType: a.mimeType,
150
+ size: a.size
151
+ };
152
+ }
150
153
  onSend(input.trim(), attachmentIds.length > 0 ? attachmentIds : void 0, attachmentIds.length > 0 ? meta : void 0);
151
154
  setInput("");
152
155
  clearDraft(conversationId);
@@ -1,3 +1,4 @@
1
+ import { safeUrl } from "../../safeUrl.js";
1
2
  import { CheckIcon, ChevronRightIcon, CopyIcon, DownloadFileIcon, EditIcon, ErrorIcon, FileIcon, RefreshIcon, RetryIcon } from "../../icons.js";
2
3
  import { durationToAbsHumanDuration } from "../../duration.js";
3
4
  import { useChatConfig } from "../core/config.js";
@@ -23,8 +24,10 @@ function parseTimestamp(iso) {
23
24
  return dt.isValid ? dt : null;
24
25
  }
25
26
  function triggerDownload(url, filename) {
27
+ const href = safeUrl(url);
28
+ if (!href) return;
26
29
  const a = document.createElement("a");
27
- a.href = url;
30
+ a.href = href;
28
31
  a.download = filename;
29
32
  document.body.appendChild(a);
30
33
  a.click();
@@ -33,7 +36,8 @@ function triggerDownload(url, filename) {
33
36
  function ChatMessage({ message, allMessages, isStreaming = false, isEditing = false, showAuthor = false, currentUsername, attachmentDownloadUrl, onOpenAttachment, onNavigateBranch, onEdit, onSaveEdit, onCancelEdit, onRegenerate, onRetry, onOpenReasoning, onApprovalAnswer, flush }) {
34
37
  const isUser = message.role === "user";
35
38
  const isTool = message.role === "tool";
36
- const isOtherUser = isUser && showAuthor && !!message.author && !!currentUsername && message.author.username !== currentUsername;
39
+ const otherAuthor = isUser && showAuthor && message.author && currentUsername && message.author.username !== currentUsername ? message.author : void 0;
40
+ const isOtherUser = !!otherAuthor;
37
41
  const isRightAligned = isUser && !isOtherUser;
38
42
  const hasError = !!message.error;
39
43
  const hasReasoning = !!message.reasoning;
@@ -166,16 +170,15 @@ function ChatMessage({ message, allMessages, isStreaming = false, isEditing = fa
166
170
  onApprovalAnswer
167
171
  })
168
172
  }),
169
- isOtherUser && /* @__PURE__ */ jsx("span", {
173
+ otherAuthor && /* @__PURE__ */ jsx("span", {
170
174
  className: "text-xs theme-muted-text",
171
- children: message.author.name || message.author.username
175
+ children: otherAuthor.name || otherAuthor.username
172
176
  }),
173
- message.content ? isUser && isOtherUser ? /* @__PURE__ */ jsxs("div", {
177
+ message.content ? otherAuthor ? /* @__PURE__ */ jsxs("div", {
174
178
  className: "flex items-end gap-2",
175
179
  children: [/* @__PURE__ */ jsx(Avatar, {
176
- src: message.author.avatarUrl ?? void 0,
177
- name: message.author.name || message.author.username,
178
- size: "sm",
180
+ src: otherAuthor.avatarUrl ?? void 0,
181
+ name: otherAuthor.name || otherAuthor.username,
179
182
  className: "flex-shrink-0 mb-0.5"
180
183
  }), /* @__PURE__ */ jsx("div", {
181
184
  className: "px-4 py-2.5 chat-ink rounded-2xl rounded-bl-md max-w-[70%] inline-block whitespace-pre-wrap text-base leading-6 bg-[color-mix(in_oklab,var(--theme-panel)_6%,transparent)]",
@@ -1,4 +1,3 @@
1
- export type ConversationGroupKey = 'groupToday' | 'groupYesterday' | 'groupThisWeek' | 'groupThisMonth' | 'groupOlder';
2
1
  export default function ChatSidebar({ actions, }: {
3
2
  /** False when the host's page header carries new chat, attachments and search. */
4
3
  actions?: boolean;
@@ -85,16 +85,15 @@ function ConversationRow({ conv, isCurrent, sharingAvailable, openMenu, onRename
85
85
  var SIDEBAR_WIDTH_STORAGE_KEY = "aiChatSidebarWidth";
86
86
  var SIDEBAR_MIN_WIDTH_PX = 200;
87
87
  var SIDEBAR_MAX_WIDTH_PX = 480;
88
- var SIDEBAR_DEFAULT_WIDTH_PX = 256;
89
88
  function clampSidebarWidth(width) {
90
89
  return Math.min(SIDEBAR_MAX_WIDTH_PX, Math.max(SIDEBAR_MIN_WIDTH_PX, width));
91
90
  }
92
91
  function readStoredSidebarWidth() {
93
92
  try {
94
93
  const stored = Number(window.localStorage.getItem(SIDEBAR_WIDTH_STORAGE_KEY));
95
- return Number.isFinite(stored) && stored > 0 ? clampSidebarWidth(stored) : SIDEBAR_DEFAULT_WIDTH_PX;
94
+ return Number.isFinite(stored) && stored > 0 ? clampSidebarWidth(stored) : 256;
96
95
  } catch {
97
- return SIDEBAR_DEFAULT_WIDTH_PX;
96
+ return 256;
98
97
  }
99
98
  }
100
99
  function storeSidebarWidth(width) {
@@ -125,8 +124,8 @@ function useResizableSidebarWidth() {
125
124
  window.addEventListener("pointerup", onUp);
126
125
  };
127
126
  const reset = () => {
128
- setWidth(SIDEBAR_DEFAULT_WIDTH_PX);
129
- storeSidebarWidth(SIDEBAR_DEFAULT_WIDTH_PX);
127
+ setWidth(256);
128
+ storeSidebarWidth(256);
130
129
  };
131
130
  const nudge = (delta) => {
132
131
  const next = clampSidebarWidth(widthRef.current + delta);
@@ -211,7 +210,6 @@ function ChatSidebar({ actions = true }) {
211
210
  /* @__PURE__ */ jsxs(SidebarPanel, {
212
211
  collapsed: sidebarCollapsed,
213
212
  width: sidebarWidth,
214
- railWidth: 48,
215
213
  resizing,
216
214
  children: [
217
215
  !sidebarCollapsed && /* @__PURE__ */ jsx("div", {
@@ -61,11 +61,8 @@ function ChatThread({ conversationId }) {
61
61
  setEditingState(null);
62
62
  }, []);
63
63
  const handleEditLastMessage = useCallback(() => {
64
- const conversationMessages = currentConversation?.messages || [];
65
- for (let i = conversationMessages.length - 1; i >= 0; i--) if (conversationMessages[i].role === "user") {
66
- handleEditMessage(conversationMessages[i].id);
67
- break;
68
- }
64
+ const lastUserMessage = (currentConversation?.messages || []).findLast((m) => m.role === "user");
65
+ if (lastUserMessage) handleEditMessage(lastUserMessage.id);
69
66
  }, [currentConversation?.messages, handleEditMessage]);
70
67
  const handleNavigateBranch = useCallback((messageId) => {
71
68
  navigateToBranch?.(messageId);
@@ -73,9 +70,9 @@ function ChatThread({ conversationId }) {
73
70
  const handleRegenerateMessage = useCallback((messageId) => {
74
71
  const conversationMessages = currentConversation?.messages || [];
75
72
  const assistantMessage = conversationMessages.find((m) => m.id === messageId);
76
- if (!assistantMessage || assistantMessage.role !== "assistant") return;
73
+ if (assistantMessage?.role !== "assistant") return;
77
74
  const userMessage = conversationMessages.find((m) => m.id === assistantMessage.parentId);
78
- if (!userMessage || userMessage.role !== "user") return;
75
+ if (userMessage?.role !== "user") return;
79
76
  sendMessage(userMessage.content || "", userMessage.attachments?.map((a) => a.id), userMessage.parentId || void 0);
80
77
  }, [currentConversation?.messages, sendMessage]);
81
78
  const handleRetryMessage = useCallback((messageId) => {
@@ -99,7 +96,7 @@ function ChatThread({ conversationId }) {
99
96
  ]);
100
97
  const messages = currentConversation?.messages || [];
101
98
  const showAuthor = useMemo(() => {
102
- return new Set(messages.filter((m) => m.author).map((m) => m.author.username)).size > 1 || currentConversation?.isOwner === false;
99
+ return new Set(messages.flatMap((m) => m.author ? [m.author.username] : [])).size > 1 || currentConversation?.isOwner === false;
103
100
  }, [messages, currentConversation?.isOwner]);
104
101
  if (isLoadingConversation && !currentConversation) return /* @__PURE__ */ jsxs("div", {
105
102
  className: "flex flex-col h-full bg-(--theme-panel-bg)",
@@ -33,8 +33,9 @@ function groupModelsByProvider(models) {
33
33
  const groups = /* @__PURE__ */ new Map();
34
34
  for (const model of models) {
35
35
  const key = model.id.includes("/") ? getProviderKeyFromModelId(model.id) : model.provider ?? model.owned_by;
36
- if (!groups.has(key)) groups.set(key, []);
37
- groups.get(key).push(model);
36
+ const group = groups.get(key);
37
+ if (group) group.push(model);
38
+ else groups.set(key, [model]);
38
39
  }
39
40
  return groups;
40
41
  }
@@ -7,6 +7,9 @@ import Dropdown from "../ui/Dropdown.js";
7
7
  import { useCallback, useEffect, useState, useTransition } from "react";
8
8
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
9
9
  //#region src/ai/components/ShareDialog.tsx
10
+ function isTeamPermission(p) {
11
+ return !!p.team && !p.entireOrganization;
12
+ }
10
13
  function ShareDialog({ conversationId, isOpen, onClose }) {
11
14
  const { strings } = useChatConfig();
12
15
  const t = strings.shareConversation;
@@ -66,8 +69,8 @@ function ShareDialog({ conversationId, isOpen, onClose }) {
66
69
  });
67
70
  else if (!wantOrgPerm && hadOrgPerm?.id) await sharing.removePermission(conversationId, hadOrgPerm.id);
68
71
  else if (wantOrgPerm && hadOrgPerm?.id && (originalOrgCollaborateAccess !== orgCollaborateAccess || hadOrgPerm.permission === "collaborate" !== orgCollaborateAccess)) await sharing.updatePermission(conversationId, hadOrgPerm.id, orgCollaborateAccess ? "collaborate" : "view");
69
- const originalTeamPerms = originalPermissions.filter((p) => p.team && !p.entireOrganization);
70
- const currentTeamPerms = permissions.filter((p) => p.team && !p.entireOrganization);
72
+ const originalTeamPerms = originalPermissions.filter(isTeamPermission);
73
+ const currentTeamPerms = permissions.filter(isTeamPermission);
71
74
  for (const perm of currentTeamPerms) {
72
75
  const original = originalTeamPerms.find((p) => p.team === perm.team);
73
76
  if (!original) await sharing.addPermission(conversationId, {
@@ -117,7 +120,7 @@ function ShareDialog({ conversationId, isOpen, onClose }) {
117
120
  } : p));
118
121
  }, []);
119
122
  if (!sharing) return null;
120
- const teamPermissions = permissions.filter((p) => p.team && !p.entireOrganization);
123
+ const teamPermissions = permissions.filter(isTeamPermission);
121
124
  const availableTeams = teams.filter((team) => !permissions.some((p) => p.team === team.id));
122
125
  return /* @__PURE__ */ jsx(ConfirmModal, {
123
126
  open: isOpen,
@@ -2,7 +2,6 @@ import { type KeyboardEvent } from 'react';
2
2
  export interface SlashCommandOption {
3
3
  name: string;
4
4
  description?: string | undefined;
5
- usage?: string | undefined;
6
5
  }
7
6
  export interface SlashMenuConfig {
8
7
  commands: SlashCommandOption[];
@@ -1,6 +1,5 @@
1
1
  /** The agent status words: one is picked per turn and held until it ends. */
2
2
  export declare const activityWords: readonly ['Thinking', 'Cogitating', 'Pondering', 'Percolating', 'Ruminating', 'Noodling', 'Conjuring', 'Simmering', 'Marinating', 'Tinkering', 'Scheming', 'Brewing', 'Wrangling', 'Finagling', 'Spelunking', 'Puttering', 'Composing', 'Synthesizing', 'Deliberating', 'Mulling', 'Contemplating', 'Vibing', 'Whittling', 'Concocting', 'Formulating', 'Hatching', 'Herding', 'Ideating', 'Manifesting', 'Moseying', 'Musing', 'Percussing', 'Reticulating', 'Schlepping', 'Stewing', 'Transmuting', 'Wibbling', 'Crunching', 'Forging', 'Cooking'];
3
- export declare function randomActivityWord(): string;
4
3
  /** Elapsed run time the way an agent status line prints it: 45s, 2m13s,
5
4
  * 1h2m3s. */
6
5
  export declare function formatElapsed(seconds: number): string;
@@ -49,7 +49,7 @@ var activityWords = [
49
49
  "Cooking"
50
50
  ];
51
51
  function randomActivityWord() {
52
- return activityWords[Math.floor(Math.random() * activityWords.length)];
52
+ return activityWords[Math.floor(Math.random() * activityWords.length)] ?? activityWords[0];
53
53
  }
54
54
  /** Elapsed run time the way an agent status line prints it: 45s, 2m13s,
55
55
  * 1h2m3s. */
@@ -148,4 +148,4 @@ function ActivityLine({ startedAt, inputTokens, outputTokens, onStop, stopping =
148
148
  });
149
149
  }
150
150
  //#endregion
151
- export { activityWords, compactCount, ActivityLine as default, formatElapsed, randomActivityWord };
151
+ export { activityWords, compactCount, ActivityLine as default, formatElapsed };
@@ -1,31 +1,9 @@
1
1
  import { type PartRendererProps } from '../../core/config.js';
2
- import type { ApprovalPart, MessagePart, NoticePart, ReasoningPart, SubagentPart, TodoSnapshotPart, ToolCallPart } from '../../types.js';
3
- export declare function ToolCallBlock({ part }: {
4
- part: ToolCallPart;
5
- }): import("react").JSX.Element;
6
- /** Drawn the way the TUI draws a delegated Task: a tool-shaped header
7
- * `Task(description)`, and under it the latest action while running or
8
- * `Done (summary)` once finished. Expanding shows the whole activity feed. */
9
- export declare function SubagentCard({ part }: {
10
- part: SubagentPart;
11
- }): import("react").JSX.Element;
2
+ import type { ApprovalPart, MessagePart, ReasoningPart, ToolCallPart } from '../../types.js';
12
3
  export declare function ApprovalBlock({ part, onApprovalAnswer, }: {
13
4
  part: ApprovalPart;
14
5
  onApprovalAnswer?: PartRendererProps['onApprovalAnswer'];
15
6
  }): import("react").JSX.Element;
16
- export declare function TodoSnapshotBlock({ part }: {
17
- part: TodoSnapshotPart;
18
- }): import("react").JSX.Element;
19
- export declare function NoticeBlock({ part }: {
20
- part: NoticePart;
21
- }): import("react").JSX.Element;
22
- /** The TUI's thought: the body shows while it streams, then folds to its
23
- * label and opens on request. The composer's activity line carries the
24
- * live signal, so nothing here animates. */
25
- export declare function ReasoningBlock({ part, isStreaming, }: {
26
- part: ReasoningPart;
27
- isStreaming?: boolean;
28
- }): import("react").JSX.Element;
29
7
  type RunMember = {
30
8
  part: ToolCallPart | ReasoningPart;
31
9
  index: number;
@@ -42,9 +20,6 @@ type RenderItem = {
42
20
  kind: 'rollup';
43
21
  members: RunMember[];
44
22
  };
45
- /** Two thoughts with nothing visible between them read as one, not as a
46
- * stack of identical lines. */
47
- export declare function mergeReasoning(parts: ReasoningPart[]): ReasoningPart;
48
23
  export declare function buildRenderItems(parts: MessagePart[]): RenderItem[];
49
24
  export default function AgentMessageParts({ message, isStreaming, onApprovalAnswer, }: {
50
25
  message: PartRendererProps['message'];
@@ -632,10 +632,9 @@ function buildRenderItems(parts) {
632
632
  emitThoughts(thoughts);
633
633
  };
634
634
  const flush = () => {
635
- let start = 0;
636
- while (start < run.length && isReasoning(run[start])) start++;
637
- let end = run.length;
638
- while (end > start && isReasoning(run[end - 1])) end--;
635
+ const firstCore = run.findIndex((m) => !isReasoning(m));
636
+ const start = firstCore === -1 ? run.length : firstCore;
637
+ const end = firstCore === -1 ? run.length : run.findLastIndex((m) => !isReasoning(m)) + 1;
639
638
  emitThoughts(run.slice(0, start));
640
639
  const core = run.slice(start, end);
641
640
  if (core.filter((m) => !isReasoning(m)).length >= 2) items.push({
@@ -718,4 +717,4 @@ function AgentMessageParts({ message, isStreaming, onApprovalAnswer }) {
718
717
  });
719
718
  }
720
719
  //#endregion
721
- export { ApprovalBlock, NoticeBlock, ReasoningBlock, SubagentCard, TodoSnapshotBlock, ToolCallBlock, buildRenderItems, AgentMessageParts as default, mergeReasoning };
720
+ export { ApprovalBlock, buildRenderItems, AgentMessageParts as default };
@@ -1,6 +1,6 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import type { ChatAdapter } from '../adapter/types.js';
3
- import type { ChatModel, ChatNavigation, ChatNotify, ChatUser, Conversation, ConversationSummary, ChatMessage as Message, MessagePart, ProviderInfo, UnreachableSession } from '../types.js';
3
+ import type { AttachmentMeta, ChatModel, ChatNavigation, ChatNotify, ChatUser, Conversation, ConversationSummary, ChatMessage as Message, MessagePart, ProviderInfo, UnreachableSession } from '../types.js';
4
4
  import { type ChatUIConfig } from './config.js';
5
5
  interface ChatContextValue {
6
6
  adapter: ChatAdapter;
@@ -36,11 +36,7 @@ interface ChatContextValue {
36
36
  refreshModels: () => Promise<void>;
37
37
  createConversation: (title?: string) => Promise<string | null>;
38
38
  deleteConversation: (id: string) => Promise<boolean>;
39
- sendMessage: (content: string, attachmentIds?: string[], parentId?: string, attachmentMeta?: Record<string, {
40
- filename: string;
41
- contentType: string;
42
- size: number;
43
- }>) => Promise<boolean>;
39
+ sendMessage: (content: string, attachmentIds?: string[], parentId?: string, attachmentMeta?: AttachmentMeta) => Promise<boolean>;
44
40
  retryMessage: (messageId: string) => Promise<boolean>;
45
41
  setSelectedProvider: (providerId: string | null) => void;
46
42
  setSelectedAllocation: (allocation: string | null) => void;