@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,9 @@
1
+ import { useKeyboardShortcuts } from "./useKeyboardShortcuts.js";
2
+ import { Fragment, jsx } from "react/jsx-runtime";
3
+ //#region src/ai/core/KeyboardShortcutsProvider.tsx
4
+ function KeyboardShortcutsProvider({ children }) {
5
+ useKeyboardShortcuts({ enabled: true });
6
+ return /* @__PURE__ */ jsx(Fragment, { children });
7
+ }
8
+ //#endregion
9
+ export { KeyboardShortcutsProvider };
@@ -0,0 +1,223 @@
1
+ import { applyPartDelta } from "./parts.js";
2
+ //#region src/ai/core/chatReducer.ts
3
+ var initialState = {
4
+ conversations: [],
5
+ currentConversation: null,
6
+ providers: [],
7
+ models: [],
8
+ unreachableSessions: [],
9
+ selectedProvider: null,
10
+ selectedAllocation: null,
11
+ isLoading: false,
12
+ isLoadingConversation: false,
13
+ isLoadingModels: false,
14
+ hasLoadedModels: false,
15
+ modelsError: null,
16
+ isStreaming: false,
17
+ isThinking: false,
18
+ streamingContent: "",
19
+ streamingReasoning: "",
20
+ streamingParts: [],
21
+ thinkingStartTime: null,
22
+ sidebarCollapsed: false,
23
+ queuedMessages: []
24
+ };
25
+ function chatReducer(state, action) {
26
+ switch (action.type) {
27
+ case "SET_CONVERSATIONS": return {
28
+ ...state,
29
+ conversations: action.conversations
30
+ };
31
+ case "SET_CURRENT_CONVERSATION":
32
+ if (action.conversation) {
33
+ const isNewConversation = state.currentConversation?.id !== action.conversation.id;
34
+ let restoredProvider = state.selectedProvider;
35
+ if (isNewConversation) {
36
+ const messages = action.conversation.messages || [];
37
+ for (let i = messages.length - 1; i >= 0; i--) {
38
+ const msg = messages[i];
39
+ if (msg.role === "assistant" && msg.model) {
40
+ if (!state.hasLoadedModels || state.models.some((m) => m.id === msg.model)) restoredProvider = msg.model;
41
+ break;
42
+ }
43
+ }
44
+ }
45
+ return {
46
+ ...state,
47
+ currentConversation: {
48
+ ...action.conversation,
49
+ messages: action.conversation.messages || []
50
+ },
51
+ selectedProvider: restoredProvider,
52
+ ...isNewConversation && {
53
+ streamingContent: "",
54
+ streamingReasoning: "",
55
+ streamingParts: [],
56
+ isStreaming: false,
57
+ isThinking: false,
58
+ thinkingStartTime: null,
59
+ queuedMessages: []
60
+ }
61
+ };
62
+ }
63
+ return {
64
+ ...state,
65
+ currentConversation: null,
66
+ isLoadingConversation: false,
67
+ streamingContent: "",
68
+ streamingReasoning: "",
69
+ streamingParts: [],
70
+ isStreaming: false,
71
+ isThinking: false,
72
+ thinkingStartTime: null,
73
+ queuedMessages: []
74
+ };
75
+ case "UPDATE_CURRENT_CONVERSATION_TITLE":
76
+ if (!state.currentConversation) return state;
77
+ return {
78
+ ...state,
79
+ currentConversation: {
80
+ ...state.currentConversation,
81
+ title: action.title
82
+ }
83
+ };
84
+ case "SET_PROVIDERS": return {
85
+ ...state,
86
+ providers: action.providers
87
+ };
88
+ case "SET_MODELS": return {
89
+ ...state,
90
+ models: action.models,
91
+ unreachableSessions: action.unreachableSessions || [],
92
+ hasLoadedModels: true,
93
+ modelsError: null
94
+ };
95
+ case "SET_SELECTED_PROVIDER": return {
96
+ ...state,
97
+ selectedProvider: action.providerId
98
+ };
99
+ case "SET_SELECTED_ALLOCATION": return {
100
+ ...state,
101
+ selectedAllocation: action.allocation
102
+ };
103
+ case "SET_LOADING": return {
104
+ ...state,
105
+ isLoading: action.isLoading
106
+ };
107
+ case "SET_LOADING_MODELS": return {
108
+ ...state,
109
+ isLoadingModels: action.isLoadingModels
110
+ };
111
+ case "SET_MODELS_ERROR": return {
112
+ ...state,
113
+ modelsError: action.error,
114
+ hasLoadedModels: true
115
+ };
116
+ case "SET_STREAMING":
117
+ if (action.conversationId && state.currentConversation?.id !== action.conversationId) return state;
118
+ return {
119
+ ...state,
120
+ isStreaming: action.isStreaming
121
+ };
122
+ case "SET_THINKING": return {
123
+ ...state,
124
+ isThinking: action.isThinking,
125
+ thinkingStartTime: action.isThinking ? Date.now() : state.thinkingStartTime
126
+ };
127
+ case "APPEND_STREAMING_CONTENT":
128
+ if (state.currentConversation?.id !== action.conversationId) return state;
129
+ return {
130
+ ...state,
131
+ streamingContent: state.streamingContent + action.content,
132
+ isThinking: false
133
+ };
134
+ case "APPEND_STREAMING_REASONING":
135
+ if (state.currentConversation?.id !== action.conversationId) return state;
136
+ return {
137
+ ...state,
138
+ streamingReasoning: state.streamingReasoning + action.reasoning,
139
+ isThinking: true,
140
+ thinkingStartTime: state.thinkingStartTime || Date.now()
141
+ };
142
+ case "APPLY_STREAMING_PART":
143
+ if (state.currentConversation?.id !== action.conversationId) return state;
144
+ return {
145
+ ...state,
146
+ streamingParts: applyPartDelta(state.streamingParts, action.delta)
147
+ };
148
+ case "CLEAR_STREAMING_CONTENT":
149
+ if (action.conversationId && state.currentConversation?.id !== action.conversationId) return state;
150
+ return {
151
+ ...state,
152
+ streamingContent: "",
153
+ streamingReasoning: "",
154
+ streamingParts: [],
155
+ isThinking: false,
156
+ thinkingStartTime: null
157
+ };
158
+ case "ADD_MESSAGE":
159
+ if (!state.currentConversation || state.currentConversation.id !== action.conversationId) return state;
160
+ return {
161
+ ...state,
162
+ currentConversation: {
163
+ ...state.currentConversation,
164
+ messages: [...state.currentConversation.messages || [], action.message],
165
+ activeBranchId: action.message.id
166
+ }
167
+ };
168
+ case "UPDATE_MESSAGE":
169
+ if (!state.currentConversation || state.currentConversation.id !== action.conversationId) return state;
170
+ return {
171
+ ...state,
172
+ currentConversation: {
173
+ ...state.currentConversation,
174
+ messages: (state.currentConversation.messages || []).map((msg) => msg.id === action.messageId ? {
175
+ ...msg,
176
+ ...action.updates
177
+ } : msg)
178
+ }
179
+ };
180
+ case "TOGGLE_SIDEBAR": return {
181
+ ...state,
182
+ sidebarCollapsed: !state.sidebarCollapsed
183
+ };
184
+ case "ADD_CONVERSATION":
185
+ if (state.conversations.some((c) => c.id === action.conversation.id)) return state;
186
+ return {
187
+ ...state,
188
+ conversations: [action.conversation, ...state.conversations]
189
+ };
190
+ case "REMOVE_CONVERSATION": return {
191
+ ...state,
192
+ conversations: state.conversations.filter((c) => c.id !== action.conversationId)
193
+ };
194
+ case "SET_LOADING_CONVERSATION": return {
195
+ ...state,
196
+ isLoadingConversation: action.isLoadingConversation
197
+ };
198
+ case "RESTORE_BACKGROUND_STREAM": return {
199
+ ...state,
200
+ streamingContent: action.content,
201
+ streamingReasoning: action.reasoning,
202
+ streamingParts: action.parts ?? [],
203
+ isStreaming: true
204
+ };
205
+ case "ADD_QUEUED_MESSAGE":
206
+ if (action.conversationId && state.currentConversation?.id !== action.conversationId) return state;
207
+ return {
208
+ ...state,
209
+ queuedMessages: [...state.queuedMessages, action.message]
210
+ };
211
+ case "REMOVE_QUEUED_MESSAGE": return {
212
+ ...state,
213
+ queuedMessages: state.queuedMessages.filter((m) => m.id !== action.id)
214
+ };
215
+ case "CLEAR_QUEUED_MESSAGES": return {
216
+ ...state,
217
+ queuedMessages: []
218
+ };
219
+ default: return state;
220
+ }
221
+ }
222
+ //#endregion
223
+ export { chatReducer, initialState };
@@ -0,0 +1,45 @@
1
+ import { defaultChatStrings } from "../strings.js";
2
+ import { createContext, useContext } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ //#region src/ai/core/config.tsx
5
+ function hrefFor(target) {
6
+ switch (target.kind) {
7
+ case "conversation": return `/chat/${target.id}`;
8
+ case "attachments": return "/chat/attachments";
9
+ case "external": return target.href;
10
+ }
11
+ }
12
+ function DefaultLink({ target, className, title, onClick, children }) {
13
+ return /* @__PURE__ */ jsx("a", {
14
+ href: hrefFor(target),
15
+ className,
16
+ title,
17
+ onClick,
18
+ children
19
+ });
20
+ }
21
+ function resolveStrings(overrides) {
22
+ const merged = {};
23
+ for (const key of Object.keys(defaultChatStrings)) merged[key] = {
24
+ ...defaultChatStrings[key],
25
+ ...overrides?.[key]
26
+ };
27
+ return merged;
28
+ }
29
+ function resolveChatConfig(config) {
30
+ return {
31
+ LinkComponent: config?.LinkComponent ?? DefaultLink,
32
+ extraLinks: config?.extraLinks ?? {},
33
+ slots: config?.slots ?? {},
34
+ strings: resolveStrings(config?.strings),
35
+ ...config?.markdownComponents ? { markdownComponents: config.markdownComponents } : {},
36
+ ...config?.suggestedPrompts ? { suggestedPrompts: config.suggestedPrompts } : {}
37
+ };
38
+ }
39
+ var ChatConfigContext = createContext(resolveChatConfig());
40
+ var ChatConfigProvider = ChatConfigContext.Provider;
41
+ function useChatConfig() {
42
+ return useContext(ChatConfigContext);
43
+ }
44
+ //#endregion
45
+ export { ChatConfigProvider, resolveChatConfig, useChatConfig };
@@ -0,0 +1,4 @@
1
+ //#region src/ai/core/events.ts
2
+ var FOCUS_SIDEBAR_SEARCH_EVENT = "aichat:focus-sidebar-search";
3
+ //#endregion
4
+ export { FOCUS_SIDEBAR_SEARCH_EVENT };
@@ -0,0 +1,42 @@
1
+ import { DateTime } from "luxon";
2
+ //#region src/ai/core/exportConversation.ts
3
+ function conversationToMarkdown(conversation) {
4
+ const lines = [];
5
+ lines.push(`# ${conversation.title?.trim() || "Conversation"}`);
6
+ if (conversation.createdAt) {
7
+ const dt = DateTime.fromISO(conversation.createdAt);
8
+ if (dt.isValid) {
9
+ lines.push("");
10
+ lines.push(`> Started ${dt.toLocaleString(DateTime.DATETIME_MED)}`);
11
+ }
12
+ }
13
+ for (const message of conversation.messages) {
14
+ if (message.role !== "user" && message.role !== "assistant") continue;
15
+ const hasAttachments = (message.attachments?.length ?? 0) > 0;
16
+ if (!message.content?.trim() && !hasAttachments && !message.error) continue;
17
+ lines.push("");
18
+ lines.push(message.role === "user" ? "## User" : "## Assistant");
19
+ lines.push("");
20
+ if (message.content?.trim()) lines.push(message.content.trim());
21
+ for (const attachment of message.attachments ?? []) lines.push(`> Attached: ${attachment.filename}`);
22
+ if (message.error) lines.push(`> Error: ${message.error}`);
23
+ }
24
+ lines.push("");
25
+ return lines.join("\n");
26
+ }
27
+ function exportFilename(title) {
28
+ return `${(title ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "conversation"}.md`;
29
+ }
30
+ function downloadText(filename, text) {
31
+ const blob = new Blob([text], { type: "text/markdown;charset=utf-8" });
32
+ const url = URL.createObjectURL(blob);
33
+ const anchor = document.createElement("a");
34
+ anchor.href = url;
35
+ anchor.download = filename;
36
+ document.body.appendChild(anchor);
37
+ anchor.click();
38
+ anchor.remove();
39
+ URL.revokeObjectURL(url);
40
+ }
41
+ //#endregion
42
+ export { conversationToMarkdown, downloadText, exportFilename };
@@ -0,0 +1,38 @@
1
+ //#region src/ai/core/greeting.ts
2
+ function getGreeting(user) {
3
+ const hour = (/* @__PURE__ */ new Date()).getHours();
4
+ let timeOfDay;
5
+ if (hour >= 5 && hour < 12) timeOfDay = "morning";
6
+ else if (hour >= 12 && hour < 18) timeOfDay = "afternoon";
7
+ else timeOfDay = "evening";
8
+ const greetings = {
9
+ morning: [
10
+ `Good morning[name]! How can I help you today?`,
11
+ `Morning[name]! What can I assist you with?`,
12
+ `Rise and shine[name]! What's on your mind this morning?`,
13
+ `Good morning[name]! How can I make your day easier?`
14
+ ],
15
+ afternoon: [
16
+ `Good afternoon[name]! What can I help you with today?`,
17
+ `Hello[name]! How can I assist you this afternoon?`,
18
+ `What can I help you accomplish today[name]?`,
19
+ `Afternoon[name]! What questions do you have?`
20
+ ],
21
+ evening: [
22
+ `Good evening[name]! How can I assist you?`,
23
+ `Evening[name]! What's on your mind?`,
24
+ `How can I help you tonight[name]?`,
25
+ `What can I do for you this evening[name]?`
26
+ ]
27
+ };
28
+ const randomIndex = Math.floor(Math.random() * greetings[timeOfDay].length);
29
+ let greeting = greetings[timeOfDay][randomIndex] ?? "";
30
+ if (user?.name) {
31
+ const firstName = user.name.split(" ")[0];
32
+ if (Math.random() > .5) greeting = greeting.replace("[name]", `, ${firstName}`);
33
+ }
34
+ greeting = greeting.replace("[name]", "");
35
+ return greeting;
36
+ }
37
+ //#endregion
38
+ export { getGreeting };
@@ -0,0 +1,84 @@
1
+ //#region src/ai/core/parts.ts
2
+ /** Fold one streamed PartDelta into a parts timeline. Pure: returns a new
3
+ * array, leaving the input untouched, so it works for both reducer state
4
+ * and the provider's local accumulation. Unknown target ids are dropped
5
+ * rather than invented — an end without a start has nothing to render. */
6
+ function applyPartDelta(parts, delta) {
7
+ switch (delta.type) {
8
+ case "tool_start": return [...parts, {
9
+ kind: "tool_call",
10
+ id: delta.id,
11
+ name: delta.name,
12
+ args: delta.args,
13
+ status: "running",
14
+ ...delta.startLine !== void 0 ? { startLine: delta.startLine } : {}
15
+ }];
16
+ case "tool_update": return parts.map((p) => p.kind === "tool_call" && p.id === delta.id ? {
17
+ ...p,
18
+ args: p.args + delta.args
19
+ } : p);
20
+ case "tool_end": return parts.map((p) => p.kind === "tool_call" && p.id === delta.id ? {
21
+ ...p,
22
+ status: delta.isError ? "error" : "ok",
23
+ result: delta.result
24
+ } : p);
25
+ case "subagent_start": {
26
+ const part = {
27
+ kind: "subagent",
28
+ id: delta.id,
29
+ status: "running",
30
+ actions: [],
31
+ ...delta.agentType !== void 0 ? { agentType: delta.agentType } : {},
32
+ ...delta.color !== void 0 ? { color: delta.color } : {},
33
+ ...delta.background !== void 0 ? { background: delta.background } : {},
34
+ ...delta.description !== void 0 ? { description: delta.description } : {}
35
+ };
36
+ return [...parts, part];
37
+ }
38
+ case "subagent_action": return parts.map((p) => p.kind === "subagent" && p.id === delta.id ? {
39
+ ...p,
40
+ actions: [...p.actions ?? [], delta.action]
41
+ } : p);
42
+ case "subagent_end": return parts.map((p) => p.kind === "subagent" && p.id === delta.id ? {
43
+ ...p,
44
+ status: delta.ok ? "done" : "failed",
45
+ ...delta.summary !== void 0 ? { summary: delta.summary } : {},
46
+ ...delta.report !== void 0 ? { report: delta.report } : {}
47
+ } : p);
48
+ case "approval_request": return [...parts, delta.approval];
49
+ case "approval_resolved": return parts.map((p) => p.kind === "approval" && p.id === delta.id ? {
50
+ ...p,
51
+ resolved: true,
52
+ ...delta.answer !== void 0 ? { answer: delta.answer } : {}
53
+ } : p);
54
+ case "notice":
55
+ case "warning": return [...parts, {
56
+ kind: delta.type,
57
+ text: delta.text
58
+ }];
59
+ case "todo_snapshot": {
60
+ const idx = parts.findLastIndex((p) => p.kind === "todo_snapshot");
61
+ if (idx === -1) return [...parts, {
62
+ kind: "todo_snapshot",
63
+ todos: delta.todos
64
+ }];
65
+ return parts.map((p, i) => i === idx ? {
66
+ kind: "todo_snapshot",
67
+ todos: delta.todos
68
+ } : p);
69
+ }
70
+ default: return parts;
71
+ }
72
+ }
73
+ /** A turn can end (completed or stopped) with tool calls still marked
74
+ * running — the transport died before their tool_end arrived. A stored
75
+ * message must not spin forever, so close them out as interrupted. */
76
+ function finalizeParts(parts) {
77
+ return parts.map((p) => p.kind === "tool_call" && p.status === "running" ? {
78
+ ...p,
79
+ status: "error",
80
+ result: p.result ?? "interrupted"
81
+ } : p);
82
+ }
83
+ //#endregion
84
+ export { applyPartDelta, finalizeParts };
@@ -0,0 +1,36 @@
1
+ import { useCallback, useRef, useState } from "react";
2
+ //#region src/ai/core/useDragDrop.ts
3
+ function useDragDrop({ onFilesDropped }) {
4
+ const [isDragging, setIsDragging] = useState(false);
5
+ const dragCounterRef = useRef(0);
6
+ return {
7
+ isDragging,
8
+ dragHandlers: {
9
+ onDragEnter: useCallback((e) => {
10
+ e.preventDefault();
11
+ e.stopPropagation();
12
+ dragCounterRef.current++;
13
+ if (e.dataTransfer.types.includes("Files")) setIsDragging(true);
14
+ }, []),
15
+ onDragLeave: useCallback((e) => {
16
+ e.preventDefault();
17
+ e.stopPropagation();
18
+ dragCounterRef.current--;
19
+ if (dragCounterRef.current === 0) setIsDragging(false);
20
+ }, []),
21
+ onDragOver: useCallback((e) => {
22
+ e.preventDefault();
23
+ e.stopPropagation();
24
+ }, []),
25
+ onDrop: useCallback((e) => {
26
+ e.preventDefault();
27
+ e.stopPropagation();
28
+ dragCounterRef.current = 0;
29
+ setIsDragging(false);
30
+ if (e.dataTransfer.files.length > 0) onFilesDropped(e.dataTransfer.files);
31
+ }, [onFilesDropped])
32
+ }
33
+ };
34
+ }
35
+ //#endregion
36
+ export { useDragDrop as default };
@@ -0,0 +1,77 @@
1
+ import { useChat } from "./ChatProvider.js";
2
+ import { isMac } from "../utils.js";
3
+ import { FOCUS_SIDEBAR_SEARCH_EVENT } from "./events.js";
4
+ import { useCallback, useEffect } from "react";
5
+ //#region src/ai/core/useKeyboardShortcuts.ts
6
+ function useKeyboardShortcuts(options = {}) {
7
+ const { enabled = true } = options;
8
+ const { currentConversation, isStreaming, stopStreaming, clearCurrentConversation, navigation, notify } = useChat();
9
+ const getLastAssistantMessage = useCallback(() => {
10
+ const messages = currentConversation?.messages || [];
11
+ for (let i = messages.length - 1; i >= 0; i--) if (messages[i].role === "assistant" && messages[i].content) return messages[i].content;
12
+ return null;
13
+ }, [currentConversation?.messages]);
14
+ const getLastUserMessage = useCallback(() => {
15
+ const messages = currentConversation?.messages || [];
16
+ for (let i = messages.length - 1; i >= 0; i--) if (messages[i].role === "user") return messages[i];
17
+ return null;
18
+ }, [currentConversation?.messages]);
19
+ const handleNewConversation = useCallback(() => {
20
+ clearCurrentConversation();
21
+ navigation.toNewChat();
22
+ }, [clearCurrentConversation, navigation]);
23
+ const handleCopyLastResponse = useCallback(async () => {
24
+ const content = getLastAssistantMessage();
25
+ if (content) try {
26
+ await navigator.clipboard.writeText(content);
27
+ notify.success("Copied to clipboard");
28
+ } catch {
29
+ notify.error("Failed to copy");
30
+ }
31
+ else notify.info("No response to copy");
32
+ }, [getLastAssistantMessage, notify]);
33
+ const handleStopGeneration = useCallback(() => {
34
+ if (isStreaming) stopStreaming();
35
+ }, [isStreaming, stopStreaming]);
36
+ useEffect(() => {
37
+ if (!enabled) return;
38
+ const handleKeyDown = (e) => {
39
+ const cmdKey = isMac() ? e.metaKey : e.ctrlKey;
40
+ if (e.key === "Escape") {
41
+ handleStopGeneration();
42
+ return;
43
+ }
44
+ if (cmdKey && e.shiftKey && e.key === "O") {
45
+ e.preventDefault();
46
+ handleNewConversation();
47
+ return;
48
+ }
49
+ if (cmdKey && e.shiftKey && e.key === "C") {
50
+ e.preventDefault();
51
+ handleCopyLastResponse();
52
+ return;
53
+ }
54
+ if (cmdKey && e.shiftKey && e.key === "F") {
55
+ e.preventDefault();
56
+ document.dispatchEvent(new Event(FOCUS_SIDEBAR_SEARCH_EVENT));
57
+ return;
58
+ }
59
+ };
60
+ document.addEventListener("keydown", handleKeyDown);
61
+ return () => document.removeEventListener("keydown", handleKeyDown);
62
+ }, [
63
+ enabled,
64
+ handleNewConversation,
65
+ handleCopyLastResponse,
66
+ handleStopGeneration
67
+ ]);
68
+ return {
69
+ getLastUserMessage,
70
+ getLastAssistantMessage,
71
+ handleNewConversation,
72
+ handleCopyLastResponse,
73
+ handleStopGeneration
74
+ };
75
+ }
76
+ //#endregion
77
+ export { useKeyboardShortcuts };
@@ -0,0 +1,98 @@
1
+ import { useCallback, useEffect, useRef } from "react";
2
+ //#region src/ai/core/useSSEStream.ts
3
+ function useSSEStream(dispatch) {
4
+ const targetStreamContentRef = useRef("");
5
+ const revealedLengthRef = useRef(0);
6
+ const streamRafRef = useRef(null);
7
+ const streamActiveRef = useRef(false);
8
+ const lastFrameTimeRef = useRef(0);
9
+ const lastDispatchTimeRef = useRef(0);
10
+ const streamingConversationIdRef = useRef(null);
11
+ const backgroundStreamsRef = useRef(/* @__PURE__ */ new Map());
12
+ const abortControllerRef = useRef(null);
13
+ const animateStreamContent = useCallback(() => {
14
+ const now = performance.now();
15
+ const target = targetStreamContentRef.current;
16
+ const revealed = revealedLengthRef.current;
17
+ const remaining = target.length - revealed;
18
+ const conversationId = streamingConversationIdRef.current;
19
+ const timeSinceDispatch = now - lastDispatchTimeRef.current;
20
+ if (remaining > 0 && timeSinceDispatch >= 32 && conversationId) {
21
+ const dt = lastFrameTimeRef.current ? now - lastFrameTimeRef.current : 32;
22
+ const newRevealed = revealed + Math.min(remaining, Math.max(1, Math.round((remaining > 360 ? .48 : .24) * dt)));
23
+ revealedLengthRef.current = newRevealed;
24
+ dispatch({
25
+ type: "APPEND_STREAMING_CONTENT",
26
+ content: target.slice(revealed, newRevealed),
27
+ conversationId
28
+ });
29
+ lastDispatchTimeRef.current = now;
30
+ }
31
+ lastFrameTimeRef.current = now;
32
+ if (streamActiveRef.current || revealedLengthRef.current < targetStreamContentRef.current.length) streamRafRef.current = requestAnimationFrame(animateStreamContent);
33
+ else {
34
+ streamRafRef.current = null;
35
+ lastFrameTimeRef.current = 0;
36
+ }
37
+ }, [dispatch]);
38
+ const onStreamingChunk = useCallback((chunk) => {
39
+ const convId = streamingConversationIdRef.current;
40
+ if (convId) {
41
+ const bgStream = backgroundStreamsRef.current.get(convId);
42
+ if (bgStream) bgStream.content += chunk;
43
+ }
44
+ if (!streamActiveRef.current) return;
45
+ targetStreamContentRef.current += chunk;
46
+ if (streamRafRef.current === null) streamRafRef.current = requestAnimationFrame(animateStreamContent);
47
+ }, [animateStreamContent]);
48
+ const startStreaming = useCallback((conversationId) => {
49
+ streamingConversationIdRef.current = conversationId;
50
+ streamActiveRef.current = true;
51
+ }, []);
52
+ const stopAnimationLoop = useCallback(() => {
53
+ const conversationId = streamingConversationIdRef.current;
54
+ streamActiveRef.current = false;
55
+ if (streamRafRef.current !== null) {
56
+ cancelAnimationFrame(streamRafRef.current);
57
+ streamRafRef.current = null;
58
+ }
59
+ const unrevealed = targetStreamContentRef.current.slice(revealedLengthRef.current);
60
+ targetStreamContentRef.current = "";
61
+ revealedLengthRef.current = 0;
62
+ lastFrameTimeRef.current = 0;
63
+ lastDispatchTimeRef.current = 0;
64
+ return {
65
+ unrevealed,
66
+ conversationId
67
+ };
68
+ }, []);
69
+ const resetAnimationRefs = useCallback(() => {
70
+ streamActiveRef.current = false;
71
+ if (streamRafRef.current !== null) {
72
+ cancelAnimationFrame(streamRafRef.current);
73
+ streamRafRef.current = null;
74
+ }
75
+ targetStreamContentRef.current = "";
76
+ revealedLengthRef.current = 0;
77
+ lastFrameTimeRef.current = 0;
78
+ lastDispatchTimeRef.current = 0;
79
+ }, []);
80
+ useEffect(() => {
81
+ return () => {
82
+ if (streamRafRef.current !== null) cancelAnimationFrame(streamRafRef.current);
83
+ };
84
+ }, []);
85
+ return {
86
+ onStreamingChunk,
87
+ startStreaming,
88
+ stopAnimationLoop,
89
+ resetAnimationRefs,
90
+ targetStreamContentRef,
91
+ streamActiveRef,
92
+ streamingConversationIdRef,
93
+ backgroundStreamsRef,
94
+ abortControllerRef
95
+ };
96
+ }
97
+ //#endregion
98
+ export { useSSEStream };