@parallelworks/ui 0.20.0 → 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 (154) hide show
  1. package/dist/ai/adapter/openai/createOpenAIStream.js +3 -2
  2. package/dist/ai/agent/diff.d.ts +0 -1
  3. package/dist/ai/agent/diff.js +3 -2
  4. package/dist/ai/agent/toolfmt.js +2 -3
  5. package/dist/ai/ai.css +5 -0
  6. package/dist/ai/components/AllocationSelector.js +3 -2
  7. package/dist/ai/components/AttachmentManager.js +1 -1
  8. package/dist/ai/components/BranchNavigator.js +4 -2
  9. package/dist/ai/components/ChatInput.d.ts +2 -5
  10. package/dist/ai/components/ChatInput.js +10 -7
  11. package/dist/ai/components/ChatMessage.js +7 -7
  12. package/dist/ai/components/ChatSidebar.d.ts +0 -1
  13. package/dist/ai/components/ChatSidebar.js +4 -6
  14. package/dist/ai/components/ChatThread.js +5 -8
  15. package/dist/ai/components/ModelSelector.js +3 -2
  16. package/dist/ai/components/ShareDialog.js +6 -3
  17. package/dist/ai/components/SlashMenu.d.ts +0 -1
  18. package/dist/ai/components/agent/ActivityLine.d.ts +0 -1
  19. package/dist/ai/components/agent/ActivityLine.js +2 -2
  20. package/dist/ai/components/agent/AgentMessageParts.d.ts +1 -26
  21. package/dist/ai/components/agent/AgentMessageParts.js +4 -5
  22. package/dist/ai/core/ChatProvider.d.ts +2 -6
  23. package/dist/ai/core/ChatProvider.js +42 -67
  24. package/dist/ai/core/KeyboardShortcutsProvider.js +1 -1
  25. package/dist/ai/core/chatReducer.d.ts +0 -6
  26. package/dist/ai/core/chatReducer.js +1 -15
  27. package/dist/ai/core/useKeyboardShortcuts.d.ts +3 -1
  28. package/dist/ai/core/useKeyboardShortcuts.js +9 -11
  29. package/dist/ai/core/useSSEStream.d.ts +0 -1
  30. package/dist/ai/core/useSSEStream.js +5 -17
  31. package/dist/ai/strings.d.ts +0 -1
  32. package/dist/ai/strings.js +0 -1
  33. package/dist/ai/types.d.ts +1 -1
  34. package/dist/ai/ui/Avatar.d.ts +1 -2
  35. package/dist/ai/ui/Avatar.js +7 -18
  36. package/dist/ai/ui/Markdown.d.ts +1 -3
  37. package/dist/ai/ui/Markdown.js +1 -2
  38. package/dist/ai/ui/sidebar.d.ts +0 -1
  39. package/dist/ai/ui/sidebar.js +3 -1
  40. package/dist/ai/utils.js +1 -1
  41. package/dist/components/Avatar.js +4 -2
  42. package/dist/components/BareModal.js +1 -5
  43. package/dist/components/Breadcrumbs.js +21 -14
  44. package/dist/components/CollapsiblePanel.d.ts +1 -3
  45. package/dist/components/CollapsiblePanel.js +5 -5
  46. package/dist/components/Dropdown.d.ts +0 -4
  47. package/dist/components/Dropdown.js +5 -6
  48. package/dist/components/Listbox.js +14 -18
  49. package/dist/components/MultiSelect.js +5 -8
  50. package/dist/components/Pagination.js +1 -1
  51. package/dist/components/Provider.d.ts +0 -8
  52. package/dist/components/SwitchToggle.js +3 -5
  53. package/dist/components/Table.d.ts +1 -3
  54. package/dist/components/dropdownUtils.js +5 -6
  55. package/dist/components/relativeTime.d.ts +0 -7
  56. package/dist/components/relativeTime.js +1 -1
  57. package/dist/duration.js +1 -1
  58. package/dist/editor/Editor.d.ts +1 -1
  59. package/dist/editor/Editor.js +1 -1
  60. package/dist/editor/EditorField.d.ts +0 -1
  61. package/dist/editor/EditorField.js +8 -8
  62. package/dist/editor/FormCodePanel.js +14 -20
  63. package/dist/editor/yamlLanguage.js +2 -2
  64. package/dist/file-explorer/FileExplorer.js +56 -59
  65. package/dist/file-explorer/FileExplorerContext.d.ts +1 -2
  66. package/dist/file-explorer/FileExplorerContext.js +92 -125
  67. package/dist/file-explorer/FilePreview/Inline.d.ts +1 -2
  68. package/dist/file-explorer/FilePreview/Inline.js +2 -2
  69. package/dist/file-explorer/FilePreview/Viewer.js +0 -4
  70. package/dist/file-explorer/FilePreview/index.d.ts +1 -2
  71. package/dist/file-explorer/FilePreview/index.js +3 -3
  72. package/dist/file-explorer/FilePreview/parse.d.ts +2 -1
  73. package/dist/file-explorer/FilePreview/useObjectBlobUrl.d.ts +5 -4
  74. package/dist/file-explorer/FilePreview/useObjectBlobUrl.js +23 -36
  75. package/dist/file-explorer/FilePreview/useObjectText.d.ts +1 -2
  76. package/dist/file-explorer/FilePreview/useObjectText.js +5 -7
  77. package/dist/file-explorer/FileRow.js +1 -1
  78. package/dist/file-explorer/FileRowsWindow.d.ts +5 -12
  79. package/dist/file-explorer/FileTable.d.ts +2 -6
  80. package/dist/file-explorer/LoadMoreRow.d.ts +8 -0
  81. package/dist/file-explorer/TreeView.d.ts +0 -1
  82. package/dist/file-explorer/TreeView.js +6 -6
  83. package/dist/file-explorer/helpers.js +2 -4
  84. package/dist/file-explorer/lib/fileExplorer.d.ts +0 -2
  85. package/dist/file-explorer/lib/types.d.ts +0 -1
  86. package/dist/file-explorer/lib/utils.js +15 -20
  87. package/dist/file-explorer/toasts.js +36 -34
  88. package/dist/form/Fields/BooleanField.js +5 -4
  89. package/dist/form/Fields/CheckboxGroupField.js +5 -4
  90. package/dist/form/Fields/ColorField.js +5 -4
  91. package/dist/form/Fields/DropdownField.d.ts +0 -1
  92. package/dist/form/Fields/DropdownField.js +10 -13
  93. package/dist/form/Fields/DurationField.js +2 -2
  94. package/dist/form/Fields/LabelField.js +5 -5
  95. package/dist/form/Fields/MultiDropdownField.d.ts +1 -1
  96. package/dist/form/Fields/MultiDropdownField.js +8 -11
  97. package/dist/form/Fields/NumberField.js +6 -5
  98. package/dist/form/Fields/ObjectField.d.ts +1 -2
  99. package/dist/form/Fields/ObjectField.js +22 -21
  100. package/dist/form/Fields/RadioField.js +4 -3
  101. package/dist/form/Fields/RangeField.js +7 -6
  102. package/dist/form/Fields/StringField.js +2 -1
  103. package/dist/form/Fields/TextAreaField.js +2 -1
  104. package/dist/form/Form.d.ts +7 -8
  105. package/dist/form/Form.js +52 -78
  106. package/dist/form/SpecialTypes/DropdownWrapper.d.ts +0 -2
  107. package/dist/form/SpecialTypes/DropdownWrapper.js +1 -3
  108. package/dist/form/SpecialTypes/FormikCustomDropdown.d.ts +1 -3
  109. package/dist/form/SpecialTypes/FormikCustomDropdown.js +4 -7
  110. package/dist/form/SpecialTypes/FormikCustomInput.js +13 -11
  111. package/dist/form/Wizard/WizardContainer.js +8 -25
  112. package/dist/form/Wizard/WizardNavigation.d.ts +1 -1
  113. package/dist/form/Wizard/WizardNavigation.js +5 -6
  114. package/dist/form/Wizard/WizardStepContent.d.ts +1 -1
  115. package/dist/form/Wizard/WizardStepContent.js +2 -2
  116. package/dist/form/Wizard/WizardStepIndicator.js +7 -17
  117. package/dist/form/Wizard/types.d.ts +4 -46
  118. package/dist/form/Wizard/useWizardState.d.ts +2 -10
  119. package/dist/form/Wizard/useWizardState.js +42 -92
  120. package/dist/form/Wizard/utils.js +6 -11
  121. package/dist/form/lib.js +28 -54
  122. package/dist/form/types/fieldTypes.d.ts +4 -5
  123. package/dist/form/utils/dependentOptions.d.ts +6 -0
  124. package/dist/form/utils/dependentOptions.js +21 -0
  125. package/dist/form/utils/getParentValue.d.ts +2 -1
  126. package/dist/form/utils/getParentValue.js +10 -15
  127. package/dist/form/utils/getValueUsingPath.js +1 -1
  128. package/dist/graph/AnnotationBanner.d.ts +1 -1
  129. package/dist/graph/AnnotationBanner.js +1 -1
  130. package/dist/graph/Collapse.d.ts +3 -3
  131. package/dist/graph/Collapse.js +3 -8
  132. package/dist/graph/DependencyGraph.d.ts +5 -4
  133. package/dist/graph/DependencyGraph.js +172 -222
  134. package/dist/graph/JobSummary.d.ts +10 -8
  135. package/dist/graph/JobSummary.js +13 -13
  136. package/dist/graph/MatrixGroup.d.ts +5 -17
  137. package/dist/graph/MatrixGroup.js +11 -22
  138. package/dist/graph/TreeView.d.ts +1 -3
  139. package/dist/graph/TreeView.js +104 -491
  140. package/dist/graph/types.d.ts +3 -2
  141. package/dist/graph/util.d.ts +5 -0
  142. package/dist/graph/util.js +18 -0
  143. package/dist/icons.d.ts +2 -2
  144. package/dist/icons.js +2 -2
  145. package/dist/index.d.ts +1 -1
  146. package/dist/list/ListViewControls.js +17 -35
  147. package/dist/list/RowContextMenu.js +7 -7
  148. package/dist/logviewer/LogViewer.js +7 -9
  149. package/dist/logviewer/segments.js +2 -2
  150. package/dist/styles/theme.css +1 -0
  151. package/dist/styles.css +1 -1
  152. package/dist/theme/index.js +5 -5
  153. package/package.json +28 -30
  154. package/dist/ai/ui/buttons.d.ts +0 -1
@@ -43,6 +43,29 @@ function generateTitleFromMessage(content) {
43
43
  function errorText(error) {
44
44
  return error instanceof Error ? error.message : String(error);
45
45
  }
46
+ function attachmentRefs(ids, meta) {
47
+ if (!ids?.length) return;
48
+ const uploadedAt = DateTime.now().toISO();
49
+ return ids.map((id) => ({
50
+ id,
51
+ filename: meta?.[id]?.filename ?? "",
52
+ contentType: meta?.[id]?.contentType ?? "",
53
+ size: meta?.[id]?.size ?? 0,
54
+ uploadedAt
55
+ }));
56
+ }
57
+ function heldSendArgs(held) {
58
+ const last = held.at(-1);
59
+ if (!last) return null;
60
+ const preceding = held.slice(0, -1).map(({ id: _id, ...rest }) => rest);
61
+ return [
62
+ last.content,
63
+ last.attachmentIds.length > 0 ? last.attachmentIds : void 0,
64
+ void 0,
65
+ Object.keys(last.attachmentMeta).length > 0 ? last.attachmentMeta : void 0,
66
+ preceding.length > 0 ? preceding : void 0
67
+ ];
68
+ }
46
69
  var ChatContext = createContext(void 0);
47
70
  function useChat() {
48
71
  const context = useContext(ChatContext);
@@ -251,7 +274,7 @@ function ChatProvider({ adapter, currentUser, navigation, notify, activeConversa
251
274
  state.currentConversation,
252
275
  navigation
253
276
  ]);
254
- const sendMessage = useCallback(async (content, attachmentIds, parentId, attachmentMeta, _precedingMessages) => {
277
+ const sendMessage = useCallback(async (content, attachmentIds, parentId, attachmentMeta, precedingMessages) => {
255
278
  if (isSendingRef.current) {
256
279
  const convId = currentConversationIdRef.current;
257
280
  if (!convId) return false;
@@ -260,31 +283,6 @@ function ChatProvider({ adapter, currentUser, navigation, notify, activeConversa
260
283
  notify.error(`You can queue up to ${MAX_QUEUED_MESSAGES} messages while waiting for a response`);
261
284
  return false;
262
285
  }
263
- for (const pm of _precedingMessages ?? []) {
264
- const pmId = crypto.randomUUID();
265
- pendingQueueRef.current.push({
266
- id: pmId,
267
- ...pm
268
- });
269
- dispatch({
270
- type: "ADD_QUEUED_MESSAGE",
271
- message: {
272
- id: pmId,
273
- role: "user",
274
- content: pm.content,
275
- timestamp: DateTime.now().toISO(),
276
- author: currentUser,
277
- attachments: pm.attachmentIds.length ? pm.attachmentIds.map((id) => ({
278
- id,
279
- filename: pm.attachmentMeta[id]?.filename ?? "",
280
- contentType: pm.attachmentMeta[id]?.contentType ?? "",
281
- size: pm.attachmentMeta[id]?.size ?? 0,
282
- uploadedAt: DateTime.now().toISO()
283
- })) : void 0
284
- },
285
- conversationId: convId
286
- });
287
- }
288
286
  const queuedId = crypto.randomUUID();
289
287
  pendingQueueRef.current.push({
290
288
  id: queuedId,
@@ -300,19 +298,14 @@ function ChatProvider({ adapter, currentUser, navigation, notify, activeConversa
300
298
  content,
301
299
  timestamp: DateTime.now().toISO(),
302
300
  author: currentUser,
303
- attachments: attachmentIds?.map((id) => ({
304
- id,
305
- filename: attachmentMeta?.[id]?.filename ?? "",
306
- contentType: attachmentMeta?.[id]?.contentType ?? "",
307
- size: attachmentMeta?.[id]?.size ?? 0,
308
- uploadedAt: DateTime.now().toISO()
309
- }))
301
+ attachments: attachmentRefs(attachmentIds, attachmentMeta)
310
302
  },
311
303
  conversationId: convId
312
304
  });
313
305
  return true;
314
306
  }
315
- if (!isSelectedModelAvailable) {
307
+ const modelString = state.selectedProvider;
308
+ if (!isSelectedModelAvailable || !modelString) {
316
309
  notify.error("Please select a provider first");
317
310
  return false;
318
311
  }
@@ -321,7 +314,7 @@ function ChatProvider({ adapter, currentUser, navigation, notify, activeConversa
321
314
  const held = pendingQueueRef.current;
322
315
  pendingQueueRef.current = [];
323
316
  dispatch({ type: "CLEAR_QUEUED_MESSAGES" });
324
- _precedingMessages = [...held.map(({ id: _id, ...rest }) => rest), ..._precedingMessages ?? []];
317
+ precedingMessages = [...held.map(({ id: _id, ...rest }) => rest), ...precedingMessages ?? []];
325
318
  }
326
319
  let conversationId = currentConversationIdRef.current;
327
320
  let isNewConversation = false;
@@ -357,20 +350,14 @@ function ChatProvider({ adapter, currentUser, navigation, notify, activeConversa
357
350
  startStreaming(targetConversationId);
358
351
  let lastBranchId = parentId || state.currentConversation?.activeBranchId || null;
359
352
  const precedingApiMessages = [];
360
- if (_precedingMessages?.length) for (const pm of _precedingMessages) {
353
+ if (precedingMessages?.length) for (const pm of precedingMessages) {
361
354
  const pmId = crypto.randomUUID();
362
355
  const pmMessage = {
363
356
  id: pmId,
364
357
  parentId: lastBranchId,
365
358
  role: "user",
366
359
  content: pm.content,
367
- attachments: pm.attachmentIds?.length ? pm.attachmentIds.map((id) => ({
368
- id,
369
- filename: pm.attachmentMeta[id]?.filename ?? "",
370
- contentType: pm.attachmentMeta[id]?.contentType ?? "",
371
- size: pm.attachmentMeta[id]?.size ?? 0,
372
- uploadedAt: DateTime.now().toISO()
373
- })) : void 0,
360
+ attachments: attachmentRefs(pm.attachmentIds, pm.attachmentMeta),
374
361
  timestamp: DateTime.now().toISO(),
375
362
  author: currentUser
376
363
  };
@@ -382,7 +369,7 @@ function ChatProvider({ adapter, currentUser, navigation, notify, activeConversa
382
369
  precedingApiMessages.push({
383
370
  role: "user",
384
371
  content: pm.content,
385
- attachment_ids: pm.attachmentIds?.length ? pm.attachmentIds : void 0
372
+ attachment_ids: pm.attachmentIds.length ? pm.attachmentIds : void 0
386
373
  });
387
374
  lastBranchId = pmId;
388
375
  }
@@ -392,13 +379,7 @@ function ChatProvider({ adapter, currentUser, navigation, notify, activeConversa
392
379
  parentId: lastBranchId,
393
380
  role: "user",
394
381
  content,
395
- attachments: attachmentIds?.map((id) => ({
396
- id,
397
- filename: attachmentMeta?.[id]?.filename ?? "",
398
- contentType: attachmentMeta?.[id]?.contentType ?? "",
399
- size: attachmentMeta?.[id]?.size ?? 0,
400
- uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
401
- })),
382
+ attachments: attachmentRefs(attachmentIds, attachmentMeta),
402
383
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
403
384
  author: currentUser
404
385
  };
@@ -455,7 +436,6 @@ function ChatProvider({ adapter, currentUser, navigation, notify, activeConversa
455
436
  let sendSucceeded = false;
456
437
  let wasAborted = false;
457
438
  try {
458
- const modelString = state.selectedProvider;
459
439
  const useResponses = state.models.find((m) => m.id === modelString)?.supports_responses === true;
460
440
  let thinkingStart = null;
461
441
  const markStreamStart = () => {
@@ -557,17 +537,14 @@ function ChatProvider({ adapter, currentUser, navigation, notify, activeConversa
557
537
  });
558
538
  if (wasAborted) isSendingRef.current = false;
559
539
  else if (sendSucceeded) {
560
- const queued = pendingQueueRef.current;
540
+ const next = heldSendArgs(pendingQueueRef.current);
561
541
  pendingQueueRef.current = [];
562
542
  dispatch({ type: "CLEAR_QUEUED_MESSAGES" });
563
- if (queued.length > 0) {
564
- const lastQueued = queued[queued.length - 1];
565
- const preceding = queued.slice(0, -1);
566
- setTimeout(() => {
567
- isSendingRef.current = false;
568
- sendMessageRef.current?.(lastQueued.content, lastQueued.attachmentIds.length > 0 ? lastQueued.attachmentIds : void 0, void 0, Object.keys(lastQueued.attachmentMeta).length > 0 ? lastQueued.attachmentMeta : void 0, preceding.length > 0 ? preceding : void 0);
569
- }, 0);
570
- } else isSendingRef.current = false;
543
+ if (next) setTimeout(() => {
544
+ isSendingRef.current = false;
545
+ sendMessageRef.current?.(...next);
546
+ }, 0);
547
+ else isSendingRef.current = false;
571
548
  } else isSendingRef.current = false;
572
549
  }
573
550
  }, [
@@ -688,7 +665,7 @@ function ChatProvider({ adapter, currentUser, navigation, notify, activeConversa
688
665
  });
689
666
  dispatch({ type: "CLEAR_STREAMING_CONTENT" });
690
667
  if (!conversationId || !hasContent) return;
691
- const lastUserMessage = [...state.currentConversation?.messages || []].reverse().find((m) => m.role === "user");
668
+ const lastUserMessage = (state.currentConversation?.messages || []).findLast((m) => m.role === "user");
692
669
  const messageId = stoppedMessageId || crypto.randomUUID();
693
670
  dispatch({
694
671
  type: "ADD_MESSAGE",
@@ -743,13 +720,11 @@ function ChatProvider({ adapter, currentUser, navigation, notify, activeConversa
743
720
  }, []);
744
721
  const flushQueuedMessages = useCallback(() => {
745
722
  if (isSendingRef.current) return;
746
- const held = pendingQueueRef.current;
747
- if (held.length === 0) return;
723
+ const next = heldSendArgs(pendingQueueRef.current);
724
+ if (!next) return;
748
725
  pendingQueueRef.current = [];
749
726
  dispatch({ type: "CLEAR_QUEUED_MESSAGES" });
750
- const last = held[held.length - 1];
751
- const preceding = held.slice(0, -1).map(({ id: _id, ...rest }) => rest);
752
- sendMessageRef.current?.(last.content, last.attachmentIds.length > 0 ? last.attachmentIds : void 0, void 0, Object.keys(last.attachmentMeta).length > 0 ? last.attachmentMeta : void 0, preceding.length > 0 ? preceding : void 0);
727
+ sendMessageRef.current?.(...next);
753
728
  }, []);
754
729
  const clearCurrentConversation = useCallback(() => {
755
730
  currentConversationIdRef.current = null;
@@ -2,7 +2,7 @@ import { useKeyboardShortcuts } from "./useKeyboardShortcuts.js";
2
2
  import { Fragment, jsx } from "react/jsx-runtime";
3
3
  //#region src/ai/core/KeyboardShortcutsProvider.tsx
4
4
  function KeyboardShortcutsProvider({ children }) {
5
- useKeyboardShortcuts({ enabled: true });
5
+ useKeyboardShortcuts();
6
6
  return /* @__PURE__ */ jsx(Fragment, { children });
7
7
  }
8
8
  //#endregion
@@ -28,9 +28,6 @@ export type ChatAction = {
28
28
  } | {
29
29
  type: 'SET_CURRENT_CONVERSATION';
30
30
  conversation: Conversation | null;
31
- } | {
32
- type: 'UPDATE_CURRENT_CONVERSATION_TITLE';
33
- title: string;
34
31
  } | {
35
32
  type: 'SET_PROVIDERS';
36
33
  providers: ProviderInfo[];
@@ -57,9 +54,6 @@ export type ChatAction = {
57
54
  type: 'SET_STREAMING';
58
55
  isStreaming: boolean;
59
56
  conversationId?: string;
60
- } | {
61
- type: 'SET_THINKING';
62
- isThinking: boolean;
63
57
  } | {
64
58
  type: 'APPEND_STREAMING_CONTENT';
65
59
  content: string;
@@ -36,7 +36,7 @@ function chatReducer(state, action) {
36
36
  const messages = action.conversation.messages || [];
37
37
  for (let i = messages.length - 1; i >= 0; i--) {
38
38
  const msg = messages[i];
39
- if (msg.role === "assistant" && msg.model) {
39
+ if (msg?.role === "assistant" && msg.model) {
40
40
  if (!state.hasLoadedModels || state.models.some((m) => m.id === msg.model)) restoredProvider = msg.model;
41
41
  break;
42
42
  }
@@ -72,15 +72,6 @@ function chatReducer(state, action) {
72
72
  thinkingStartTime: null,
73
73
  queuedMessages: []
74
74
  };
75
- case "UPDATE_CURRENT_CONVERSATION_TITLE":
76
- if (!state.currentConversation) return state;
77
- return {
78
- ...state,
79
- currentConversation: {
80
- ...state.currentConversation,
81
- title: action.title
82
- }
83
- };
84
75
  case "SET_PROVIDERS": return {
85
76
  ...state,
86
77
  providers: action.providers
@@ -119,11 +110,6 @@ function chatReducer(state, action) {
119
110
  ...state,
120
111
  isStreaming: action.isStreaming
121
112
  };
122
- case "SET_THINKING": return {
123
- ...state,
124
- isThinking: action.isThinking,
125
- thinkingStartTime: action.isThinking ? Date.now() : state.thinkingStartTime
126
- };
127
113
  case "APPEND_STREAMING_CONTENT":
128
114
  if (state.currentConversation?.id !== action.conversationId) return state;
129
115
  return {
@@ -1,10 +1,12 @@
1
1
  import type { ChatMessage } from '../types.js';
2
+ export declare function lastAssistantContent(messages: ChatMessage[]): string | null;
3
+ export declare function lastUserMessage(messages: ChatMessage[]): ChatMessage | null;
2
4
  interface UseKeyboardShortcutsOptions {
3
5
  enabled?: boolean;
4
6
  }
5
7
  export declare function useKeyboardShortcuts(options?: UseKeyboardShortcutsOptions): {
6
8
  getLastUserMessage: () => ChatMessage | null;
7
- getLastAssistantMessage: () => string | null | undefined;
9
+ getLastAssistantMessage: () => string | null;
8
10
  handleNewConversation: () => void;
9
11
  handleCopyLastResponse: () => Promise<void>;
10
12
  handleStopGeneration: () => void;
@@ -3,19 +3,17 @@ import { isMac } from "../utils.js";
3
3
  import { FOCUS_SIDEBAR_SEARCH_EVENT } from "./events.js";
4
4
  import { useCallback, useEffect } from "react";
5
5
  //#region src/ai/core/useKeyboardShortcuts.ts
6
+ function lastAssistantContent(messages) {
7
+ return messages.findLast((m) => m.role === "assistant" && m.content)?.content ?? null;
8
+ }
9
+ function lastUserMessage(messages) {
10
+ return messages.findLast((m) => m.role === "user") ?? null;
11
+ }
6
12
  function useKeyboardShortcuts(options = {}) {
7
13
  const { enabled = true } = options;
8
14
  const { currentConversation, isStreaming, stopStreaming, clearCurrentConversation, navigation, notify } = useChat();
9
- const getLastAssistantMessage = useCallback(() => {
10
- const messages = currentConversation?.messages || [];
11
- for (let i = messages.length - 1; i >= 0; i--) if (messages[i].role === "assistant" && messages[i].content) return messages[i].content;
12
- return null;
13
- }, [currentConversation?.messages]);
14
- const getLastUserMessage = useCallback(() => {
15
- const messages = currentConversation?.messages || [];
16
- for (let i = messages.length - 1; i >= 0; i--) if (messages[i].role === "user") return messages[i];
17
- return null;
18
- }, [currentConversation?.messages]);
15
+ const getLastAssistantMessage = useCallback(() => lastAssistantContent(currentConversation?.messages ?? []), [currentConversation?.messages]);
16
+ const getLastUserMessage = useCallback(() => lastUserMessage(currentConversation?.messages ?? []), [currentConversation?.messages]);
19
17
  const handleNewConversation = useCallback(() => {
20
18
  clearCurrentConversation();
21
19
  navigation.toNewChat();
@@ -74,4 +72,4 @@ function useKeyboardShortcuts(options = {}) {
74
72
  };
75
73
  }
76
74
  //#endregion
77
- export { useKeyboardShortcuts };
75
+ export { lastAssistantContent, lastUserMessage, useKeyboardShortcuts };
@@ -15,7 +15,6 @@ interface UseSSEStreamResult {
15
15
  /** Stop the animation loop, flush remaining content */
16
16
  stopAnimationLoop: () => {
17
17
  unrevealed: string;
18
- conversationId: string | null;
19
18
  };
20
19
  /** Reset all animation refs (used when navigating away) */
21
20
  resetAnimationRefs: () => void;
@@ -49,23 +49,6 @@ function useSSEStream(dispatch) {
49
49
  streamingConversationIdRef.current = conversationId;
50
50
  streamActiveRef.current = true;
51
51
  }, []);
52
- const stopAnimationLoop = useCallback(() => {
53
- const conversationId = streamingConversationIdRef.current;
54
- streamActiveRef.current = false;
55
- if (streamRafRef.current !== null) {
56
- cancelAnimationFrame(streamRafRef.current);
57
- streamRafRef.current = null;
58
- }
59
- const unrevealed = targetStreamContentRef.current.slice(revealedLengthRef.current);
60
- targetStreamContentRef.current = "";
61
- revealedLengthRef.current = 0;
62
- lastFrameTimeRef.current = 0;
63
- lastDispatchTimeRef.current = 0;
64
- return {
65
- unrevealed,
66
- conversationId
67
- };
68
- }, []);
69
52
  const resetAnimationRefs = useCallback(() => {
70
53
  streamActiveRef.current = false;
71
54
  if (streamRafRef.current !== null) {
@@ -77,6 +60,11 @@ function useSSEStream(dispatch) {
77
60
  lastFrameTimeRef.current = 0;
78
61
  lastDispatchTimeRef.current = 0;
79
62
  }, []);
63
+ const stopAnimationLoop = useCallback(() => {
64
+ const unrevealed = targetStreamContentRef.current.slice(revealedLengthRef.current);
65
+ resetAnimationRefs();
66
+ return { unrevealed };
67
+ }, [resetAnimationRefs]);
80
68
  useEffect(() => {
81
69
  return () => {
82
70
  if (streamRafRef.current !== null) cancelAnimationFrame(streamRafRef.current);
@@ -71,7 +71,6 @@ export interface ChatStrings {
71
71
  share: string;
72
72
  delete: string;
73
73
  sharedWithYou: string;
74
- moreOptions: string;
75
74
  openSidebar: string;
76
75
  closeSidebar: string;
77
76
  newChat: string;
@@ -78,7 +78,6 @@ var defaultChatStrings = {
78
78
  share: "Share",
79
79
  delete: "Delete",
80
80
  sharedWithYou: "Shared with you",
81
- moreOptions: "More options",
82
81
  openSidebar: "Open sidebar",
83
82
  closeSidebar: "Close sidebar",
84
83
  newChat: "New chat",
@@ -13,6 +13,7 @@ export interface AttachmentRef {
13
13
  conversationId?: string;
14
14
  messageId?: string;
15
15
  }
16
+ export type AttachmentMeta = Record<string, Pick<AttachmentRef, 'filename' | 'contentType' | 'size'>>;
16
17
  export interface FunctionCall {
17
18
  name: string;
18
19
  arguments: string;
@@ -206,7 +207,6 @@ export interface Allocation {
206
207
  export interface ChatNavigation {
207
208
  toConversation(id: string): void;
208
209
  toNewChat(): void;
209
- toAttachments(): void;
210
210
  }
211
211
  export interface ChatNotify {
212
212
  success(message: string): void;
@@ -1,8 +1,7 @@
1
1
  interface AvatarProps {
2
2
  src?: string | undefined;
3
3
  name?: string;
4
- size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl';
5
4
  className?: string;
6
5
  }
7
- export declare function Avatar({ src, name, size, className }: AvatarProps): import("react").JSX.Element;
6
+ export declare function Avatar({ src, name, className }: AvatarProps): import("react").JSX.Element;
8
7
  export {};
@@ -1,33 +1,22 @@
1
+ import { avatarInitials } from "../../components/Avatar.js";
1
2
  import cx from "classnames";
2
- import { useMemo, useState } from "react";
3
+ import { useState } from "react";
3
4
  import { jsx } from "react/jsx-runtime";
4
5
  //#region src/ai/ui/Avatar.tsx
5
- var sizeClasses = {
6
- sm: "w-6 h-6 text-xs",
7
- md: "w-8 h-8 text-sm",
8
- lg: "w-10 h-10 text-base",
9
- xl: "w-12 h-12 text-lg",
10
- "2xl": "w-16 h-16 text-xl"
11
- };
12
- function Avatar({ src, name, size = "md", className }) {
6
+ var SIZE_CLASSES = "w-6 h-6 text-xs";
7
+ function Avatar({ src, name, className }) {
13
8
  const [erroredUrl, setErroredUrl] = useState(null);
14
9
  const errored = erroredUrl !== null && erroredUrl === src;
15
- const initials = useMemo(() => {
16
- if (!name) return "?";
17
- const parts = name.split(/[\s.]+/).filter(Boolean);
18
- if (parts.length >= 2) return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
19
- return parts[0]?.[0]?.toUpperCase() || "?";
20
- }, [name]);
21
10
  return /* @__PURE__ */ jsx("div", {
22
11
  className: cx("relative inline-flex", className),
23
12
  children: !src || errored ? /* @__PURE__ */ jsx("div", {
24
- className: cx("rounded-full flex items-center justify-center font-medium", "bg-(--theme-element,#3b82f6) text-(--theme-element-text,#ffffff)", sizeClasses[size]),
25
- children: initials
13
+ className: cx("rounded-full flex items-center justify-center font-medium", "bg-(--theme-element,#3b82f6) text-(--theme-element-text,#ffffff)", SIZE_CLASSES),
14
+ children: avatarInitials(name)
26
15
  }) : /* @__PURE__ */ jsx("img", {
27
16
  src,
28
17
  alt: name || "User avatar",
29
18
  onError: () => setErroredUrl(src),
30
- className: cx("rounded-full object-cover", sizeClasses[size])
19
+ className: cx("rounded-full object-cover", SIZE_CLASSES)
31
20
  })
32
21
  });
33
22
  }
@@ -1,8 +1,6 @@
1
- import { type ControlsConfig } from 'streamdown';
2
1
  interface MarkdownProps {
3
2
  children: string;
4
3
  isStreaming?: boolean;
5
- controls?: ControlsConfig;
6
4
  }
7
- export default function Markdown({ children, isStreaming, controls }: MarkdownProps): import("react").JSX.Element;
5
+ export default function Markdown({ children, isStreaming }: MarkdownProps): import("react").JSX.Element;
8
6
  export {};
@@ -37,7 +37,7 @@ function loadMermaid() {
37
37
  }
38
38
  var shikiTheme = ["github-light", "github-dark"];
39
39
  var rehypePlugins = [...Object.values(defaultRehypePlugins), rehypeCodeBreaks];
40
- function Markdown({ children, isStreaming, controls }) {
40
+ function Markdown({ children, isStreaming }) {
41
41
  const { markdownComponents } = useChatConfig();
42
42
  const needsMermaid = !!children && children.includes("```mermaid");
43
43
  const [, setMermaidReady] = useState(() => !!pluginsWithMermaid);
@@ -58,7 +58,6 @@ function Markdown({ children, isStreaming, controls }) {
58
58
  mode: isStreaming ? "streaming" : "static",
59
59
  ...isStreaming === void 0 ? {} : { isAnimating: isStreaming },
60
60
  ...isStreaming ? { caret: "block" } : {},
61
- ...controls === void 0 ? {} : { controls },
62
61
  plugins,
63
62
  rehypePlugins,
64
63
  shikiTheme,
@@ -1,6 +1,5 @@
1
1
  import { type CSSProperties, type ReactNode } from 'react';
2
2
  import { type OpenMenu, type RowMenuItem } from '../../list/index.js';
3
- export declare const SIDEBAR_RAIL_WIDTH_PX = 48;
4
3
  export declare const SIDEBAR_DEFAULT_WIDTH_PX = 256;
5
4
  /** The column a conversation list lives in: it animates between its width
6
5
  * and a rail, unless a drag is setting the width right now. */
@@ -5,11 +5,13 @@ import { MoreButton, rowContextMenuProps, useRowMenuActive } from "../../list/Ro
5
5
  import cx from "classnames";
6
6
  import { useState } from "react";
7
7
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
8
+ //#region src/ai/ui/sidebar.tsx
9
+ var SIDEBAR_RAIL_WIDTH_PX = 48;
8
10
  var HIGHLIGHT_SELECTED = "bg-[color-mix(in_oklab,var(--theme-panel)_8%,transparent)]";
9
11
  var HIGHLIGHT_HOVER = "hover:bg-[color-mix(in_oklab,var(--theme-panel)_5%,transparent)]";
10
12
  /** The column a conversation list lives in: it animates between its width
11
13
  * and a rail, unless a drag is setting the width right now. */
12
- function SidebarPanel({ collapsed, width = 256, railWidth = 48, resizing = false, label, className, style, children }) {
14
+ function SidebarPanel({ collapsed, width = 256, railWidth = SIDEBAR_RAIL_WIDTH_PX, resizing = false, label, className, style, children }) {
13
15
  return /* @__PURE__ */ jsx("aside", {
14
16
  "aria-label": label,
15
17
  className: cx("relative h-full flex shrink-0 flex-col bg-(--theme-muted-panel-bg) overflow-hidden", !resizing && "transition-[width] duration-300 ease-in-out", className),
package/dist/ai/utils.js CHANGED
@@ -10,7 +10,7 @@ function formatFileSize(bytes) {
10
10
  "TB"
11
11
  ];
12
12
  const i = Math.floor(Math.log(bytes) / Math.log(k));
13
- return Number.parseFloat((bytes / k ** i).toFixed(2)) + " " + sizes[i];
13
+ return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`;
14
14
  }
15
15
  function isMac() {
16
16
  return navigator.platform.toUpperCase().includes("MAC");
@@ -19,8 +19,10 @@ var STATUS_CLASSES = {
19
19
  /** Up to two initials: the first and last word of a name split on spaces and dots. */
20
20
  function avatarInitials(name) {
21
21
  const parts = (name ?? "").split(/[\s.]+/).filter(Boolean);
22
- if (parts.length >= 2) return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
23
- return parts[0]?.[0]?.toUpperCase() || "?";
22
+ const first = parts[0]?.charAt(0);
23
+ const last = parts.at(-1)?.charAt(0);
24
+ if (parts.length >= 2 && first && last) return (first + last).toUpperCase();
25
+ return first?.toUpperCase() || "?";
24
26
  }
25
27
  function Avatar({ src, name, size = "md", status, className }) {
26
28
  const strings = useStrings();
@@ -37,14 +37,10 @@ function BareModal({ open, onClose, ariaLabel, className, onPanelKeyDown, onPane
37
37
  return;
38
38
  }
39
39
  const focusable = panel.querySelectorAll(FOCUSABLE);
40
- if (focusable.length === 0) {
41
- e.preventDefault();
42
- onPanelKeyDown?.(e);
43
- return;
44
- }
45
40
  const first = focusable[0];
46
41
  const last = focusable[focusable.length - 1];
47
42
  if (!first || !last) {
43
+ e.preventDefault();
48
44
  onPanelKeyDown?.(e);
49
45
  return;
50
46
  }
@@ -1,4 +1,4 @@
1
- import React, { createContext, useContext, useEffect, useLayoutEffect } from "react";
1
+ import React, { createContext, useContext, useEffect, useLayoutEffect, useMemo } from "react";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  //#region src/components/Breadcrumbs.tsx
4
4
  var BreadcrumbContext = createContext({
@@ -22,20 +22,27 @@ function BreadcrumbProvider({ children }) {
22
22
  const [titleActionSlot, setTitleActionSlot] = React.useState(null);
23
23
  const [rightActionSlot, setRightActionSlot] = React.useState(null);
24
24
  const claimedPath = React.useRef(null);
25
+ const value = useMemo(() => ({
26
+ breadcrumbs,
27
+ setBreadcrumbs,
28
+ rightsideBreadcrumb,
29
+ setRightsideBreadcrumb,
30
+ hidden,
31
+ setHidden,
32
+ titleActionSlot,
33
+ setTitleActionSlot,
34
+ rightActionSlot,
35
+ setRightActionSlot,
36
+ claimedPath
37
+ }), [
38
+ breadcrumbs,
39
+ rightsideBreadcrumb,
40
+ hidden,
41
+ titleActionSlot,
42
+ rightActionSlot
43
+ ]);
25
44
  return /* @__PURE__ */ jsx(BreadcrumbContext.Provider, {
26
- value: {
27
- breadcrumbs,
28
- setBreadcrumbs,
29
- rightsideBreadcrumb,
30
- setRightsideBreadcrumb,
31
- hidden,
32
- setHidden,
33
- titleActionSlot,
34
- setTitleActionSlot,
35
- rightActionSlot,
36
- setRightActionSlot,
37
- claimedPath
38
- },
45
+ value,
39
46
  children
40
47
  });
41
48
  }
@@ -6,15 +6,13 @@ interface IControlledCollapsiblePanelProps {
6
6
  setOpen?: React.Dispatch<React.SetStateAction<boolean>>;
7
7
  onClick?: (() => void) | undefined;
8
8
  wrapperElement?: React.ElementType | undefined;
9
- shown?: boolean;
10
- setShown?: (shown: boolean) => void;
11
9
  customizedButton?: React.ReactNode;
12
10
  /** Rendered right after the title, outside the toggle button. */
13
11
  titleAction?: React.ReactNode;
14
12
  className?: string | undefined;
15
13
  }
16
14
  export declare function ControlledCollapsiblePanel({ title, children, open, setOpen, onClick, wrapperElement, customizedButton, titleAction, className, }: IControlledCollapsiblePanelProps): React.JSX.Element;
17
- export declare function UncontrolledCollapsiblePanel({ title, children, onClick, wrapperElement, initialState, className, customizedButton, }: Omit<IControlledCollapsiblePanelProps, 'setOpen' | 'open'> & {
15
+ export declare function UncontrolledCollapsiblePanel({ title, children, onClick, wrapperElement, initialState, className, customizedButton, titleAction, }: Omit<IControlledCollapsiblePanelProps, 'setOpen' | 'open'> & {
18
16
  initialState?: boolean | undefined;
19
17
  }): React.JSX.Element;
20
18
  type MaybeCollapsibleProps = {
@@ -4,14 +4,13 @@ import React, { useCallback } from "react";
4
4
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
5
5
  import AnimateHeight from "react-animate-height";
6
6
  //#region src/components/CollapsiblePanel.tsx
7
- var noop = () => {};
8
- function ControlledCollapsiblePanel({ title = "", children, open, setOpen, onClick = noop, wrapperElement, customizedButton = null, titleAction, className }) {
7
+ function ControlledCollapsiblePanel({ title = "", children, open, setOpen, onClick, wrapperElement, customizedButton = null, titleAction, className }) {
9
8
  const toggleOpen = useCallback(() => {
10
- if (onClick) onClick();
9
+ onClick?.();
11
10
  setOpen?.((open) => !open);
12
11
  }, [setOpen, onClick]);
13
12
  const contentId = React.useId();
14
- const Wrapper = wrapperElement ? wrapperElement : React.Fragment;
13
+ const Wrapper = wrapperElement ?? React.Fragment;
15
14
  return React.isValidElement(title) ? /* @__PURE__ */ jsxs(Fragment$1, { children: [title, /* @__PURE__ */ jsx(Wrapper, { children: /* @__PURE__ */ jsx(AnimateHeight, {
16
15
  duration: 300,
17
16
  height: open ? "auto" : 0,
@@ -46,7 +45,7 @@ function ControlledCollapsiblePanel({ title = "", children, open, setOpen, onCli
46
45
  })]
47
46
  });
48
47
  }
49
- function UncontrolledCollapsiblePanel({ title, children, onClick, wrapperElement, initialState = true, className, customizedButton = null }) {
48
+ function UncontrolledCollapsiblePanel({ title, children, onClick, wrapperElement, initialState = true, className, customizedButton = null, titleAction }) {
50
49
  const [open, setOpen] = React.useState(initialState);
51
50
  const passTitle = typeof title === "function" ? title(open, setOpen) : title;
52
51
  return /* @__PURE__ */ jsx(ControlledCollapsiblePanel, {
@@ -57,6 +56,7 @@ function UncontrolledCollapsiblePanel({ title, children, onClick, wrapperElement
57
56
  wrapperElement,
58
57
  className,
59
58
  customizedButton,
59
+ titleAction,
60
60
  children
61
61
  });
62
62
  }
@@ -4,10 +4,6 @@ export interface IProps {
4
4
  options: (ICategory | IOptions | string)[];
5
5
  onActiveChange?: (option: unknown) => void;
6
6
  onChange: React.Dispatch<SetStateAction<unknown>>;
7
- onRangeChange?: (range: {
8
- min: number;
9
- max: number;
10
- }) => void;
11
7
  value: IOptions | string | Record<string, unknown>;
12
8
  id?: string | undefined;
13
9
  ariaLabel?: string | undefined;