@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,265 @@
1
+ import { ArrowUpIcon, AttachmentIcon, StopSolidIcon } from "../../icons.js";
2
+ import { useChatConfig } from "../core/config.js";
3
+ import { useChat } from "../core/ChatProvider.js";
4
+ import AttachmentUpload from "./AttachmentUpload.js";
5
+ import useDragDrop from "../core/useDragDrop.js";
6
+ import { ComposerContext, ComposerSettings } from "./ComposerChrome.js";
7
+ import DragOverlay from "./DragOverlay.js";
8
+ import { SlashMenu, useSlashMenu } from "./SlashMenu.js";
9
+ import cx from "classnames";
10
+ import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react";
11
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
12
+ //#region src/ai/components/ChatInput.tsx
13
+ var DRAFT_STORAGE_PREFIX = "aiChatDraft:";
14
+ var DRAFT_MAX_LENGTH = 1e4;
15
+ function draftKey(conversationId) {
16
+ return `${DRAFT_STORAGE_PREFIX}${conversationId ?? "new"}`;
17
+ }
18
+ function readDraft(conversationId) {
19
+ try {
20
+ return window.localStorage.getItem(draftKey(conversationId)) ?? "";
21
+ } catch {
22
+ return "";
23
+ }
24
+ }
25
+ function storeDraft(conversationId, text) {
26
+ try {
27
+ if (!text || text.length > DRAFT_MAX_LENGTH) window.localStorage.removeItem(draftKey(conversationId));
28
+ else window.localStorage.setItem(draftKey(conversationId), text);
29
+ } catch {}
30
+ }
31
+ function clearDraft(conversationId) {
32
+ try {
33
+ window.localStorage.removeItem(draftKey(conversationId));
34
+ } catch {}
35
+ }
36
+ /** An optional box that is still empty should not wear the filled look of
37
+ * one waiting to send something. */
38
+ function sendability({ hasText, canFlushQueue, allowEmpty, disabled, modelReady, pendingCount }) {
39
+ const canSend = (hasText || canFlushQueue || allowEmpty) && !disabled && modelReady && pendingCount === 0;
40
+ return {
41
+ canSend,
42
+ emphasised: canSend && (hasText || !allowEmpty)
43
+ };
44
+ }
45
+ function keyAction(e, empty, canEditLast) {
46
+ if (e.key === "Enter" && !e.shiftKey) return "send";
47
+ if (e.key === "ArrowUp" && empty && canEditLast) return "editLast";
48
+ return null;
49
+ }
50
+ function AttachButton({ open, disabled, label, onClick }) {
51
+ return /* @__PURE__ */ jsx("button", {
52
+ type: "button",
53
+ onClick,
54
+ disabled,
55
+ className: cx("flex-shrink-0 p-3 ml-1 mb-1 rounded-xl transition-colors", open ? "text-blue-600" : "theme-muted-text hover:theme-text", "disabled:opacity-50 disabled:cursor-not-allowed"),
56
+ "aria-label": label,
57
+ children: /* @__PURE__ */ jsx(AttachmentIcon, { className: "h-5 w-5" })
58
+ });
59
+ }
60
+ function SendButton({ onClick, enabled, emphasised, label, testId }) {
61
+ return /* @__PURE__ */ jsx("button", {
62
+ type: "button",
63
+ onClick,
64
+ disabled: !enabled,
65
+ "data-testid": testId,
66
+ className: cx("p-2.5 rounded-xl transition-all duration-200", emphasised ? "theme-element hover:opacity-90" : "theme-muted-panel theme-muted-text", !enabled && "cursor-not-allowed"),
67
+ "aria-label": label,
68
+ title: label,
69
+ children: /* @__PURE__ */ jsx(ArrowUpIcon, { className: "h-4 w-4" })
70
+ });
71
+ }
72
+ /** A host-driven turn stands in for the provider's stream and queue. */
73
+ function streamControls(chat, turn) {
74
+ if (turn) return {
75
+ isStreaming: turn.running,
76
+ stopStreaming: turn.onStop,
77
+ queuedCount: 0
78
+ };
79
+ return {
80
+ isStreaming: chat.isStreaming,
81
+ stopStreaming: chat.stopStreaming,
82
+ queuedCount: chat.queuedMessages.length
83
+ };
84
+ }
85
+ var ChatInput = forwardRef(function ChatInput({ onSend, disabled = false, placeholder, conversationId, onEditLastMessage, flush = false, allowEmpty = false, attachments: attachmentsWanted = true, requireModel = true, inputLabel, sendLabel, sendTestId, slash, context, settingsLeft, settingsRight, turn }, ref) {
86
+ const [input, setInput] = useState(() => readDraft(conversationId));
87
+ const [attachments, setAttachments] = useState([]);
88
+ const [showAttachments, setShowAttachments] = useState(false);
89
+ const textareaRef = useRef(null);
90
+ const attachmentUploadRef = useRef(null);
91
+ const chat = useChat();
92
+ const { adapter, flushQueuedMessages, isSelectedModelAvailable } = chat;
93
+ const { isStreaming, stopStreaming, queuedCount } = streamControls(chat, turn);
94
+ const t = useChatConfig().strings.input;
95
+ const effectivePlaceholder = placeholder ?? t.placeholder;
96
+ const attachmentsAvailable = attachmentsWanted && !!adapter.attachments;
97
+ useImperativeHandle(ref, () => ({
98
+ addFiles: (files) => {
99
+ setShowAttachments(true);
100
+ setTimeout(() => {
101
+ attachmentUploadRef.current?.addFiles(files);
102
+ }, 0);
103
+ },
104
+ focus: () => {
105
+ textareaRef.current?.focus();
106
+ }
107
+ }), []);
108
+ useEffect(() => {
109
+ if (textareaRef.current) {
110
+ textareaRef.current.style.height = "auto";
111
+ textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
112
+ }
113
+ }, [input]);
114
+ const previousConversationRef = useRef(conversationId);
115
+ useEffect(() => {
116
+ if (previousConversationRef.current !== conversationId) {
117
+ previousConversationRef.current = conversationId;
118
+ setInput(readDraft(conversationId));
119
+ }
120
+ }, [conversationId]);
121
+ const setInputAndPersist = (value) => {
122
+ setInput(value);
123
+ storeDraft(conversationId, value);
124
+ };
125
+ const uploadedCount = attachments.filter((a) => a.uploaded).length;
126
+ const pendingCount = attachments.length - uploadedCount;
127
+ const canFlushQueue = !isStreaming && queuedCount > 0 && !disabled;
128
+ const { canSend, emphasised } = sendability({
129
+ hasText: input.trim().length > 0,
130
+ canFlushQueue,
131
+ allowEmpty,
132
+ disabled,
133
+ modelReady: isSelectedModelAvailable || !requireModel,
134
+ pendingCount
135
+ });
136
+ const handleSubmit = () => {
137
+ if (!canSend) return;
138
+ if (!input.trim() && canFlushQueue) {
139
+ flushQueuedMessages();
140
+ return;
141
+ }
142
+ const uploaded = attachments.filter((a) => a.uploaded && a.id);
143
+ const attachmentIds = uploaded.map((a) => a.id);
144
+ const meta = {};
145
+ for (const a of uploaded) meta[a.id] = {
146
+ filename: a.filename,
147
+ contentType: a.mimeType,
148
+ size: a.size
149
+ };
150
+ onSend(input.trim(), attachmentIds.length > 0 ? attachmentIds : void 0, attachmentIds.length > 0 ? meta : void 0);
151
+ setInput("");
152
+ clearDraft(conversationId);
153
+ setAttachments([]);
154
+ setShowAttachments(false);
155
+ };
156
+ const slashMenu = useSlashMenu(input, slash, setInputAndPersist);
157
+ const handleKeyDown = (e) => {
158
+ if (slashMenu.handleKey(e)) return;
159
+ const action = keyAction(e, input.trim() === "", !!onEditLastMessage);
160
+ if (action === "send") {
161
+ e.preventDefault();
162
+ handleSubmit();
163
+ } else if (action === "editLast") {
164
+ e.preventDefault();
165
+ onEditLastMessage?.();
166
+ }
167
+ };
168
+ const handleFilesDropped = useCallback((files) => {
169
+ setShowAttachments(true);
170
+ setTimeout(() => {
171
+ attachmentUploadRef.current?.addFiles(files);
172
+ }, 0);
173
+ }, []);
174
+ const handlePaste = (e) => {
175
+ if (!attachmentsAvailable) return;
176
+ const files = Array.from(e.clipboardData?.files ?? []);
177
+ if (files.length === 0) return;
178
+ e.preventDefault();
179
+ handleFilesDropped(files);
180
+ };
181
+ const { isDragging, dragHandlers } = useDragDrop({ onFilesDropped: handleFilesDropped });
182
+ return /* @__PURE__ */ jsxs("div", {
183
+ className: cx("relative", !flush && "px-4 pb-6 pt-2 bg-(--theme-panel-bg)"),
184
+ ...attachmentsAvailable ? dragHandlers : {},
185
+ children: [isDragging && /* @__PURE__ */ jsx(DragOverlay, { className: "rounded-2xl" }), /* @__PURE__ */ jsxs("div", {
186
+ className: cx(!flush && "w-full max-w-[50rem] mx-auto px-4"),
187
+ children: [
188
+ showAttachments && attachmentsAvailable && /* @__PURE__ */ jsx("div", {
189
+ className: "mb-3 p-3 rounded-2xl border theme-border shadow-sm bg-(--theme-panel-bg)",
190
+ children: /* @__PURE__ */ jsx(AttachmentUpload, {
191
+ ref: attachmentUploadRef,
192
+ conversationId,
193
+ attachments,
194
+ onAttachmentsChange: setAttachments,
195
+ maxFiles: 10
196
+ })
197
+ }),
198
+ context && /* @__PURE__ */ jsx(ComposerContext, { children: context }),
199
+ /* @__PURE__ */ jsxs("div", {
200
+ className: "relative",
201
+ children: [slash && slashMenu.open && /* @__PURE__ */ jsx(SlashMenu, {
202
+ label: slash.label,
203
+ matches: slashMenu.matches,
204
+ active: slashMenu.active,
205
+ onChoose: (name) => {
206
+ slashMenu.choose(name);
207
+ textareaRef.current?.focus();
208
+ }
209
+ }), /* @__PURE__ */ jsxs("div", {
210
+ className: cx("relative flex items-end gap-2", "rounded-3xl border shadow-sm", "px-2 overflow-hidden", "transition-all duration-200", "focus-within:shadow-md", "theme-border", "bg-(--theme-panel-bg)"),
211
+ children: [attachmentsAvailable && /* @__PURE__ */ jsx(AttachButton, {
212
+ open: showAttachments,
213
+ disabled,
214
+ label: t.attachFiles,
215
+ onClick: () => setShowAttachments(!showAttachments)
216
+ }), /* @__PURE__ */ jsx("div", {
217
+ className: "flex-1 relative max-h-[200px] overflow-y-auto",
218
+ children: /* @__PURE__ */ jsxs("div", {
219
+ className: "flex items-end",
220
+ children: [/* @__PURE__ */ jsx("textarea", {
221
+ ref: textareaRef,
222
+ value: input,
223
+ onChange: (e) => setInputAndPersist(e.target.value),
224
+ onKeyDown: handleKeyDown,
225
+ onPaste: handlePaste,
226
+ placeholder: effectivePlaceholder,
227
+ "aria-label": inputLabel,
228
+ disabled,
229
+ rows: 1,
230
+ className: cx("theme-text", "flex-1 resize-none py-3.5 px-2 pr-3", "bg-transparent border-0", "placeholder:theme-muted-text", "focus:outline-none focus:ring-0", "disabled:opacity-50 disabled:cursor-not-allowed", "text-base leading-relaxed", "overflow-hidden")
231
+ }), /* @__PURE__ */ jsx("div", {
232
+ className: "flex-shrink-0 p-1.5 mr-1 sticky bottom-1 flex items-center",
233
+ children: isStreaming && !input.trim() ? /* @__PURE__ */ jsx("button", {
234
+ type: "button",
235
+ onClick: stopStreaming,
236
+ disabled: turn?.stopping,
237
+ className: cx("p-2.5 rounded-xl", "theme-muted-panel", "hover:theme-hover", "transition-colors"),
238
+ "aria-label": t.stopGenerating,
239
+ title: t.stopGenerating,
240
+ children: /* @__PURE__ */ jsx(StopSolidIcon, { className: "h-4 w-4" })
241
+ }) : /* @__PURE__ */ jsx(SendButton, {
242
+ onClick: handleSubmit,
243
+ enabled: canSend,
244
+ emphasised,
245
+ label: sendLabel ?? t.sendMessage,
246
+ testId: sendTestId
247
+ })
248
+ })]
249
+ })
250
+ })]
251
+ })]
252
+ }),
253
+ !flush && /* @__PURE__ */ jsx(ComposerSettings, {
254
+ left: /* @__PURE__ */ jsxs(Fragment$1, { children: [settingsLeft, uploadedCount > 0 && /* @__PURE__ */ jsx("span", {
255
+ className: "text-[11px] theme-muted-text",
256
+ children: t.filesAttached(uploadedCount)
257
+ })] }),
258
+ children: settingsRight
259
+ })
260
+ ]
261
+ })]
262
+ });
263
+ });
264
+ //#endregion
265
+ export { ChatInput as default };
@@ -0,0 +1,15 @@
1
+ import { KeyboardShortcutsProvider } from "../core/KeyboardShortcutsProvider.js";
2
+ import ChatSidebar from "./ChatSidebar.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ //#region src/ai/components/ChatLayout.tsx
5
+ function ChatLayout({ children, sidebarActions = true }) {
6
+ return /* @__PURE__ */ jsx(KeyboardShortcutsProvider, { children: /* @__PURE__ */ jsxs("div", {
7
+ className: "flex h-full",
8
+ children: [/* @__PURE__ */ jsx(ChatSidebar, { actions: sidebarActions }), /* @__PURE__ */ jsx("main", {
9
+ className: "flex-1 flex flex-col min-w-0",
10
+ children
11
+ })]
12
+ }) });
13
+ }
14
+ //#endregion
15
+ export { ChatLayout as default };
@@ -0,0 +1,338 @@
1
+ import { CheckIcon, ChevronRightIcon, CopyIcon, DownloadFileIcon, EditIcon, ErrorIcon, FileIcon, RefreshIcon, RetryIcon } from "../../icons.js";
2
+ import { durationToAbsHumanDuration } from "../../duration.js";
3
+ import { useChatConfig } from "../core/config.js";
4
+ import { formatFileSize } from "../utils.js";
5
+ import Markdown from "../ui/Markdown.js";
6
+ import { chatProseClasses } from "../ui/prose.js";
7
+ import AgentMessageParts from "./agent/AgentMessageParts.js";
8
+ import BranchNavigator from "./BranchNavigator.js";
9
+ import { Avatar } from "../ui/Avatar.js";
10
+ import cx from "classnames";
11
+ import { memo, useEffect, useRef, useState } from "react";
12
+ import { jsx, jsxs } from "react/jsx-runtime";
13
+ import { DateTime, Duration } from "luxon";
14
+ //#region src/ai/components/ChatMessage.tsx
15
+ function formatDuration(ms) {
16
+ if (!ms) return "";
17
+ return durationToAbsHumanDuration(Duration.fromMillis(ms));
18
+ }
19
+ var COLLAPSE_THRESHOLD = 680;
20
+ function parseTimestamp(iso) {
21
+ if (!iso) return null;
22
+ const dt = DateTime.fromISO(iso);
23
+ return dt.isValid ? dt : null;
24
+ }
25
+ function triggerDownload(url, filename) {
26
+ const a = document.createElement("a");
27
+ a.href = url;
28
+ a.download = filename;
29
+ document.body.appendChild(a);
30
+ a.click();
31
+ document.body.removeChild(a);
32
+ }
33
+ function ChatMessage({ message, allMessages, isStreaming = false, isEditing = false, showAuthor = false, currentUsername, attachmentDownloadUrl, onOpenAttachment, onNavigateBranch, onEdit, onSaveEdit, onCancelEdit, onRegenerate, onRetry, onOpenReasoning, onApprovalAnswer, flush }) {
34
+ const isUser = message.role === "user";
35
+ const isTool = message.role === "tool";
36
+ const isOtherUser = isUser && showAuthor && !!message.author && !!currentUsername && message.author.username !== currentUsername;
37
+ const isRightAligned = isUser && !isOtherUser;
38
+ const hasError = !!message.error;
39
+ const hasReasoning = !!message.reasoning;
40
+ const toolCallCount = message.parts?.filter((p) => p.kind === "tool_call").length ?? 0;
41
+ const showReasoningButton = !isUser && !isStreaming && hasReasoning;
42
+ const showTurnDurationLabel = !isUser && !isStreaming && !hasReasoning && (!!message.reasoningDuration || toolCallCount > 0);
43
+ const t = useChatConfig().strings.messageMeta;
44
+ const [copied, setCopied] = useState(false);
45
+ const [showActions, setShowActions] = useState(false);
46
+ const [editContent, setEditContent] = useState(message.content || "");
47
+ const textareaRef = useRef(null);
48
+ const contentRef = useRef(null);
49
+ const [collapsible, setCollapsible] = useState(false);
50
+ const [expanded, setExpanded] = useState(false);
51
+ useEffect(() => {
52
+ if (isUser || isStreaming || !contentRef.current) return;
53
+ const el = contentRef.current;
54
+ const measure = () => setCollapsible(el.scrollHeight > COLLAPSE_THRESHOLD);
55
+ measure();
56
+ if (typeof ResizeObserver === "undefined") return;
57
+ const ro = new ResizeObserver(measure);
58
+ ro.observe(el);
59
+ return () => ro.disconnect();
60
+ }, [isUser, isStreaming]);
61
+ useEffect(() => {
62
+ if (isEditing && textareaRef.current) {
63
+ textareaRef.current.focus();
64
+ textareaRef.current.select();
65
+ const maxHeight = 200;
66
+ textareaRef.current.style.height = "auto";
67
+ textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, maxHeight)}px`;
68
+ }
69
+ }, [isEditing]);
70
+ useEffect(() => {
71
+ if (!isEditing) setEditContent(message.content || "");
72
+ }, [isEditing, message.content]);
73
+ const handleSave = () => {
74
+ if (editContent.trim() && onSaveEdit) onSaveEdit(message.id, editContent.trim());
75
+ };
76
+ const handleKeyDown = (e) => {
77
+ if (e.key === "Enter" && !e.shiftKey) {
78
+ e.preventDefault();
79
+ handleSave();
80
+ } else if (e.key === "Escape") onCancelEdit?.();
81
+ };
82
+ const handleTextareaChange = (e) => {
83
+ setEditContent(e.target.value);
84
+ e.target.style.height = "auto";
85
+ const maxHeight = 200;
86
+ e.target.style.height = `${Math.min(e.target.scrollHeight, maxHeight)}px`;
87
+ };
88
+ const hasParts = !!message.parts?.length;
89
+ if (hasParts && !message.content) return /* @__PURE__ */ jsx("div", {
90
+ className: cx("group py-5 w-full", !flush && "px-4"),
91
+ children: /* @__PURE__ */ jsx(AgentMessageParts, {
92
+ message,
93
+ isStreaming,
94
+ onApprovalAnswer
95
+ })
96
+ });
97
+ if (isTool) return null;
98
+ if (!isUser && !message.content && message.toolCalls?.length) return null;
99
+ const timestamp = parseTimestamp(message.timestamp);
100
+ const totalTokens = message.tokensUsed?.total_tokens;
101
+ const collapsed = !isUser && collapsible && !expanded && !isStreaming;
102
+ const copyToClipboard = async () => {
103
+ if (message.content) {
104
+ await navigator.clipboard.writeText(message.content);
105
+ setCopied(true);
106
+ setTimeout(() => setCopied(false), 2e3);
107
+ }
108
+ };
109
+ return /* @__PURE__ */ jsx("div", {
110
+ role: "none",
111
+ className: cx("group py-5 w-full", !flush && "px-4"),
112
+ onMouseEnter: () => setShowActions(true),
113
+ onMouseLeave: () => setShowActions(false),
114
+ onFocus: () => setShowActions(true),
115
+ onBlur: () => setShowActions(false),
116
+ children: isEditing ? /* @__PURE__ */ jsxs("div", {
117
+ className: "w-full",
118
+ children: [/* @__PURE__ */ jsx("textarea", {
119
+ ref: textareaRef,
120
+ value: editContent,
121
+ onChange: handleTextareaChange,
122
+ onKeyDown: handleKeyDown,
123
+ className: "w-full min-h-[100px] max-h-[200px] overflow-y-auto p-4 rounded-xl theme-input resize-none focus:outline-none focus:ring-2 focus:ring-blue-500",
124
+ placeholder: t.editPlaceholder
125
+ }), /* @__PURE__ */ jsxs("div", {
126
+ className: "flex items-center justify-end gap-2 mt-3",
127
+ children: [/* @__PURE__ */ jsx("button", {
128
+ type: "button",
129
+ onClick: onCancelEdit,
130
+ className: "px-4 py-1.5 text-sm font-medium rounded-full transition-colors bg-(--theme-muted-panel-bg) text-(--theme-panel)",
131
+ children: t.cancel
132
+ }), /* @__PURE__ */ jsx("button", {
133
+ type: "button",
134
+ onClick: handleSave,
135
+ disabled: !editContent.trim(),
136
+ className: "px-4 py-1.5 text-sm font-medium rounded-full transition-colors disabled:opacity-50 disabled:cursor-not-allowed bg-(--theme-panel) text-(--theme-panel-bg)",
137
+ children: t.send
138
+ })]
139
+ })]
140
+ }) : /* @__PURE__ */ jsx("div", {
141
+ className: cx("flex gap-3 w-full", isRightAligned ? "flex-row-reverse" : "flex-row"),
142
+ children: /* @__PURE__ */ jsxs("div", {
143
+ className: cx("flex-1 min-w-0 space-y-1", isRightAligned && "relative flex flex-col items-end"),
144
+ children: [
145
+ showReasoningButton && /* @__PURE__ */ jsx("div", {
146
+ className: "mb-2",
147
+ children: /* @__PURE__ */ jsxs("button", {
148
+ type: "button",
149
+ onClick: () => onOpenReasoning?.(message.reasoning || "", message.reasoningDuration ?? null),
150
+ className: "flex items-center gap-x-1 text-sm theme-muted-text hover:theme-text transition-colors",
151
+ children: [/* @__PURE__ */ jsx("span", { children: message.reasoningDuration ? `Thought for ${formatDuration(message.reasoningDuration)}` : "Thought process" }), /* @__PURE__ */ jsx(ChevronRightIcon, { className: "w-3.5 h-3.5" })]
152
+ })
153
+ }),
154
+ showTurnDurationLabel && /* @__PURE__ */ jsx("div", {
155
+ className: "mb-2",
156
+ children: /* @__PURE__ */ jsx("div", {
157
+ className: "flex items-center gap-x-1.5 text-sm theme-muted-text",
158
+ children: /* @__PURE__ */ jsx("span", { children: message.reasoningDuration ? `Thought for ${formatDuration(message.reasoningDuration)}` : `Ran ${toolCallCount} tool ${toolCallCount === 1 ? "call" : "calls"}` })
159
+ })
160
+ }),
161
+ !isUser && hasParts && /* @__PURE__ */ jsx("div", {
162
+ className: "mb-1",
163
+ children: /* @__PURE__ */ jsx(AgentMessageParts, {
164
+ message,
165
+ isStreaming,
166
+ onApprovalAnswer
167
+ })
168
+ }),
169
+ isOtherUser && /* @__PURE__ */ jsx("span", {
170
+ className: "text-xs theme-muted-text",
171
+ children: message.author.name || message.author.username
172
+ }),
173
+ message.content ? isUser && isOtherUser ? /* @__PURE__ */ jsxs("div", {
174
+ className: "flex items-end gap-2",
175
+ children: [/* @__PURE__ */ jsx(Avatar, {
176
+ src: message.author.avatarUrl ?? void 0,
177
+ name: message.author.name || message.author.username,
178
+ size: "sm",
179
+ className: "flex-shrink-0 mb-0.5"
180
+ }), /* @__PURE__ */ jsx("div", {
181
+ 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)]",
182
+ children: message.content
183
+ })]
184
+ }) : isUser ? /* @__PURE__ */ jsx("div", {
185
+ className: "px-4 py-2.5 chat-ink rounded-2xl rounded-br-md max-w-[70%] inline-block whitespace-pre-wrap text-base leading-6 bg-[color-mix(in_oklab,var(--theme-panel)_6%,transparent)]",
186
+ children: message.content
187
+ }) : /* @__PURE__ */ jsx("div", {
188
+ ref: contentRef,
189
+ className: cx(chatProseClasses, collapsed && "chat-message-clamp"),
190
+ children: /* @__PURE__ */ jsx(Markdown, {
191
+ isStreaming,
192
+ children: message.content
193
+ })
194
+ }) : null,
195
+ !isUser && collapsible && !isStreaming && /* @__PURE__ */ jsx("button", {
196
+ type: "button",
197
+ onClick: () => setExpanded((e) => !e),
198
+ className: "text-xs font-medium theme-muted-text hover:theme-text transition-colors",
199
+ children: expanded ? t.showLess : t.showMore
200
+ }),
201
+ message.stopped && !isStreaming && /* @__PURE__ */ jsx("span", {
202
+ className: "inline-flex items-center mt-1 text-[11px] italic theme-muted-text",
203
+ children: t.stopped
204
+ }),
205
+ hasError && /* @__PURE__ */ jsxs("div", {
206
+ className: "flex items-center gap-2 mt-2 p-3 rounded-lg bg-red-500/10 border border-red-500/30",
207
+ children: [
208
+ /* @__PURE__ */ jsx(ErrorIcon, { className: "w-4 h-4 text-red-600 flex-shrink-0" }),
209
+ /* @__PURE__ */ jsx("span", {
210
+ className: "text-sm text-red-700 flex-1",
211
+ children: message.error
212
+ }),
213
+ onRetry && /* @__PURE__ */ jsxs("button", {
214
+ type: "button",
215
+ onClick: () => onRetry(message.id),
216
+ className: "flex items-center gap-1 px-2 py-1 text-xs font-medium text-red-700 hover:bg-red-500/15 rounded transition-colors",
217
+ children: [/* @__PURE__ */ jsx(RetryIcon, { className: "w-3 h-3" }), t.retry]
218
+ })
219
+ ]
220
+ }),
221
+ message.attachments && message.attachments.length > 0 && /* @__PURE__ */ jsx("div", {
222
+ className: "mt-3 flex flex-wrap gap-2",
223
+ children: message.attachments.map((att) => {
224
+ const isImage = att.contentType?.startsWith("image/");
225
+ const downloadUrl = att.id && attachmentDownloadUrl ? attachmentDownloadUrl(att.id) : void 0;
226
+ if (isImage && downloadUrl) return /* @__PURE__ */ jsx("div", {
227
+ className: "block rounded-lg overflow-hidden border theme-border",
228
+ ...onOpenAttachment ? {
229
+ onClick: () => onOpenAttachment(att),
230
+ role: "button",
231
+ style: { cursor: "pointer" }
232
+ } : {},
233
+ children: /* @__PURE__ */ jsx("img", {
234
+ src: downloadUrl,
235
+ alt: att.filename || "Image attachment",
236
+ className: "max-w-[200px] max-h-[200px] object-cover",
237
+ loading: "lazy"
238
+ })
239
+ }, att.id);
240
+ if (downloadUrl) return /* @__PURE__ */ jsxs("button", {
241
+ type: "button",
242
+ onClick: () => onOpenAttachment ? onOpenAttachment(att) : triggerDownload(downloadUrl, att.filename || "File"),
243
+ className: "inline-flex items-center gap-1.5 text-xs px-2.5 py-1.5 rounded-lg bg-(--theme-muted-panel-bg) hover:theme-hover transition-colors cursor-pointer",
244
+ children: [
245
+ /* @__PURE__ */ jsx(FileIcon, { className: "w-3.5 h-3.5 flex-shrink-0" }),
246
+ /* @__PURE__ */ jsx("span", {
247
+ className: "truncate max-w-[150px]",
248
+ children: att.filename || "File"
249
+ }),
250
+ att.size ? /* @__PURE__ */ jsxs("span", {
251
+ className: "theme-muted-text",
252
+ children: [
253
+ "(",
254
+ formatFileSize(att.size),
255
+ ")"
256
+ ]
257
+ }) : null,
258
+ /* @__PURE__ */ jsx(DownloadFileIcon, { className: "w-3 h-3 flex-shrink-0" })
259
+ ]
260
+ }, att.id);
261
+ return /* @__PURE__ */ jsxs("div", {
262
+ className: "inline-flex items-center gap-1.5 text-xs px-2.5 py-1.5 rounded-lg bg-(--theme-muted-panel-bg) theme-muted-text",
263
+ ...onOpenAttachment ? {
264
+ onClick: () => onOpenAttachment(att),
265
+ role: "button",
266
+ style: { cursor: "pointer" }
267
+ } : {},
268
+ children: [
269
+ /* @__PURE__ */ jsx(FileIcon, { className: "w-3.5 h-3.5 flex-shrink-0" }),
270
+ /* @__PURE__ */ jsx("span", {
271
+ className: "truncate max-w-[150px]",
272
+ children: att.filename || "File"
273
+ }),
274
+ att.size ? /* @__PURE__ */ jsxs("span", {
275
+ className: "theme-muted-text",
276
+ children: [
277
+ "(",
278
+ formatFileSize(att.size),
279
+ ")"
280
+ ]
281
+ }) : null
282
+ ]
283
+ }, att.id);
284
+ })
285
+ }),
286
+ !isStreaming && !isEditing && /* @__PURE__ */ jsxs("div", {
287
+ className: cx("chat-actions flex items-center gap-0.5 transition-opacity duration-150", isRightAligned ? "absolute right-0 top-full -mr-1.5 mt-0.5" : "-ml-1.5 mt-2", isRightAligned && (showActions ? "opacity-100" : "opacity-0 pointer-events-none")),
288
+ children: [
289
+ /* @__PURE__ */ jsx("button", {
290
+ type: "button",
291
+ onClick: copyToClipboard,
292
+ className: "p-1.5 rounded-lg hover:theme-hover theme-muted-text transition-colors",
293
+ title: t.copy,
294
+ children: copied ? /* @__PURE__ */ jsx(CheckIcon, { className: "w-4 h-4 text-green-500" }) : /* @__PURE__ */ jsx(CopyIcon, { className: "w-4 h-4" })
295
+ }),
296
+ isUser && !isOtherUser && onEdit && /* @__PURE__ */ jsx("button", {
297
+ type: "button",
298
+ onClick: () => onEdit(message.id),
299
+ className: "p-1.5 rounded-lg hover:theme-hover theme-muted-text transition-colors",
300
+ title: t.edit,
301
+ children: /* @__PURE__ */ jsx(EditIcon, { className: "w-4 h-4" })
302
+ }),
303
+ !isUser && onRegenerate && /* @__PURE__ */ jsx("button", {
304
+ type: "button",
305
+ onClick: () => onRegenerate(message.id),
306
+ className: "p-1.5 rounded-lg hover:theme-hover theme-muted-text transition-colors",
307
+ title: t.regenerate,
308
+ children: /* @__PURE__ */ jsx(RefreshIcon, { className: "w-4 h-4" })
309
+ }),
310
+ !isUser && onNavigateBranch && /* @__PURE__ */ jsx(BranchNavigator, {
311
+ messages: allMessages,
312
+ currentMessageId: message.id,
313
+ onNavigate: onNavigateBranch
314
+ }),
315
+ (timestamp || !isUser && (message.model || totalTokens)) && /* @__PURE__ */ jsxs("span", {
316
+ className: cx("chat-meta flex items-center gap-2 ml-2 text-[11px] theme-muted-text transition-opacity duration-150 select-none", showActions ? "opacity-100" : "opacity-0"),
317
+ children: [
318
+ timestamp && /* @__PURE__ */ jsx("span", {
319
+ title: timestamp.toLocaleString(DateTime.DATETIME_MED_WITH_SECONDS),
320
+ children: timestamp.toRelative()
321
+ }),
322
+ !isUser && message.model && /* @__PURE__ */ jsx("span", { children: message.model }),
323
+ !isUser && totalTokens ? /* @__PURE__ */ jsx("span", {
324
+ title: message.tokensUsed?.prompt_tokens !== void 0 && message.tokensUsed?.completion_tokens !== void 0 ? t.tokensBreakdown(message.tokensUsed.prompt_tokens, message.tokensUsed.completion_tokens) : void 0,
325
+ children: t.tokens(totalTokens)
326
+ }) : null
327
+ ]
328
+ })
329
+ ]
330
+ })
331
+ ]
332
+ })
333
+ })
334
+ });
335
+ }
336
+ var ChatMessage_default = memo(ChatMessage);
337
+ //#endregion
338
+ export { ChatMessage_default as default };