@parallelworks/ui 0.19.0 → 0.19.1

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 (217) hide show
  1. package/dist/{chunks/memoryStore-CC91S5LP.js → ai/adapter/memoryStore.js} +2 -2
  2. package/dist/ai/adapter/mock/index.js +2 -80
  3. package/dist/ai/adapter/mock/mockAdapter.js +81 -0
  4. package/dist/ai/adapter/openai/createOpenAIStream.js +33 -0
  5. package/dist/ai/adapter/openai/errors.js +37 -0
  6. package/dist/ai/adapter/openai/index.js +5 -333
  7. package/dist/ai/adapter/openai/openaiAdapter.js +54 -0
  8. package/dist/ai/adapter/openai/responses.js +108 -0
  9. package/dist/ai/adapter/openai/sse.js +112 -0
  10. package/dist/{chunks/types-Yy0NrerF.js → ai/adapter/types.js} +1 -1
  11. package/dist/ai/agent/classifier.js +12 -0
  12. package/dist/ai/agent/diff.js +223 -0
  13. package/dist/ai/agent/diffHighlight.js +101 -0
  14. package/dist/ai/agent/permissions.js +29 -0
  15. package/dist/ai/agent/toolfmt.js +383 -0
  16. package/dist/ai/agent/transcript.js +127 -0
  17. package/dist/ai/components/AllocationSelector.js +56 -0
  18. package/dist/ai/components/AttachmentManager.js +144 -0
  19. package/dist/ai/components/AttachmentUpload.js +179 -0
  20. package/dist/ai/components/BlockedGroupBanner.js +29 -0
  21. package/dist/ai/components/BranchNavigator.js +57 -0
  22. package/dist/ai/components/ChatEmptyState.js +171 -0
  23. package/dist/ai/components/ChatInput.js +265 -0
  24. package/dist/ai/components/ChatLayout.js +15 -0
  25. package/dist/ai/components/ChatMessage.js +338 -0
  26. package/dist/ai/components/ChatMessageList.js +301 -0
  27. package/dist/ai/components/ChatSidebar.js +345 -0
  28. package/dist/ai/components/ChatThread.js +198 -0
  29. package/dist/ai/components/ComposerChrome.js +50 -0
  30. package/dist/ai/components/ComposerFrame.js +33 -0
  31. package/dist/ai/components/DragOverlay.js +20 -0
  32. package/dist/ai/components/ModelSelector.js +343 -0
  33. package/dist/ai/components/ShareDialog.js +260 -0
  34. package/dist/ai/components/SlashMenu.js +91 -0
  35. package/dist/ai/components/agent/ActivityLine.js +151 -0
  36. package/dist/ai/components/agent/AgentMessageParts.js +721 -0
  37. package/dist/ai/components/agent/PermissionPicker.js +38 -0
  38. package/dist/ai/components/attachmentKeys.js +10 -0
  39. package/dist/ai/core/ChatProvider.js +848 -0
  40. package/dist/ai/core/KeyboardShortcutsProvider.js +9 -0
  41. package/dist/ai/core/chatReducer.js +223 -0
  42. package/dist/ai/core/config.js +45 -0
  43. package/dist/ai/core/events.js +4 -0
  44. package/dist/ai/core/exportConversation.js +42 -0
  45. package/dist/ai/core/greeting.js +38 -0
  46. package/dist/ai/core/parts.js +84 -0
  47. package/dist/ai/core/useDragDrop.js +36 -0
  48. package/dist/ai/core/useKeyboardShortcuts.js +77 -0
  49. package/dist/ai/core/useSSEStream.js +98 -0
  50. package/dist/ai/index.js +43 -6644
  51. package/dist/ai/strings.js +175 -0
  52. package/dist/ai/ui/Avatar.js +35 -0
  53. package/dist/ai/ui/Dropdown.js +110 -0
  54. package/dist/ai/ui/Markdown.js +79 -0
  55. package/dist/ai/ui/Notice.js +77 -0
  56. package/dist/ai/ui/codeBreaks.js +36 -0
  57. package/dist/ai/ui/prose.js +13 -0
  58. package/dist/ai/ui/sidebar.js +148 -0
  59. package/dist/ai/utils.js +23 -0
  60. package/dist/components/AddMenu.js +46 -0
  61. package/dist/components/Avatar.js +46 -0
  62. package/dist/{chunks/BareModal-BtRiQezg.js → components/BareModal.js} +1 -1
  63. package/dist/components/Breadcrumbs.js +83 -0
  64. package/dist/components/Button.js +83 -0
  65. package/dist/components/Callout.js +23 -0
  66. package/dist/components/Card.js +31 -0
  67. package/dist/components/CollapsiblePanel.js +77 -0
  68. package/dist/{chunks/ConfirmModal-aOR4O1QE.js → components/ConfirmModal.js} +4 -9
  69. package/dist/components/CopyToClipboard.js +81 -0
  70. package/dist/{chunks/Markdown-CM-FenNE.js → components/CreateModal.js} +9 -116
  71. package/dist/components/DescriptionList.js +42 -0
  72. package/dist/components/Dropdown.js +349 -0
  73. package/dist/components/EmptyState.js +27 -0
  74. package/dist/components/Filter.js +32 -0
  75. package/dist/components/FilterPill.js +13 -0
  76. package/dist/components/IconButton.js +34 -0
  77. package/dist/{chunks/Indicator-hBSIxOPK.js → components/Indicator.js} +1 -1
  78. package/dist/components/Input.js +88 -0
  79. package/dist/components/LetterBadge.js +13 -0
  80. package/dist/components/Listbox.js +123 -0
  81. package/dist/{chunks/Loader-BXoXTZGc.js → components/Loader.js} +1 -1
  82. package/dist/components/Markdown.js +15 -0
  83. package/dist/components/MultiSelect.js +171 -0
  84. package/dist/components/Overview.js +39 -0
  85. package/dist/components/PageHeader.js +194 -0
  86. package/dist/components/Pagination.js +35 -0
  87. package/dist/{chunks/Provider-CDYurraE.js → components/Provider.js} +1 -1
  88. package/dist/{chunks/RequiredMark-DfLdYbYZ.js → components/RequiredMark.js} +1 -1
  89. package/dist/components/SectionHeader.js +11 -0
  90. package/dist/components/SettingModeOption.js +30 -0
  91. package/dist/components/SettingRow.js +35 -0
  92. package/dist/components/SettingsCard.js +31 -0
  93. package/dist/components/SettingsGroup.js +17 -0
  94. package/dist/components/Sort.js +42 -0
  95. package/dist/components/StatusBadge.js +63 -0
  96. package/dist/components/SwitchToggle.js +59 -0
  97. package/dist/{chunks/SwitchToggleSmall-FcsaYSTs.js → components/SwitchToggleSmall.js} +1 -1
  98. package/dist/{chunks/Table-DU2MW8xz.js → components/Table.js} +3 -3
  99. package/dist/components/Toggle.js +20 -0
  100. package/dist/{chunks/Tooltip-K5HJr4_p.js → components/Tooltip.js} +2 -27
  101. package/dist/{chunks/StatusBadge-C1Blx_cW.js → components/dropdownUtils.js} +2 -63
  102. package/dist/{chunks/focus-D_iZJTW1.js → components/focus.js} +1 -1
  103. package/dist/components/ghostButton.js +7 -0
  104. package/dist/components/inputClasses.js +4 -0
  105. package/dist/components/keys.js +27 -0
  106. package/dist/components/relativeTime.js +32 -0
  107. package/dist/{chunks/useCssIsDark-DHr05dG2.js → components/useCssIsDark.js} +1 -1
  108. package/dist/{chunks/duration-BOv8Phl8.js → duration.js} +1 -1
  109. package/dist/editor/Editor.js +13 -0
  110. package/dist/editor/EditorField.js +53 -0
  111. package/dist/editor/FormCodePanel.js +173 -0
  112. package/dist/{chunks/Monaco-DJ8MW-Dq.js → editor/Monaco.js} +4 -2
  113. package/dist/editor/index.js +6 -1
  114. package/dist/editor/themes.js +91 -0
  115. package/dist/editor/workers.js +29 -0
  116. package/dist/editor/yaml.js +15 -0
  117. package/dist/file-explorer/FileExplorer.js +1402 -0
  118. package/dist/file-explorer/FileExplorerContext.js +370 -0
  119. package/dist/{chunks/CodePreviewEditor-YqQBz1YL.js → file-explorer/FilePreview/CodePreviewEditor.js} +3 -2
  120. package/dist/file-explorer/FilePreview/Inline.js +45 -0
  121. package/dist/file-explorer/FilePreview/StreamedBadge.js +16 -0
  122. package/dist/file-explorer/FilePreview/Viewer.js +526 -0
  123. package/dist/file-explorer/FilePreview/index.js +266 -0
  124. package/dist/file-explorer/FilePreview/parse.js +63 -0
  125. package/dist/file-explorer/FilePreview/previewCache.js +98 -0
  126. package/dist/file-explorer/FilePreview/previewType.js +151 -0
  127. package/dist/file-explorer/FilePreview/readCappedBytes.js +196 -0
  128. package/dist/file-explorer/FilePreview/useObjectBlobUrl.js +95 -0
  129. package/dist/file-explorer/FilePreview/useObjectText.js +109 -0
  130. package/dist/file-explorer/FilePreview/usePresignedUrl.js +73 -0
  131. package/dist/file-explorer/FileRow.js +165 -0
  132. package/dist/file-explorer/FileRowsWindow.js +92 -0
  133. package/dist/file-explorer/FileTable.js +172 -0
  134. package/dist/file-explorer/FileTableHeaders.js +49 -0
  135. package/dist/file-explorer/LoadMoreRow.js +46 -0
  136. package/dist/file-explorer/TreeView.js +376 -0
  137. package/dist/file-explorer/helpers.js +537 -0
  138. package/dist/file-explorer/index.js +7 -5671
  139. package/dist/file-explorer/lib/batch.js +39 -0
  140. package/dist/file-explorer/lib/errors.js +9 -0
  141. package/dist/file-explorer/lib/paging.js +124 -0
  142. package/dist/file-explorer/lib/utils.js +322 -0
  143. package/dist/file-explorer/toasts.js +400 -0
  144. package/dist/file-explorer/treeRows.js +73 -0
  145. package/dist/file-explorer/useNearEnd.js +27 -0
  146. package/dist/form/FieldWrapper.js +54 -0
  147. package/dist/form/Fields/BooleanField.js +51 -0
  148. package/dist/form/Fields/CheckboxGroupField.js +72 -0
  149. package/dist/form/Fields/ColorField.js +33 -0
  150. package/dist/form/Fields/DropdownField.js +49 -0
  151. package/dist/form/Fields/DurationField.js +130 -0
  152. package/dist/form/Fields/LabelField.js +19 -0
  153. package/dist/form/Fields/MultiDropdownField.js +40 -0
  154. package/dist/form/Fields/NumberField.js +51 -0
  155. package/dist/form/Fields/ObjectField.js +54 -0
  156. package/dist/form/Fields/RadioField.js +49 -0
  157. package/dist/form/Fields/RangeField.js +58 -0
  158. package/dist/form/Fields/ResourceOptionIcon.js +21 -0
  159. package/dist/form/Fields/StepField.js +6 -0
  160. package/dist/form/Fields/StringField.js +37 -0
  161. package/dist/form/Fields/TextAreaField.js +31 -0
  162. package/dist/form/Fields/durationValidity.js +11 -0
  163. package/dist/form/Form.js +643 -0
  164. package/dist/form/SpecialTypes/DropdownWrapper.js +30 -0
  165. package/dist/form/SpecialTypes/FormikCustomDropdown.js +122 -0
  166. package/dist/form/SpecialTypes/FormikCustomInput.js +111 -0
  167. package/dist/form/SpecialTypes/FormikMultiSelectDropdown.js +31 -0
  168. package/dist/form/SpecialTypes/FormikNumericInput.js +60 -0
  169. package/dist/form/Wizard/WizardContainer.js +138 -0
  170. package/dist/form/Wizard/WizardNavigation.js +92 -0
  171. package/dist/form/Wizard/WizardStepContent.js +35 -0
  172. package/dist/form/Wizard/WizardStepIndicator.js +108 -0
  173. package/dist/form/Wizard/useWizardState.js +118 -0
  174. package/dist/form/Wizard/utils.js +34 -0
  175. package/dist/form/fieldContext.js +26 -0
  176. package/dist/form/fieldRegistry.js +50 -0
  177. package/dist/form/index.js +17 -2321
  178. package/dist/{chunks/FieldWrapper-Cszwg-4B.js → form/lib.js} +2 -97
  179. package/dist/form/useParsedOpts.js +62 -0
  180. package/dist/form/usePrevious.js +11 -0
  181. package/dist/form/utils/checkShowIfObject.js +14 -0
  182. package/dist/form/utils/duration.js +69 -0
  183. package/dist/form/utils/getParentValue.js +23 -0
  184. package/dist/form/utils/getPlaceholder.js +9 -0
  185. package/dist/form/utils/getValueUsingPath.js +23 -0
  186. package/dist/form/utils/secondaryField.js +22 -0
  187. package/dist/graph/AnnotationBanner.js +141 -0
  188. package/dist/graph/Collapse.js +41 -0
  189. package/dist/graph/DependencyGraph.js +1398 -0
  190. package/dist/graph/JobSummary.js +104 -0
  191. package/dist/graph/MatrixGroup.js +107 -0
  192. package/dist/graph/Reveal.js +57 -0
  193. package/dist/graph/TreeView.js +711 -0
  194. package/dist/graph/index.js +1 -2519
  195. package/dist/graph/types.js +6 -0
  196. package/dist/index.js +46 -599
  197. package/dist/list/ActionToolbar.js +184 -0
  198. package/dist/list/HoverCard.js +73 -0
  199. package/dist/list/ListPage.js +145 -0
  200. package/dist/list/ListPager.js +59 -0
  201. package/dist/list/ListTable.js +163 -0
  202. package/dist/list/ListViewControls.js +372 -0
  203. package/dist/list/RowContextMenu.js +350 -0
  204. package/dist/list/UserHoverCard.js +117 -0
  205. package/dist/list/index.js +11 -2
  206. package/dist/list/listSearch.js +137 -0
  207. package/dist/list/listView.js +210 -0
  208. package/dist/list/textEntry.js +10 -0
  209. package/dist/{chunks/logviewer-BTKuQWzw.js → logviewer/LogViewer.js} +4 -82
  210. package/dist/logviewer/index.js +2 -1
  211. package/dist/logviewer/segments.js +80 -0
  212. package/package.json +1 -1
  213. package/dist/chunks/PageHeader-0kG_iypP.js +0 -692
  214. package/dist/chunks/SwitchToggle-DShNdcao.js +0 -758
  215. package/dist/chunks/editor-Dk1rbV75.js +0 -361
  216. package/dist/chunks/list-CJm875m7.js +0 -1418
  217. /package/dist/{chunks/MarkdownImpl-CyeTVvSt.js → components/MarkdownImpl.js} +0 -0
@@ -0,0 +1,144 @@
1
+ import { DownloadFileIcon, FileIcon, TrashIcon } from "../../icons.js";
2
+ import { useChatConfig } from "../core/config.js";
3
+ import { useChat } from "../core/ChatProvider.js";
4
+ import { formatFileSize } from "../utils.js";
5
+ import { attachmentsPageKey } from "./attachmentKeys.js";
6
+ import cx from "classnames";
7
+ import { useCallback, useMemo, useState } from "react";
8
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
9
+ import { DateTime } from "luxon";
10
+ import useSWRInfinite from "swr/infinite";
11
+ //#region src/ai/components/AttachmentManager.tsx
12
+ function triggerDownload(url, filename) {
13
+ const a = document.createElement("a");
14
+ a.href = url;
15
+ a.download = filename;
16
+ document.body.appendChild(a);
17
+ a.click();
18
+ document.body.removeChild(a);
19
+ }
20
+ function AttachmentManager() {
21
+ const { adapter, notify } = useChat();
22
+ const t = useChatConfig().strings.attachmentManager;
23
+ const attachmentsAdapter = adapter.attachments;
24
+ const [deleting, setDeleting] = useState(null);
25
+ const [thumbnails, setThumbnails] = useState({});
26
+ const markThumbnail = useCallback((attachmentId, state) => {
27
+ setThumbnails((previous) => previous[attachmentId] === state ? previous : {
28
+ ...previous,
29
+ [attachmentId]: state
30
+ });
31
+ }, []);
32
+ const { data: pages, size, setSize, isLoading, isValidating, mutate } = useSWRInfinite(attachmentsAdapter ? attachmentsPageKey : () => null, ([, cursor]) => attachmentsAdapter.list({
33
+ limit: 24,
34
+ cursor
35
+ }));
36
+ const attachments = useMemo(() => pages?.flatMap((page) => page?.attachments ?? []) ?? [], [pages]);
37
+ const handleDelete = useCallback(async (attachmentId) => {
38
+ if (!attachmentsAdapter) return;
39
+ setDeleting(attachmentId);
40
+ try {
41
+ await attachmentsAdapter.remove(attachmentId);
42
+ await mutate();
43
+ } catch (error) {
44
+ notify.error(error instanceof Error ? error.message : String(error));
45
+ } finally {
46
+ setDeleting(null);
47
+ }
48
+ }, [
49
+ attachmentsAdapter,
50
+ mutate,
51
+ notify
52
+ ]);
53
+ if (!attachmentsAdapter) return null;
54
+ const hasMore = pages?.[pages.length - 1]?.hasMore ?? false;
55
+ return /* @__PURE__ */ jsx("div", {
56
+ className: "flex h-full bg-(--theme-app-bg)",
57
+ children: /* @__PURE__ */ jsx("div", {
58
+ className: "flex-1 flex flex-col min-w-0",
59
+ children: /* @__PURE__ */ jsx("div", {
60
+ className: "flex-1 overflow-y-auto p-8",
61
+ children: /* @__PURE__ */ jsxs("div", {
62
+ className: "max-w-4xl mx-auto",
63
+ children: [/* @__PURE__ */ jsx("h1", {
64
+ className: "text-2xl font-semibold mb-6 text-(--theme-panel)",
65
+ children: t.title
66
+ }), isLoading && attachments.length === 0 ? /* @__PURE__ */ jsx("div", {
67
+ className: "flex items-center justify-center py-12",
68
+ children: /* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-2 border-current border-t-transparent theme-muted-text" })
69
+ }) : attachments.length === 0 ? /* @__PURE__ */ jsx("p", {
70
+ className: "text-center theme-muted-text py-12 text-sm",
71
+ children: t.empty
72
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
73
+ className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4",
74
+ children: attachments.map((att) => {
75
+ const downloadUrl = attachmentsAdapter.downloadUrl(att.id);
76
+ const isDeleting = deleting === att.id;
77
+ const thumbnail = thumbnails[att.id];
78
+ const showThumbnail = att.contentType?.startsWith("image/") && thumbnail !== "broken";
79
+ const openProps = attachmentsAdapter.onOpen ? {
80
+ onClick: () => attachmentsAdapter.onOpen?.(att),
81
+ role: "button",
82
+ style: { cursor: "pointer" }
83
+ } : {};
84
+ return /* @__PURE__ */ jsxs("div", {
85
+ className: cx("rounded-lg border theme-border overflow-hidden", isDeleting && "opacity-50 pointer-events-none"),
86
+ children: [/* @__PURE__ */ jsxs("div", {
87
+ className: "relative aspect-square bg-(--theme-muted-panel-bg) flex items-center justify-center",
88
+ ...openProps,
89
+ children: [/* @__PURE__ */ jsx(FileIcon, { className: "w-8 h-8 theme-muted-text" }), showThumbnail && /* @__PURE__ */ jsx("img", {
90
+ src: downloadUrl,
91
+ alt: att.filename || "Image",
92
+ className: cx("absolute inset-0 w-full h-full object-cover", thumbnail !== "loaded" && "opacity-0"),
93
+ loading: "lazy",
94
+ onLoad: () => markThumbnail(att.id, "loaded"),
95
+ onError: () => markThumbnail(att.id, "broken")
96
+ })]
97
+ }), /* @__PURE__ */ jsxs("div", {
98
+ className: "p-3 text-xs bg-(--theme-panel-bg) flex items-center justify-between gap-1",
99
+ children: [/* @__PURE__ */ jsxs("div", {
100
+ className: "min-w-0 flex-1",
101
+ children: [/* @__PURE__ */ jsx("p", {
102
+ className: "truncate font-medium text-(--theme-panel)",
103
+ children: att.filename || "File"
104
+ }), /* @__PURE__ */ jsxs("p", {
105
+ className: "truncate theme-muted-text",
106
+ children: [att.size ? formatFileSize(att.size) : "", att.uploadedAt && /* @__PURE__ */ jsxs(Fragment$1, { children: [att.size ? " · " : "", DateTime.fromISO(att.uploadedAt).toRelative()] })]
107
+ })]
108
+ }), /* @__PURE__ */ jsxs("div", {
109
+ className: "flex items-center gap-0.5 flex-shrink-0",
110
+ children: [/* @__PURE__ */ jsx("button", {
111
+ type: "button",
112
+ onClick: () => triggerDownload(downloadUrl, att.filename || "File"),
113
+ className: "p-1.5 rounded-lg hover:theme-hover theme-muted-text transition-colors",
114
+ title: t.download,
115
+ children: /* @__PURE__ */ jsx(DownloadFileIcon, { className: "w-4 h-4" })
116
+ }), /* @__PURE__ */ jsx("button", {
117
+ type: "button",
118
+ onClick: () => handleDelete(att.id),
119
+ disabled: isDeleting,
120
+ className: "p-1.5 rounded-lg hover:bg-red-500/15 theme-muted-text hover:text-red-600 transition-colors",
121
+ title: t.delete,
122
+ children: /* @__PURE__ */ jsx(TrashIcon, { className: "w-4 h-4" })
123
+ })]
124
+ })]
125
+ })]
126
+ }, att.id);
127
+ })
128
+ }), hasMore && /* @__PURE__ */ jsx("div", {
129
+ className: "flex justify-center mt-8",
130
+ children: /* @__PURE__ */ jsx("button", {
131
+ type: "button",
132
+ onClick: () => setSize(size + 1),
133
+ disabled: isValidating,
134
+ className: "px-6 py-3 text-sm rounded-lg theme-muted-panel hover:theme-hover transition-colors disabled:opacity-50",
135
+ children: isValidating ? t.loading : t.loadMore
136
+ })
137
+ })] })]
138
+ })
139
+ })
140
+ })
141
+ });
142
+ }
143
+ //#endregion
144
+ export { AttachmentManager as default };
@@ -0,0 +1,179 @@
1
+ import { AddIcon, FileSolidIcon, ImageSolidIcon, PdfSolidIcon, TrashIcon } from "../../icons.js";
2
+ import { useChatConfig } from "../core/config.js";
3
+ import { useChat } from "../core/ChatProvider.js";
4
+ import { formatFileSize } from "../utils.js";
5
+ import { ATTACHMENTS_INFINITE_KEY } from "./attachmentKeys.js";
6
+ import cx from "classnames";
7
+ import { forwardRef, useCallback, useImperativeHandle, useRef, useState } from "react";
8
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
9
+ import { mutate } from "swr";
10
+ //#region src/ai/components/AttachmentUpload.tsx
11
+ var DEFAULT_MAX_FILE_SIZE = 26214400;
12
+ var DEFAULT_MAX_FILES = 10;
13
+ function getFileIcon(mimeType) {
14
+ if (!mimeType) return FileSolidIcon;
15
+ if (mimeType.startsWith("image/")) return ImageSolidIcon;
16
+ if (mimeType === "application/pdf") return PdfSolidIcon;
17
+ return FileSolidIcon;
18
+ }
19
+ var AttachmentUpload = forwardRef(function AttachmentUpload({ conversationId, attachments, onAttachmentsChange, disabled = false, maxFiles = DEFAULT_MAX_FILES, maxFileSize = DEFAULT_MAX_FILE_SIZE }, ref) {
20
+ const { adapter, notify } = useChat();
21
+ const t = useChatConfig().strings.attachmentUpload;
22
+ const [isDragging, setIsDragging] = useState(false);
23
+ const fileInputRef = useRef(null);
24
+ const validateFile = useCallback((file) => {
25
+ if (file.size > maxFileSize) return t.fileTooLarge(formatFileSize(maxFileSize));
26
+ if (attachments.length >= maxFiles) return t.maxFilesAllowed(maxFiles);
27
+ return null;
28
+ }, [
29
+ attachments.length,
30
+ maxFileSize,
31
+ maxFiles,
32
+ t
33
+ ]);
34
+ const uploadFile = useCallback(async (file) => {
35
+ if (!adapter.attachments) return null;
36
+ try {
37
+ const uploaded = await adapter.attachments.upload(file, conversationId);
38
+ await mutate(ATTACHMENTS_INFINITE_KEY);
39
+ return {
40
+ id: uploaded.id,
41
+ filename: uploaded.filename,
42
+ mimeType: uploaded.contentType,
43
+ size: uploaded.size,
44
+ uploaded: true
45
+ };
46
+ } catch (error) {
47
+ return {
48
+ file,
49
+ filename: file.name,
50
+ mimeType: file.type,
51
+ size: file.size,
52
+ error: error instanceof Error ? error.message : String(error)
53
+ };
54
+ }
55
+ }, [adapter, conversationId]);
56
+ const handleFiles = useCallback(async (files) => {
57
+ const fileArray = Array.from(files);
58
+ const newAttachments = [];
59
+ for (const file of fileArray) {
60
+ const error = validateFile(file);
61
+ if (error) {
62
+ notify.error(error);
63
+ continue;
64
+ }
65
+ const pendingAttachment = {
66
+ file,
67
+ filename: file.name,
68
+ mimeType: file.type,
69
+ size: file.size,
70
+ uploading: true
71
+ };
72
+ newAttachments.push(pendingAttachment);
73
+ }
74
+ onAttachmentsChange([...attachments, ...newAttachments]);
75
+ const uploaded = await Promise.all(newAttachments.map(async (a) => {
76
+ if (a.file) return uploadFile(a.file);
77
+ return a;
78
+ }));
79
+ onAttachmentsChange(attachments.concat(uploaded.filter((a) => a !== null)));
80
+ }, [
81
+ attachments,
82
+ notify,
83
+ onAttachmentsChange,
84
+ uploadFile,
85
+ validateFile
86
+ ]);
87
+ const handleDragOver = useCallback((e) => {
88
+ e.preventDefault();
89
+ setIsDragging(true);
90
+ }, []);
91
+ const handleDragLeave = useCallback((e) => {
92
+ e.preventDefault();
93
+ setIsDragging(false);
94
+ }, []);
95
+ const handleDrop = useCallback((e) => {
96
+ e.preventDefault();
97
+ setIsDragging(false);
98
+ if (e.dataTransfer.files.length > 0) handleFiles(e.dataTransfer.files);
99
+ }, [handleFiles]);
100
+ const handleInputChange = useCallback((e) => {
101
+ if (e.target.files && e.target.files.length > 0) handleFiles(e.target.files);
102
+ e.target.value = "";
103
+ }, [handleFiles]);
104
+ const handleRemove = useCallback((index) => {
105
+ onAttachmentsChange(attachments.filter((_, i) => i !== index));
106
+ }, [attachments, onAttachmentsChange]);
107
+ useImperativeHandle(ref, () => ({ addFiles: handleFiles }), [handleFiles]);
108
+ const handleClick = () => {
109
+ fileInputRef.current?.click();
110
+ };
111
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("input", {
112
+ ref: fileInputRef,
113
+ type: "file",
114
+ multiple: true,
115
+ onChange: handleInputChange,
116
+ disabled,
117
+ className: "hidden",
118
+ accept: "image/*,.pdf,.txt,.csv,.doc,.docx,.xls,.xlsx,.ppt,.pptx"
119
+ }), /* @__PURE__ */ jsxs("div", {
120
+ className: "space-y-2",
121
+ children: [attachments.length > 0 && /* @__PURE__ */ jsx("div", {
122
+ className: "flex flex-wrap gap-2",
123
+ children: attachments.map((attachment, index) => {
124
+ const Icon = getFileIcon(attachment.mimeType);
125
+ return /* @__PURE__ */ jsxs("div", {
126
+ className: cx("flex items-center gap-2 px-2 py-1 rounded border theme-border theme-muted-panel", attachment.error && "border-red-500"),
127
+ children: [
128
+ /* @__PURE__ */ jsx(Icon, { className: "w-4 h-4 theme-muted-text" }),
129
+ /* @__PURE__ */ jsx("span", {
130
+ className: "text-sm truncate max-w-[150px] text-(--theme-app)",
131
+ children: attachment.filename
132
+ }),
133
+ /* @__PURE__ */ jsx("span", {
134
+ className: "text-xs theme-muted-text",
135
+ children: formatFileSize(attachment.size)
136
+ }),
137
+ attachment.uploading && /* @__PURE__ */ jsx("span", {
138
+ className: "text-xs text-blue-500 animate-pulse",
139
+ children: "Uploading..."
140
+ }),
141
+ attachment.error && /* @__PURE__ */ jsx("span", {
142
+ className: "text-xs text-red-500",
143
+ title: attachment.error,
144
+ children: "⚠️"
145
+ }),
146
+ /* @__PURE__ */ jsx("button", {
147
+ type: "button",
148
+ onClick: () => handleRemove(index),
149
+ className: "p-0.5 rounded hover:bg-red-500/10",
150
+ title: t.remove,
151
+ disabled,
152
+ children: /* @__PURE__ */ jsx(TrashIcon, { className: "w-3 h-3 text-red-500" })
153
+ })
154
+ ]
155
+ }, attachment.id || index);
156
+ })
157
+ }), /* @__PURE__ */ jsxs("button", {
158
+ type: "button",
159
+ onDragOver: handleDragOver,
160
+ onDragLeave: handleDragLeave,
161
+ onDrop: handleDrop,
162
+ className: cx("relative block w-full border-2 border-dashed rounded-lg p-3 text-center transition-colors", isDragging ? "border-blue-500 bg-blue-500/10" : "theme-border hover:theme-border", disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"),
163
+ onClick: disabled ? void 0 : handleClick,
164
+ children: [/* @__PURE__ */ jsxs("span", {
165
+ className: "flex items-center justify-center gap-2 text-sm theme-muted-text",
166
+ children: [/* @__PURE__ */ jsx(AddIcon, { className: "w-4 h-4" }), /* @__PURE__ */ jsx("span", { children: isDragging ? t.dropFilesHere : t.addFiles })]
167
+ }), /* @__PURE__ */ jsxs("span", {
168
+ className: "block text-xs theme-muted-text mt-1",
169
+ children: [
170
+ "Max ",
171
+ formatFileSize(maxFileSize),
172
+ " per file"
173
+ ]
174
+ })]
175
+ })]
176
+ })] });
177
+ });
178
+ //#endregion
179
+ export { AttachmentUpload as default };
@@ -0,0 +1,29 @@
1
+ import { WarningTriangleIcon } from "../../icons.js";
2
+ import { useChatConfig } from "../core/config.js";
3
+ import { useChat } from "../core/ChatProvider.js";
4
+ import { getProviderKeyFromModelId } from "./ModelSelector.js";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/ai/components/BlockedGroupBanner.tsx
7
+ function BlockedGroupBanner() {
8
+ const { strings } = useChatConfig();
9
+ const { providers, selectedProvider } = useChat();
10
+ if (!selectedProvider) return null;
11
+ const providerKey = getProviderKeyFromModelId(selectedProvider);
12
+ const provider = providers.find((p) => `${p.user}:${p.name}` === providerKey);
13
+ if (!provider?.groupBlocked || !provider.group) return null;
14
+ return /* @__PURE__ */ jsxs("div", {
15
+ className: "mx-4 mt-2 flex items-start gap-3 p-3 rounded-lg bg-amber-500/10 border border-amber-500/20",
16
+ children: [/* @__PURE__ */ jsx(WarningTriangleIcon, { className: "w-4 h-4 text-amber-500 shrink-0 mt-0.5" }), /* @__PURE__ */ jsxs("div", {
17
+ className: "flex-1 min-w-0",
18
+ children: [/* @__PURE__ */ jsx("p", {
19
+ className: "text-sm font-medium text-amber-700",
20
+ children: strings.blockedGroup.title.replace("{group}", provider.group)
21
+ }), /* @__PURE__ */ jsx("p", {
22
+ className: "text-xs text-amber-600/80 mt-0.5",
23
+ children: strings.blockedGroup.message
24
+ })]
25
+ })]
26
+ });
27
+ }
28
+ //#endregion
29
+ export { BlockedGroupBanner as default };
@@ -0,0 +1,57 @@
1
+ import { AngleLeftIcon, AngleRightIcon } from "../../icons.js";
2
+ import { useChatConfig } from "../core/config.js";
3
+ import { useMemo } from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/ai/components/BranchNavigator.tsx
6
+ function findSiblings(messages, messageId) {
7
+ const message = messages.find((m) => m.id === messageId);
8
+ if (!message) return [];
9
+ return messages.filter((m) => m.parentId === message.parentId);
10
+ }
11
+ function BranchNavigator({ messages, currentMessageId, onNavigate }) {
12
+ const t = useChatConfig().strings.branch;
13
+ const siblings = useMemo(() => findSiblings(messages, currentMessageId), [messages, currentMessageId]);
14
+ if (siblings.length <= 1) return null;
15
+ const currentIndex = siblings.findIndex((m) => m.id === currentMessageId);
16
+ const hasPrev = currentIndex > 0;
17
+ const hasNext = currentIndex < siblings.length - 1;
18
+ const handlePrev = () => {
19
+ if (hasPrev) onNavigate(siblings[currentIndex - 1].id);
20
+ };
21
+ const handleNext = () => {
22
+ if (hasNext) onNavigate(siblings[currentIndex + 1].id);
23
+ };
24
+ return /* @__PURE__ */ jsxs("div", {
25
+ className: "inline-flex items-center gap-1 text-xs theme-muted-text",
26
+ children: [
27
+ /* @__PURE__ */ jsx("button", {
28
+ type: "button",
29
+ onClick: handlePrev,
30
+ disabled: !hasPrev,
31
+ className: "p-0.5 rounded theme-hover disabled:opacity-30 disabled:cursor-not-allowed",
32
+ title: t.previous,
33
+ "aria-label": t.previous,
34
+ children: /* @__PURE__ */ jsx(AngleLeftIcon, { className: "w-3 h-3" })
35
+ }),
36
+ /* @__PURE__ */ jsxs("span", {
37
+ className: "min-w-[3ch] text-center",
38
+ children: [
39
+ currentIndex + 1,
40
+ "/",
41
+ siblings.length
42
+ ]
43
+ }),
44
+ /* @__PURE__ */ jsx("button", {
45
+ type: "button",
46
+ onClick: handleNext,
47
+ disabled: !hasNext,
48
+ className: "p-0.5 rounded theme-hover disabled:opacity-30 disabled:cursor-not-allowed",
49
+ title: t.next,
50
+ "aria-label": t.next,
51
+ children: /* @__PURE__ */ jsx(AngleRightIcon, { className: "w-3 h-3" })
52
+ })
53
+ ]
54
+ });
55
+ }
56
+ //#endregion
57
+ export { BranchNavigator as default };
@@ -0,0 +1,171 @@
1
+ import { AddIcon, CloudIcon, TimesIcon, WarningTriangleIcon } from "../../icons.js";
2
+ import { keyedByContent } from "../../components/keys.js";
3
+ import { useChatConfig } from "../core/config.js";
4
+ import { useChat } from "../core/ChatProvider.js";
5
+ import BlockedGroupBanner from "./BlockedGroupBanner.js";
6
+ import { getGreeting } from "../core/greeting.js";
7
+ import useDragDrop from "../core/useDragDrop.js";
8
+ import { ComposerControls, ConnectToolsLink } from "./ComposerChrome.js";
9
+ import DragOverlay from "./DragOverlay.js";
10
+ import ChatInput from "./ChatInput.js";
11
+ import cx from "classnames";
12
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
13
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
14
+ //#region src/ai/components/ChatEmptyState.tsx
15
+ function pickRandom(arr, count) {
16
+ return [...arr].sort(() => Math.random() - .5).slice(0, count);
17
+ }
18
+ function ChatEmptyState({ targetSession }) {
19
+ const { extraLinks, LinkComponent, slots, suggestedPrompts, strings } = useChatConfig();
20
+ const t = strings.emptyState;
21
+ const tInput = strings.input;
22
+ const { adapter, currentUser, models, loadProviders, loadConversations, sendMessage, isLoading, isLoadingModels, hasLoadedModels, clearCurrentConversation, setSelectedProvider, refreshModels } = useChat();
23
+ const [builtinGreeting] = useState(() => getGreeting(currentUser));
24
+ const greeting = t.greeting ?? builtinGreeting;
25
+ const [prompts] = useState(() => pickRandom(suggestedPrompts ?? [], 4));
26
+ const inputRef = useRef(null);
27
+ useEffect(() => {
28
+ clearCurrentConversation();
29
+ }, [clearCurrentConversation]);
30
+ useEffect(() => {
31
+ loadProviders();
32
+ loadConversations();
33
+ }, [loadProviders, loadConversations]);
34
+ const sessionUnreachable = useMemo(() => {
35
+ if (!targetSession || !hasLoadedModels || isLoadingModels) return false;
36
+ if (models.length === 0) return false;
37
+ const prefix = `session:${targetSession}/`;
38
+ return !models.some((m) => m.id.startsWith(prefix));
39
+ }, [
40
+ targetSession,
41
+ models,
42
+ hasLoadedModels,
43
+ isLoadingModels
44
+ ]);
45
+ const [sessionBannerDismissed, setSessionBannerDismissed] = useState(false);
46
+ useEffect(() => {
47
+ if (!targetSession || !hasLoadedModels || isLoadingModels || models.length === 0) return;
48
+ const prefix = `session:${targetSession}/`;
49
+ const match = models.find((m) => m.id.startsWith(prefix));
50
+ if (match) setSelectedProvider(match.id);
51
+ }, [
52
+ targetSession,
53
+ models,
54
+ hasLoadedModels,
55
+ isLoadingModels,
56
+ setSelectedProvider
57
+ ]);
58
+ const handleSendMessage = useCallback(async (content, attachmentIds, attachmentMeta) => {
59
+ await sendMessage(content, attachmentIds, void 0, attachmentMeta);
60
+ }, [sendMessage]);
61
+ const handleSuggestedPrompt = useCallback((prompt) => {
62
+ handleSendMessage(prompt);
63
+ }, [handleSendMessage]);
64
+ const handleFilesDropped = useCallback((files) => {
65
+ inputRef.current?.addFiles(files);
66
+ }, []);
67
+ const { isDragging, dragHandlers } = useDragDrop({ onFilesDropped: handleFilesDropped });
68
+ const attachmentsAvailable = !!adapter.attachments;
69
+ return /* @__PURE__ */ jsx("div", {
70
+ className: "flex h-full bg-(--theme-panel-bg)",
71
+ children: /* @__PURE__ */ jsxs("div", {
72
+ className: "flex-1 flex flex-col min-w-0",
73
+ children: [
74
+ /* @__PURE__ */ jsx(BlockedGroupBanner, {}),
75
+ sessionUnreachable && !sessionBannerDismissed && /* @__PURE__ */ jsxs("div", {
76
+ className: "mx-4 mt-2 flex items-start gap-3 p-3 rounded-lg bg-amber-500/10 border border-amber-500/20",
77
+ children: [
78
+ /* @__PURE__ */ jsx(WarningTriangleIcon, { className: "w-4 h-4 text-amber-500 flex-shrink-0 mt-0.5" }),
79
+ /* @__PURE__ */ jsxs("div", {
80
+ className: "flex-1 min-w-0",
81
+ children: [/* @__PURE__ */ jsx("p", {
82
+ className: "text-sm font-medium text-amber-700",
83
+ children: t.sessionUnreachableTitle(targetSession?.split(":").pop() ?? "")
84
+ }), /* @__PURE__ */ jsx("p", {
85
+ className: "text-xs text-amber-600/80 mt-0.5",
86
+ children: t.sessionUnreachableBody
87
+ })]
88
+ }),
89
+ /* @__PURE__ */ jsxs("div", {
90
+ className: "flex items-center gap-2 flex-shrink-0",
91
+ children: [/* @__PURE__ */ jsx("button", {
92
+ type: "button",
93
+ onClick: () => refreshModels(),
94
+ className: "text-xs font-medium text-amber-700 hover:text-amber-800 hover:bg-amber-500/10 px-2 py-1 rounded transition-colors cursor-pointer",
95
+ children: t.retry
96
+ }), /* @__PURE__ */ jsx("button", {
97
+ type: "button",
98
+ onClick: () => setSessionBannerDismissed(true),
99
+ className: "text-amber-400 hover:text-amber-600 transition-colors cursor-pointer",
100
+ children: /* @__PURE__ */ jsx(TimesIcon, { className: "w-3 h-3" })
101
+ })]
102
+ })
103
+ ]
104
+ }),
105
+ /* @__PURE__ */ jsxs("div", {
106
+ className: "flex-1 flex flex-col relative bg-(--theme-panel-bg)",
107
+ ...attachmentsAvailable ? dragHandlers : {},
108
+ children: [
109
+ isDragging && attachmentsAvailable && /* @__PURE__ */ jsx(DragOverlay, { className: "rounded-2xl m-4" }),
110
+ /* @__PURE__ */ jsx("div", {
111
+ className: "flex-1 flex flex-col items-center justify-center p-8",
112
+ children: /* @__PURE__ */ jsxs("div", {
113
+ className: "max-w-2xl w-full",
114
+ children: [!(hasLoadedModels && models.length === 0) && /* @__PURE__ */ jsxs("div", {
115
+ className: "text-center mb-10",
116
+ children: [/* @__PURE__ */ jsx("h1", {
117
+ className: cx("text-2xl font-semibold text-(--theme-panel)", "tracking-tight"),
118
+ children: greeting
119
+ }), /* @__PURE__ */ jsx("p", {
120
+ className: "mt-2 text-sm theme-muted-text",
121
+ children: t.modernSubtitle
122
+ })]
123
+ }), hasLoadedModels && models.length === 0 ? /* @__PURE__ */ jsxs("div", {
124
+ className: "flex flex-col items-center justify-center gap-4 py-8",
125
+ children: [
126
+ /* @__PURE__ */ jsx(CloudIcon, { className: "h-16 w-16 theme-muted-text opacity-50" }),
127
+ /* @__PURE__ */ jsx("p", {
128
+ className: "text-lg theme-muted-text",
129
+ children: t.noProvidersTitle
130
+ }),
131
+ /* @__PURE__ */ jsx("p", {
132
+ className: "text-sm theme-muted-text text-center max-w-md",
133
+ children: t.noProvidersBody
134
+ }),
135
+ extraLinks.addProvider && /* @__PURE__ */ jsxs(LinkComponent, {
136
+ target: {
137
+ kind: "external",
138
+ href: extraLinks.addProvider
139
+ },
140
+ className: "inline-flex items-center gap-2 px-4 py-2 rounded-lg transition-colors text-sm font-medium theme-element hover:opacity-90",
141
+ children: [/* @__PURE__ */ jsx(AddIcon, { className: "h-3 w-3" }), t.addProvider]
142
+ })
143
+ ]
144
+ }) : /* @__PURE__ */ jsx("div", {
145
+ className: "grid grid-cols-1 md:grid-cols-2 gap-3",
146
+ children: keyedByContent(prompts, (p) => p).map(({ key, item: prompt }) => /* @__PURE__ */ jsx("button", {
147
+ type: "button",
148
+ onClick: () => handleSuggestedPrompt(prompt),
149
+ disabled: isLoading || isLoadingModels || models.length === 0,
150
+ className: "text-left p-4 rounded-2xl border theme-border\n hover:opacity-80 transition-all duration-200 cursor-pointer\n disabled:opacity-50 disabled:cursor-not-allowed\n text-sm shadow-sm bg-(--theme-panel-bg) text-(--theme-panel)",
151
+ children: prompt
152
+ }, key))
153
+ })]
154
+ })
155
+ }),
156
+ /* @__PURE__ */ jsx(ChatInput, {
157
+ ref: inputRef,
158
+ onSend: handleSendMessage,
159
+ disabled: isLoading || isLoadingModels || models.length === 0,
160
+ placeholder: isLoadingModels ? tInput.placeholderLoading : hasLoadedModels && models.length === 0 ? tInput.placeholderNoProviders : tInput.placeholder,
161
+ settingsLeft: /* @__PURE__ */ jsx(ConnectToolsLink, {}),
162
+ settingsRight: /* @__PURE__ */ jsxs(Fragment$1, { children: [slots.composerUsage, /* @__PURE__ */ jsx(ComposerControls, { targetSession })] })
163
+ })
164
+ ]
165
+ })
166
+ ]
167
+ })
168
+ });
169
+ }
170
+ //#endregion
171
+ export { ChatEmptyState as default };