@parallelworks/ui 0.19.0 → 0.20.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 (233) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunks/memoryStore-CC91S5LP.js → ai/adapter/memoryStore.js} +2 -2
  3. package/dist/ai/adapter/mock/index.js +2 -80
  4. package/dist/ai/adapter/mock/mockAdapter.js +81 -0
  5. package/dist/ai/adapter/openai/createOpenAIStream.js +33 -0
  6. package/dist/ai/adapter/openai/errors.js +37 -0
  7. package/dist/ai/adapter/openai/index.js +5 -333
  8. package/dist/ai/adapter/openai/openaiAdapter.js +54 -0
  9. package/dist/ai/adapter/openai/responses.js +108 -0
  10. package/dist/ai/adapter/openai/sse.js +112 -0
  11. package/dist/{chunks/types-Yy0NrerF.js → ai/adapter/types.js} +1 -1
  12. package/dist/ai/agent/classifier.js +12 -0
  13. package/dist/ai/agent/diff.js +223 -0
  14. package/dist/ai/agent/diffHighlight.js +101 -0
  15. package/dist/ai/agent/permissions.js +29 -0
  16. package/dist/ai/agent/toolfmt.js +383 -0
  17. package/dist/ai/agent/transcript.js +127 -0
  18. package/dist/ai/components/AllocationSelector.js +56 -0
  19. package/dist/ai/components/AttachmentManager.js +147 -0
  20. package/dist/ai/components/AttachmentUpload.js +179 -0
  21. package/dist/ai/components/BlockedGroupBanner.js +29 -0
  22. package/dist/ai/components/BranchNavigator.js +57 -0
  23. package/dist/ai/components/ChatEmptyState.js +171 -0
  24. package/dist/ai/components/ChatInput.js +265 -0
  25. package/dist/ai/components/ChatLayout.js +15 -0
  26. package/dist/ai/components/ChatMessage.js +341 -0
  27. package/dist/ai/components/ChatMessageList.js +301 -0
  28. package/dist/ai/components/ChatSidebar.js +345 -0
  29. package/dist/ai/components/ChatThread.js +198 -0
  30. package/dist/ai/components/ComposerChrome.js +50 -0
  31. package/dist/ai/components/ComposerFrame.js +33 -0
  32. package/dist/ai/components/DragOverlay.js +20 -0
  33. package/dist/ai/components/ModelSelector.js +343 -0
  34. package/dist/ai/components/ShareDialog.js +260 -0
  35. package/dist/ai/components/SlashMenu.js +91 -0
  36. package/dist/ai/components/agent/ActivityLine.js +151 -0
  37. package/dist/ai/components/agent/AgentMessageParts.js +721 -0
  38. package/dist/ai/components/agent/PermissionPicker.js +38 -0
  39. package/dist/ai/components/attachmentKeys.js +10 -0
  40. package/dist/ai/core/ChatProvider.js +848 -0
  41. package/dist/ai/core/KeyboardShortcutsProvider.js +9 -0
  42. package/dist/ai/core/chatReducer.js +223 -0
  43. package/dist/ai/core/config.js +46 -0
  44. package/dist/ai/core/events.js +4 -0
  45. package/dist/ai/core/exportConversation.js +42 -0
  46. package/dist/ai/core/greeting.js +38 -0
  47. package/dist/ai/core/parts.js +84 -0
  48. package/dist/ai/core/useDragDrop.js +36 -0
  49. package/dist/ai/core/useKeyboardShortcuts.js +77 -0
  50. package/dist/ai/core/useSSEStream.js +98 -0
  51. package/dist/ai/index.js +43 -6644
  52. package/dist/ai/strings.js +175 -0
  53. package/dist/ai/ui/Avatar.js +35 -0
  54. package/dist/ai/ui/Dropdown.js +110 -0
  55. package/dist/ai/ui/Markdown.js +79 -0
  56. package/dist/ai/ui/Notice.js +77 -0
  57. package/dist/ai/ui/codeBreaks.js +36 -0
  58. package/dist/ai/ui/prose.js +13 -0
  59. package/dist/ai/ui/sidebar.js +148 -0
  60. package/dist/ai/utils.js +23 -0
  61. package/dist/assets/editor.worker-DfmPPyOL.js +60 -0
  62. package/dist/assets/json.worker-Dqgjpq9F.js +92 -0
  63. package/dist/assets/yaml.worker-DLx0whrM.js +1441 -0
  64. package/dist/components/AddMenu.js +46 -0
  65. package/dist/components/Avatar.js +46 -0
  66. package/dist/{chunks/BareModal-BtRiQezg.js → components/BareModal.js} +1 -1
  67. package/dist/components/Breadcrumbs.js +83 -0
  68. package/dist/components/Button.js +83 -0
  69. package/dist/components/Callout.js +23 -0
  70. package/dist/components/Card.js +31 -0
  71. package/dist/components/CollapsiblePanel.js +77 -0
  72. package/dist/{chunks/ConfirmModal-aOR4O1QE.js → components/ConfirmModal.js} +4 -9
  73. package/dist/components/CopyToClipboard.js +81 -0
  74. package/dist/{chunks/Markdown-CM-FenNE.js → components/CreateModal.js} +9 -116
  75. package/dist/components/DescriptionList.js +42 -0
  76. package/dist/components/Dropdown.js +349 -0
  77. package/dist/components/EmptyState.js +27 -0
  78. package/dist/components/Filter.js +32 -0
  79. package/dist/components/FilterPill.js +13 -0
  80. package/dist/components/IconButton.js +34 -0
  81. package/dist/{chunks/Indicator-hBSIxOPK.js → components/Indicator.js} +1 -1
  82. package/dist/components/Input.js +88 -0
  83. package/dist/components/LetterBadge.js +13 -0
  84. package/dist/components/Listbox.js +123 -0
  85. package/dist/{chunks/Loader-BXoXTZGc.js → components/Loader.js} +1 -1
  86. package/dist/components/Markdown.js +15 -0
  87. package/dist/components/MultiSelect.js +171 -0
  88. package/dist/components/Overview.js +39 -0
  89. package/dist/components/PageHeader.js +194 -0
  90. package/dist/components/Pagination.js +35 -0
  91. package/dist/{chunks/Provider-CDYurraE.js → components/Provider.js} +13 -5
  92. package/dist/{chunks/RequiredMark-DfLdYbYZ.js → components/RequiredMark.js} +1 -1
  93. package/dist/components/SectionHeader.js +11 -0
  94. package/dist/components/SettingModeOption.js +30 -0
  95. package/dist/components/SettingRow.js +35 -0
  96. package/dist/components/SettingsCard.js +31 -0
  97. package/dist/components/SettingsGroup.js +17 -0
  98. package/dist/components/Sort.js +42 -0
  99. package/dist/components/StatusBadge.js +63 -0
  100. package/dist/components/SwitchToggle.js +59 -0
  101. package/dist/{chunks/SwitchToggleSmall-FcsaYSTs.js → components/SwitchToggleSmall.js} +1 -1
  102. package/dist/{chunks/Table-DU2MW8xz.js → components/Table.js} +3 -3
  103. package/dist/components/Toggle.js +20 -0
  104. package/dist/{chunks/Tooltip-K5HJr4_p.js → components/Tooltip.js} +2 -27
  105. package/dist/{chunks/StatusBadge-C1Blx_cW.js → components/dropdownUtils.js} +2 -63
  106. package/dist/{chunks/focus-D_iZJTW1.js → components/focus.js} +1 -1
  107. package/dist/components/ghostButton.js +7 -0
  108. package/dist/components/inputClasses.js +4 -0
  109. package/dist/components/keys.js +27 -0
  110. package/dist/components/relativeTime.js +32 -0
  111. package/dist/{chunks/useCssIsDark-DHr05dG2.js → components/useCssIsDark.js} +1 -1
  112. package/dist/{chunks/duration-BOv8Phl8.js → duration.js} +1 -1
  113. package/dist/editor/Editor.js +13 -0
  114. package/dist/editor/EditorField.js +53 -0
  115. package/dist/editor/FormCodePanel.js +173 -0
  116. package/dist/{chunks/Monaco-DJ8MW-Dq.js → editor/Monaco.js} +5 -26
  117. package/dist/editor/index.js +6 -1
  118. package/dist/editor/themes.js +91 -0
  119. package/dist/editor/workers.d.ts +5 -0
  120. package/dist/editor/workers.js +35 -0
  121. package/dist/editor/yaml.d.ts +9 -1
  122. package/dist/editor/yaml.js +15 -0
  123. package/dist/editor/yaml.worker.d.ts +1 -0
  124. package/dist/editor/yamlLanguage.d.ts +6 -0
  125. package/dist/editor/yamlLanguage.js +312 -0
  126. package/dist/file-explorer/FileExplorer.js +1405 -0
  127. package/dist/file-explorer/FileExplorerContext.js +370 -0
  128. package/dist/{chunks/CodePreviewEditor-YqQBz1YL.js → file-explorer/FilePreview/CodePreviewEditor.js} +3 -2
  129. package/dist/file-explorer/FilePreview/Inline.js +45 -0
  130. package/dist/file-explorer/FilePreview/StreamedBadge.js +16 -0
  131. package/dist/file-explorer/FilePreview/Viewer.js +526 -0
  132. package/dist/file-explorer/FilePreview/index.js +266 -0
  133. package/dist/file-explorer/FilePreview/parse.js +63 -0
  134. package/dist/file-explorer/FilePreview/previewCache.js +98 -0
  135. package/dist/file-explorer/FilePreview/previewType.js +151 -0
  136. package/dist/file-explorer/FilePreview/readCappedBytes.js +196 -0
  137. package/dist/file-explorer/FilePreview/useObjectBlobUrl.js +95 -0
  138. package/dist/file-explorer/FilePreview/useObjectText.js +109 -0
  139. package/dist/file-explorer/FilePreview/usePresignedUrl.js +73 -0
  140. package/dist/file-explorer/FileRow.js +165 -0
  141. package/dist/file-explorer/FileRowsWindow.js +92 -0
  142. package/dist/file-explorer/FileTable.js +172 -0
  143. package/dist/file-explorer/FileTableHeaders.js +49 -0
  144. package/dist/file-explorer/LoadMoreRow.js +46 -0
  145. package/dist/file-explorer/TreeView.js +376 -0
  146. package/dist/file-explorer/helpers.js +537 -0
  147. package/dist/file-explorer/index.js +7 -5671
  148. package/dist/file-explorer/lib/batch.js +39 -0
  149. package/dist/file-explorer/lib/errors.js +9 -0
  150. package/dist/file-explorer/lib/paging.js +124 -0
  151. package/dist/file-explorer/lib/utils.js +322 -0
  152. package/dist/file-explorer/toasts.js +400 -0
  153. package/dist/file-explorer/treeRows.js +73 -0
  154. package/dist/file-explorer/useNearEnd.js +27 -0
  155. package/dist/form/FieldWrapper.js +54 -0
  156. package/dist/form/Fields/BooleanField.js +51 -0
  157. package/dist/form/Fields/CheckboxGroupField.js +72 -0
  158. package/dist/form/Fields/ColorField.js +33 -0
  159. package/dist/form/Fields/DropdownField.js +49 -0
  160. package/dist/form/Fields/DurationField.js +130 -0
  161. package/dist/form/Fields/LabelField.js +19 -0
  162. package/dist/form/Fields/MultiDropdownField.js +40 -0
  163. package/dist/form/Fields/NumberField.js +51 -0
  164. package/dist/form/Fields/ObjectField.js +54 -0
  165. package/dist/form/Fields/RadioField.js +49 -0
  166. package/dist/form/Fields/RangeField.js +58 -0
  167. package/dist/form/Fields/ResourceOptionIcon.js +21 -0
  168. package/dist/form/Fields/StepField.js +6 -0
  169. package/dist/form/Fields/StringField.js +37 -0
  170. package/dist/form/Fields/TextAreaField.js +31 -0
  171. package/dist/form/Fields/durationValidity.js +11 -0
  172. package/dist/form/Form.js +643 -0
  173. package/dist/form/SpecialTypes/DropdownWrapper.js +30 -0
  174. package/dist/form/SpecialTypes/FormikCustomDropdown.js +122 -0
  175. package/dist/form/SpecialTypes/FormikCustomInput.js +111 -0
  176. package/dist/form/SpecialTypes/FormikMultiSelectDropdown.js +31 -0
  177. package/dist/form/SpecialTypes/FormikNumericInput.js +60 -0
  178. package/dist/form/Wizard/WizardContainer.js +138 -0
  179. package/dist/form/Wizard/WizardNavigation.js +92 -0
  180. package/dist/form/Wizard/WizardStepContent.js +35 -0
  181. package/dist/form/Wizard/WizardStepIndicator.js +108 -0
  182. package/dist/form/Wizard/useWizardState.js +118 -0
  183. package/dist/form/Wizard/utils.js +34 -0
  184. package/dist/form/fieldContext.js +26 -0
  185. package/dist/form/fieldRegistry.js +50 -0
  186. package/dist/form/index.js +17 -2321
  187. package/dist/{chunks/FieldWrapper-Cszwg-4B.js → form/lib.js} +2 -97
  188. package/dist/form/useParsedOpts.js +62 -0
  189. package/dist/form/usePrevious.js +11 -0
  190. package/dist/form/utils/checkShowIfObject.js +14 -0
  191. package/dist/form/utils/duration.js +69 -0
  192. package/dist/form/utils/getParentValue.js +23 -0
  193. package/dist/form/utils/getPlaceholder.js +9 -0
  194. package/dist/form/utils/getValueUsingPath.js +23 -0
  195. package/dist/form/utils/secondaryField.js +22 -0
  196. package/dist/graph/AnnotationBanner.js +141 -0
  197. package/dist/graph/Collapse.js +41 -0
  198. package/dist/graph/DependencyGraph.js +1398 -0
  199. package/dist/graph/JobSummary.js +104 -0
  200. package/dist/graph/MatrixGroup.js +107 -0
  201. package/dist/graph/Reveal.js +57 -0
  202. package/dist/graph/TreeView.js +711 -0
  203. package/dist/graph/index.js +1 -2519
  204. package/dist/graph/types.js +6 -0
  205. package/dist/icons.d.ts +4 -4
  206. package/dist/icons.js +5 -5
  207. package/dist/index.js +46 -599
  208. package/dist/list/ActionToolbar.js +184 -0
  209. package/dist/list/HoverCard.js +73 -0
  210. package/dist/list/ListPage.js +145 -0
  211. package/dist/list/ListPager.js +59 -0
  212. package/dist/list/ListTable.js +163 -0
  213. package/dist/list/ListViewControls.js +372 -0
  214. package/dist/list/RowContextMenu.js +351 -0
  215. package/dist/list/UserHoverCard.js +117 -0
  216. package/dist/list/index.js +11 -2
  217. package/dist/list/listSearch.js +137 -0
  218. package/dist/list/listView.js +210 -0
  219. package/dist/list/textEntry.js +10 -0
  220. package/dist/{chunks/logviewer-BTKuQWzw.js → logviewer/LogViewer.js} +4 -82
  221. package/dist/logviewer/index.js +2 -1
  222. package/dist/logviewer/segments.js +80 -0
  223. package/dist/safeUrl.d.ts +6 -0
  224. package/dist/safeUrl.js +22 -0
  225. package/package.json +7 -9
  226. package/dist/assets/editor.worker-BmHUIVG4.js +0 -14807
  227. package/dist/assets/json.worker-D8bJ92nw.js +0 -22207
  228. package/dist/assets/yaml.worker-C4gyGAko.js +0 -50201
  229. package/dist/chunks/PageHeader-0kG_iypP.js +0 -692
  230. package/dist/chunks/SwitchToggle-DShNdcao.js +0 -758
  231. package/dist/chunks/editor-Dk1rbV75.js +0 -361
  232. package/dist/chunks/list-CJm875m7.js +0 -1418
  233. /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,341 @@
1
+ import { safeUrl } from "../../safeUrl.js";
2
+ import { CheckIcon, ChevronRightIcon, CopyIcon, DownloadFileIcon, EditIcon, ErrorIcon, FileIcon, RefreshIcon, RetryIcon } from "../../icons.js";
3
+ import { durationToAbsHumanDuration } from "../../duration.js";
4
+ import { useChatConfig } from "../core/config.js";
5
+ import { formatFileSize } from "../utils.js";
6
+ import Markdown from "../ui/Markdown.js";
7
+ import { chatProseClasses } from "../ui/prose.js";
8
+ import AgentMessageParts from "./agent/AgentMessageParts.js";
9
+ import BranchNavigator from "./BranchNavigator.js";
10
+ import { Avatar } from "../ui/Avatar.js";
11
+ import cx from "classnames";
12
+ import { memo, useEffect, useRef, useState } from "react";
13
+ import { jsx, jsxs } from "react/jsx-runtime";
14
+ import { DateTime, Duration } from "luxon";
15
+ //#region src/ai/components/ChatMessage.tsx
16
+ function formatDuration(ms) {
17
+ if (!ms) return "";
18
+ return durationToAbsHumanDuration(Duration.fromMillis(ms));
19
+ }
20
+ var COLLAPSE_THRESHOLD = 680;
21
+ function parseTimestamp(iso) {
22
+ if (!iso) return null;
23
+ const dt = DateTime.fromISO(iso);
24
+ return dt.isValid ? dt : null;
25
+ }
26
+ function triggerDownload(url, filename) {
27
+ const href = safeUrl(url);
28
+ if (!href) return;
29
+ const a = document.createElement("a");
30
+ a.href = href;
31
+ a.download = filename;
32
+ document.body.appendChild(a);
33
+ a.click();
34
+ document.body.removeChild(a);
35
+ }
36
+ function ChatMessage({ message, allMessages, isStreaming = false, isEditing = false, showAuthor = false, currentUsername, attachmentDownloadUrl, onOpenAttachment, onNavigateBranch, onEdit, onSaveEdit, onCancelEdit, onRegenerate, onRetry, onOpenReasoning, onApprovalAnswer, flush }) {
37
+ const isUser = message.role === "user";
38
+ const isTool = message.role === "tool";
39
+ const isOtherUser = isUser && showAuthor && !!message.author && !!currentUsername && message.author.username !== currentUsername;
40
+ const isRightAligned = isUser && !isOtherUser;
41
+ const hasError = !!message.error;
42
+ const hasReasoning = !!message.reasoning;
43
+ const toolCallCount = message.parts?.filter((p) => p.kind === "tool_call").length ?? 0;
44
+ const showReasoningButton = !isUser && !isStreaming && hasReasoning;
45
+ const showTurnDurationLabel = !isUser && !isStreaming && !hasReasoning && (!!message.reasoningDuration || toolCallCount > 0);
46
+ const t = useChatConfig().strings.messageMeta;
47
+ const [copied, setCopied] = useState(false);
48
+ const [showActions, setShowActions] = useState(false);
49
+ const [editContent, setEditContent] = useState(message.content || "");
50
+ const textareaRef = useRef(null);
51
+ const contentRef = useRef(null);
52
+ const [collapsible, setCollapsible] = useState(false);
53
+ const [expanded, setExpanded] = useState(false);
54
+ useEffect(() => {
55
+ if (isUser || isStreaming || !contentRef.current) return;
56
+ const el = contentRef.current;
57
+ const measure = () => setCollapsible(el.scrollHeight > COLLAPSE_THRESHOLD);
58
+ measure();
59
+ if (typeof ResizeObserver === "undefined") return;
60
+ const ro = new ResizeObserver(measure);
61
+ ro.observe(el);
62
+ return () => ro.disconnect();
63
+ }, [isUser, isStreaming]);
64
+ useEffect(() => {
65
+ if (isEditing && textareaRef.current) {
66
+ textareaRef.current.focus();
67
+ textareaRef.current.select();
68
+ const maxHeight = 200;
69
+ textareaRef.current.style.height = "auto";
70
+ textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, maxHeight)}px`;
71
+ }
72
+ }, [isEditing]);
73
+ useEffect(() => {
74
+ if (!isEditing) setEditContent(message.content || "");
75
+ }, [isEditing, message.content]);
76
+ const handleSave = () => {
77
+ if (editContent.trim() && onSaveEdit) onSaveEdit(message.id, editContent.trim());
78
+ };
79
+ const handleKeyDown = (e) => {
80
+ if (e.key === "Enter" && !e.shiftKey) {
81
+ e.preventDefault();
82
+ handleSave();
83
+ } else if (e.key === "Escape") onCancelEdit?.();
84
+ };
85
+ const handleTextareaChange = (e) => {
86
+ setEditContent(e.target.value);
87
+ e.target.style.height = "auto";
88
+ const maxHeight = 200;
89
+ e.target.style.height = `${Math.min(e.target.scrollHeight, maxHeight)}px`;
90
+ };
91
+ const hasParts = !!message.parts?.length;
92
+ if (hasParts && !message.content) return /* @__PURE__ */ jsx("div", {
93
+ className: cx("group py-5 w-full", !flush && "px-4"),
94
+ children: /* @__PURE__ */ jsx(AgentMessageParts, {
95
+ message,
96
+ isStreaming,
97
+ onApprovalAnswer
98
+ })
99
+ });
100
+ if (isTool) return null;
101
+ if (!isUser && !message.content && message.toolCalls?.length) return null;
102
+ const timestamp = parseTimestamp(message.timestamp);
103
+ const totalTokens = message.tokensUsed?.total_tokens;
104
+ const collapsed = !isUser && collapsible && !expanded && !isStreaming;
105
+ const copyToClipboard = async () => {
106
+ if (message.content) {
107
+ await navigator.clipboard.writeText(message.content);
108
+ setCopied(true);
109
+ setTimeout(() => setCopied(false), 2e3);
110
+ }
111
+ };
112
+ return /* @__PURE__ */ jsx("div", {
113
+ role: "none",
114
+ className: cx("group py-5 w-full", !flush && "px-4"),
115
+ onMouseEnter: () => setShowActions(true),
116
+ onMouseLeave: () => setShowActions(false),
117
+ onFocus: () => setShowActions(true),
118
+ onBlur: () => setShowActions(false),
119
+ children: isEditing ? /* @__PURE__ */ jsxs("div", {
120
+ className: "w-full",
121
+ children: [/* @__PURE__ */ jsx("textarea", {
122
+ ref: textareaRef,
123
+ value: editContent,
124
+ onChange: handleTextareaChange,
125
+ onKeyDown: handleKeyDown,
126
+ 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",
127
+ placeholder: t.editPlaceholder
128
+ }), /* @__PURE__ */ jsxs("div", {
129
+ className: "flex items-center justify-end gap-2 mt-3",
130
+ children: [/* @__PURE__ */ jsx("button", {
131
+ type: "button",
132
+ onClick: onCancelEdit,
133
+ className: "px-4 py-1.5 text-sm font-medium rounded-full transition-colors bg-(--theme-muted-panel-bg) text-(--theme-panel)",
134
+ children: t.cancel
135
+ }), /* @__PURE__ */ jsx("button", {
136
+ type: "button",
137
+ onClick: handleSave,
138
+ disabled: !editContent.trim(),
139
+ 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)",
140
+ children: t.send
141
+ })]
142
+ })]
143
+ }) : /* @__PURE__ */ jsx("div", {
144
+ className: cx("flex gap-3 w-full", isRightAligned ? "flex-row-reverse" : "flex-row"),
145
+ children: /* @__PURE__ */ jsxs("div", {
146
+ className: cx("flex-1 min-w-0 space-y-1", isRightAligned && "relative flex flex-col items-end"),
147
+ children: [
148
+ showReasoningButton && /* @__PURE__ */ jsx("div", {
149
+ className: "mb-2",
150
+ children: /* @__PURE__ */ jsxs("button", {
151
+ type: "button",
152
+ onClick: () => onOpenReasoning?.(message.reasoning || "", message.reasoningDuration ?? null),
153
+ className: "flex items-center gap-x-1 text-sm theme-muted-text hover:theme-text transition-colors",
154
+ children: [/* @__PURE__ */ jsx("span", { children: message.reasoningDuration ? `Thought for ${formatDuration(message.reasoningDuration)}` : "Thought process" }), /* @__PURE__ */ jsx(ChevronRightIcon, { className: "w-3.5 h-3.5" })]
155
+ })
156
+ }),
157
+ showTurnDurationLabel && /* @__PURE__ */ jsx("div", {
158
+ className: "mb-2",
159
+ children: /* @__PURE__ */ jsx("div", {
160
+ className: "flex items-center gap-x-1.5 text-sm theme-muted-text",
161
+ children: /* @__PURE__ */ jsx("span", { children: message.reasoningDuration ? `Thought for ${formatDuration(message.reasoningDuration)}` : `Ran ${toolCallCount} tool ${toolCallCount === 1 ? "call" : "calls"}` })
162
+ })
163
+ }),
164
+ !isUser && hasParts && /* @__PURE__ */ jsx("div", {
165
+ className: "mb-1",
166
+ children: /* @__PURE__ */ jsx(AgentMessageParts, {
167
+ message,
168
+ isStreaming,
169
+ onApprovalAnswer
170
+ })
171
+ }),
172
+ isOtherUser && /* @__PURE__ */ jsx("span", {
173
+ className: "text-xs theme-muted-text",
174
+ children: message.author.name || message.author.username
175
+ }),
176
+ message.content ? isUser && isOtherUser ? /* @__PURE__ */ jsxs("div", {
177
+ className: "flex items-end gap-2",
178
+ children: [/* @__PURE__ */ jsx(Avatar, {
179
+ src: message.author.avatarUrl ?? void 0,
180
+ name: message.author.name || message.author.username,
181
+ size: "sm",
182
+ className: "flex-shrink-0 mb-0.5"
183
+ }), /* @__PURE__ */ jsx("div", {
184
+ className: "px-4 py-2.5 chat-ink rounded-2xl rounded-bl-md max-w-[70%] inline-block whitespace-pre-wrap text-base leading-6 bg-[color-mix(in_oklab,var(--theme-panel)_6%,transparent)]",
185
+ children: message.content
186
+ })]
187
+ }) : isUser ? /* @__PURE__ */ jsx("div", {
188
+ 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)]",
189
+ children: message.content
190
+ }) : /* @__PURE__ */ jsx("div", {
191
+ ref: contentRef,
192
+ className: cx(chatProseClasses, collapsed && "chat-message-clamp"),
193
+ children: /* @__PURE__ */ jsx(Markdown, {
194
+ isStreaming,
195
+ children: message.content
196
+ })
197
+ }) : null,
198
+ !isUser && collapsible && !isStreaming && /* @__PURE__ */ jsx("button", {
199
+ type: "button",
200
+ onClick: () => setExpanded((e) => !e),
201
+ className: "text-xs font-medium theme-muted-text hover:theme-text transition-colors",
202
+ children: expanded ? t.showLess : t.showMore
203
+ }),
204
+ message.stopped && !isStreaming && /* @__PURE__ */ jsx("span", {
205
+ className: "inline-flex items-center mt-1 text-[11px] italic theme-muted-text",
206
+ children: t.stopped
207
+ }),
208
+ hasError && /* @__PURE__ */ jsxs("div", {
209
+ className: "flex items-center gap-2 mt-2 p-3 rounded-lg bg-red-500/10 border border-red-500/30",
210
+ children: [
211
+ /* @__PURE__ */ jsx(ErrorIcon, { className: "w-4 h-4 text-red-600 flex-shrink-0" }),
212
+ /* @__PURE__ */ jsx("span", {
213
+ className: "text-sm text-red-700 flex-1",
214
+ children: message.error
215
+ }),
216
+ onRetry && /* @__PURE__ */ jsxs("button", {
217
+ type: "button",
218
+ onClick: () => onRetry(message.id),
219
+ 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",
220
+ children: [/* @__PURE__ */ jsx(RetryIcon, { className: "w-3 h-3" }), t.retry]
221
+ })
222
+ ]
223
+ }),
224
+ message.attachments && message.attachments.length > 0 && /* @__PURE__ */ jsx("div", {
225
+ className: "mt-3 flex flex-wrap gap-2",
226
+ children: message.attachments.map((att) => {
227
+ const isImage = att.contentType?.startsWith("image/");
228
+ const downloadUrl = att.id && attachmentDownloadUrl ? attachmentDownloadUrl(att.id) : void 0;
229
+ if (isImage && downloadUrl) return /* @__PURE__ */ jsx("div", {
230
+ className: "block rounded-lg overflow-hidden border theme-border",
231
+ ...onOpenAttachment ? {
232
+ onClick: () => onOpenAttachment(att),
233
+ role: "button",
234
+ style: { cursor: "pointer" }
235
+ } : {},
236
+ children: /* @__PURE__ */ jsx("img", {
237
+ src: downloadUrl,
238
+ alt: att.filename || "Image attachment",
239
+ className: "max-w-[200px] max-h-[200px] object-cover",
240
+ loading: "lazy"
241
+ })
242
+ }, att.id);
243
+ if (downloadUrl) return /* @__PURE__ */ jsxs("button", {
244
+ type: "button",
245
+ onClick: () => onOpenAttachment ? onOpenAttachment(att) : triggerDownload(downloadUrl, att.filename || "File"),
246
+ 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",
247
+ children: [
248
+ /* @__PURE__ */ jsx(FileIcon, { className: "w-3.5 h-3.5 flex-shrink-0" }),
249
+ /* @__PURE__ */ jsx("span", {
250
+ className: "truncate max-w-[150px]",
251
+ children: att.filename || "File"
252
+ }),
253
+ att.size ? /* @__PURE__ */ jsxs("span", {
254
+ className: "theme-muted-text",
255
+ children: [
256
+ "(",
257
+ formatFileSize(att.size),
258
+ ")"
259
+ ]
260
+ }) : null,
261
+ /* @__PURE__ */ jsx(DownloadFileIcon, { className: "w-3 h-3 flex-shrink-0" })
262
+ ]
263
+ }, att.id);
264
+ return /* @__PURE__ */ jsxs("div", {
265
+ 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",
266
+ ...onOpenAttachment ? {
267
+ onClick: () => onOpenAttachment(att),
268
+ role: "button",
269
+ style: { cursor: "pointer" }
270
+ } : {},
271
+ children: [
272
+ /* @__PURE__ */ jsx(FileIcon, { className: "w-3.5 h-3.5 flex-shrink-0" }),
273
+ /* @__PURE__ */ jsx("span", {
274
+ className: "truncate max-w-[150px]",
275
+ children: att.filename || "File"
276
+ }),
277
+ att.size ? /* @__PURE__ */ jsxs("span", {
278
+ className: "theme-muted-text",
279
+ children: [
280
+ "(",
281
+ formatFileSize(att.size),
282
+ ")"
283
+ ]
284
+ }) : null
285
+ ]
286
+ }, att.id);
287
+ })
288
+ }),
289
+ !isStreaming && !isEditing && /* @__PURE__ */ jsxs("div", {
290
+ 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")),
291
+ children: [
292
+ /* @__PURE__ */ jsx("button", {
293
+ type: "button",
294
+ onClick: copyToClipboard,
295
+ className: "p-1.5 rounded-lg hover:theme-hover theme-muted-text transition-colors",
296
+ title: t.copy,
297
+ children: copied ? /* @__PURE__ */ jsx(CheckIcon, { className: "w-4 h-4 text-green-500" }) : /* @__PURE__ */ jsx(CopyIcon, { className: "w-4 h-4" })
298
+ }),
299
+ isUser && !isOtherUser && onEdit && /* @__PURE__ */ jsx("button", {
300
+ type: "button",
301
+ onClick: () => onEdit(message.id),
302
+ className: "p-1.5 rounded-lg hover:theme-hover theme-muted-text transition-colors",
303
+ title: t.edit,
304
+ children: /* @__PURE__ */ jsx(EditIcon, { className: "w-4 h-4" })
305
+ }),
306
+ !isUser && onRegenerate && /* @__PURE__ */ jsx("button", {
307
+ type: "button",
308
+ onClick: () => onRegenerate(message.id),
309
+ className: "p-1.5 rounded-lg hover:theme-hover theme-muted-text transition-colors",
310
+ title: t.regenerate,
311
+ children: /* @__PURE__ */ jsx(RefreshIcon, { className: "w-4 h-4" })
312
+ }),
313
+ !isUser && onNavigateBranch && /* @__PURE__ */ jsx(BranchNavigator, {
314
+ messages: allMessages,
315
+ currentMessageId: message.id,
316
+ onNavigate: onNavigateBranch
317
+ }),
318
+ (timestamp || !isUser && (message.model || totalTokens)) && /* @__PURE__ */ jsxs("span", {
319
+ 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"),
320
+ children: [
321
+ timestamp && /* @__PURE__ */ jsx("span", {
322
+ title: timestamp.toLocaleString(DateTime.DATETIME_MED_WITH_SECONDS),
323
+ children: timestamp.toRelative()
324
+ }),
325
+ !isUser && message.model && /* @__PURE__ */ jsx("span", { children: message.model }),
326
+ !isUser && totalTokens ? /* @__PURE__ */ jsx("span", {
327
+ 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,
328
+ children: t.tokens(totalTokens)
329
+ }) : null
330
+ ]
331
+ })
332
+ ]
333
+ })
334
+ ]
335
+ })
336
+ })
337
+ });
338
+ }
339
+ var ChatMessage_default = memo(ChatMessage);
340
+ //#endregion
341
+ export { ChatMessage_default as default };