@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,175 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ //#region src/ai/strings.tsx
3
+ var defaultChatStrings = {
4
+ modal: {
5
+ cancel: "Cancel",
6
+ save: "Save",
7
+ loading: "Loading…"
8
+ },
9
+ shareConversation: {
10
+ title: "Share Conversation",
11
+ loadError: "Failed to load sharing settings",
12
+ saveSuccess: "Sharing settings saved",
13
+ orgAccess: "Organization Access",
14
+ orgCanView: "Anyone in the organization can view",
15
+ allowCollaboration: "Allow collaboration (edit messages)",
16
+ groupAccess: "Group Access",
17
+ noGroups: "No groups have access yet",
18
+ viewOnly: "View only",
19
+ canCollaborate: "Can collaborate",
20
+ remove: "Remove",
21
+ addGroups: "Add groups…",
22
+ info: /* @__PURE__ */ jsxs(Fragment, { children: [
23
+ /* @__PURE__ */ jsx("strong", { children: "View" }),
24
+ " permission allows reading the conversation.",
25
+ /* @__PURE__ */ jsx("br", {}),
26
+ /* @__PURE__ */ jsx("strong", { children: "Collaborate" }),
27
+ " permission allows adding messages."
28
+ ] }),
29
+ shareButton: "Share conversation"
30
+ },
31
+ blockedGroup: {
32
+ title: "Billing group \"{group}\" is over its allocation",
33
+ message: "It has reached its allocation limit. Messages to this provider are paused until your administrator increases the allocation."
34
+ },
35
+ queue: { remove: "Remove queued message" },
36
+ activity: {
37
+ tokens: (input, output) => `↑${input} ↓${output} tokens`,
38
+ stop: "Stop"
39
+ },
40
+ emptyState: {
41
+ greeting: null,
42
+ modernSubtitle: "Pick a starter below or type a question to your selected model.",
43
+ noProvidersTitle: "No AI providers configured",
44
+ noProvidersBody: "To start chatting, add an AI provider.",
45
+ addProvider: "Add a provider",
46
+ sessionUnreachableTitle: (session) => /* @__PURE__ */ jsxs(Fragment, { children: [
47
+ "Session “",
48
+ session,
49
+ "” is not reachable"
50
+ ] }),
51
+ sessionUnreachableBody: "The session may be offline or still starting up. You can select a different model to continue.",
52
+ retry: "Retry"
53
+ },
54
+ input: {
55
+ placeholder: "Ask anything",
56
+ placeholderLoading: "Loading models...",
57
+ placeholderNoProviders: "Add an AI provider to start chatting",
58
+ attachFiles: "Attach files",
59
+ stopGenerating: "Stop generating (Escape)",
60
+ sendMessage: "Send message (Enter)",
61
+ filesAttached: (count) => `${count} file${count > 1 ? "s" : ""} attached`
62
+ },
63
+ thinking: {
64
+ label: "Thinking",
65
+ ellipsis: "Thinking...",
66
+ thoughtFor: (duration) => `Thought for ${duration}`,
67
+ activity: "Activity"
68
+ },
69
+ thread: { viewOnly: "You have view only access" },
70
+ sidebar: {
71
+ groupToday: "Today",
72
+ groupYesterday: "Yesterday",
73
+ groupThisWeek: "This Week",
74
+ groupThisMonth: "This Month",
75
+ groupOlder: "Older",
76
+ untitled: "Untitled",
77
+ rename: "Rename",
78
+ share: "Share",
79
+ delete: "Delete",
80
+ sharedWithYou: "Shared with you",
81
+ moreOptions: "More options",
82
+ openSidebar: "Open sidebar",
83
+ closeSidebar: "Close sidebar",
84
+ newChat: "New chat",
85
+ attachments: "Attachments",
86
+ connectTools: "Connect Tools",
87
+ manageProviders: "Manage Providers",
88
+ noConversations: "No conversations yet",
89
+ deleteTitle: "Delete Conversation",
90
+ deleteBody: "Are you sure you want to delete this conversation? This action cannot be undone.",
91
+ deleteAction: "Delete",
92
+ renameTitle: "Rename Conversation",
93
+ renameAction: "Rename",
94
+ renameLabel: "Conversation title",
95
+ renamePlaceholder: "Enter a new title"
96
+ },
97
+ attachmentUpload: {
98
+ fileTooLarge: (maxSize) => `File too large. Maximum size is ${maxSize}`,
99
+ maxFilesAllowed: (max) => `Maximum ${max} files allowed`,
100
+ remove: "Remove",
101
+ dropFilesHere: "Drop files here",
102
+ addFiles: "Add files (drag or click)"
103
+ },
104
+ attachmentManager: {
105
+ title: "Attachments",
106
+ empty: "No attachments yet",
107
+ download: "Download",
108
+ delete: "Delete",
109
+ loadMore: "Load more",
110
+ loading: "Loading..."
111
+ },
112
+ branch: {
113
+ previous: "Previous branch",
114
+ next: "Next branch"
115
+ },
116
+ chrome: {
117
+ searchPlaceholder: "Search conversations",
118
+ searchLabel: "Search conversations (⌘/Ctrl+Shift+F)",
119
+ noMatches: "No conversations match your search",
120
+ download: "Download",
121
+ exportError: "Failed to export conversation",
122
+ jumpToLatest: "Jump to latest"
123
+ },
124
+ messageMeta: {
125
+ stopped: "Stopped",
126
+ showMore: "Show more",
127
+ showLess: "Show less",
128
+ tokens: (total) => `${total.toLocaleString()} tokens`,
129
+ tokensBreakdown: (prompt, completion) => `${prompt.toLocaleString()} in · ${completion.toLocaleString()} out`,
130
+ copy: "Copy message",
131
+ edit: "Edit message",
132
+ regenerate: "Regenerate response",
133
+ retry: "Retry",
134
+ editPlaceholder: "Edit your message...",
135
+ cancel: "Cancel",
136
+ send: "Send"
137
+ },
138
+ permissions: {
139
+ choose: "Choose what the agent may do",
140
+ readOnly: "Read only",
141
+ acceptEdits: "Accept edits",
142
+ bypassPermissions: "Bypass permissions",
143
+ plan: "Plan",
144
+ readOnlyHint: "The workspace can be read but not changed.",
145
+ acceptEditsHint: "File edits are applied without asking. The workspace can be changed without your review.",
146
+ bypassPermissionsHint: "Every action is taken without asking, including commands that change the system.",
147
+ planHint: "The workspace is left unchanged while a plan is drafted."
148
+ },
149
+ agentTranscript: {
150
+ showMore: "Show more",
151
+ showLess: "Show less",
152
+ running: "Running…",
153
+ done: "Done",
154
+ failed: "Failed",
155
+ interrupted: "Interrupted",
156
+ noOutput: "(no output)",
157
+ diffTruncated: "Diff truncated",
158
+ moreLines: "+{count} more lines",
159
+ allowOnce: "Yes, allow this command",
160
+ allowDirectory: "Yes, and allow this directory for the session",
161
+ deny: "No, deny this command",
162
+ denyWithFeedback: "No, and provide feedback to the agent",
163
+ feedbackPlaceholder: "Tell the agent what to do instead…",
164
+ send: "Send",
165
+ answered: "Answered",
166
+ writeOwnAnswer: "Write my own answer…",
167
+ subagent: "Task",
168
+ tasks: "Tasks",
169
+ planPending: "Plan awaiting approval in the terminal",
170
+ approvalPending: "Waiting for an answer in the terminal",
171
+ approvalOptions: "Offered choices"
172
+ }
173
+ };
174
+ //#endregion
175
+ export { defaultChatStrings };
@@ -0,0 +1,35 @@
1
+ import cx from "classnames";
2
+ import { useMemo, useState } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ //#region src/ai/ui/Avatar.tsx
5
+ var sizeClasses = {
6
+ sm: "w-6 h-6 text-xs",
7
+ md: "w-8 h-8 text-sm",
8
+ lg: "w-10 h-10 text-base",
9
+ xl: "w-12 h-12 text-lg",
10
+ "2xl": "w-16 h-16 text-xl"
11
+ };
12
+ function Avatar({ src, name, size = "md", className }) {
13
+ const [erroredUrl, setErroredUrl] = useState(null);
14
+ const errored = erroredUrl !== null && erroredUrl === src;
15
+ const initials = useMemo(() => {
16
+ if (!name) return "?";
17
+ const parts = name.split(/[\s.]+/).filter(Boolean);
18
+ if (parts.length >= 2) return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
19
+ return parts[0]?.[0]?.toUpperCase() || "?";
20
+ }, [name]);
21
+ return /* @__PURE__ */ jsx("div", {
22
+ className: cx("relative inline-flex", className),
23
+ children: !src || errored ? /* @__PURE__ */ jsx("div", {
24
+ className: cx("rounded-full flex items-center justify-center font-medium", "bg-(--theme-element,#3b82f6) text-(--theme-element-text,#ffffff)", sizeClasses[size]),
25
+ children: initials
26
+ }) : /* @__PURE__ */ jsx("img", {
27
+ src,
28
+ alt: name || "User avatar",
29
+ onError: () => setErroredUrl(src),
30
+ className: cx("rounded-full object-cover", sizeClasses[size])
31
+ })
32
+ });
33
+ }
34
+ //#endregion
35
+ export { Avatar };
@@ -0,0 +1,110 @@
1
+ import { ChevronDownIcon } from "../../icons.js";
2
+ import { TOOLTIP_ID } from "../../components/Tooltip.js";
3
+ import cx from "classnames";
4
+ import { useCallback, useEffect, useRef, useState } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { createPortal } from "react-dom";
7
+ //#region src/ai/ui/Dropdown.tsx
8
+ /** Matches the list's max-h-60, so the flip decision matches what renders. */
9
+ var MAX_LIST_HEIGHT_PX = 240;
10
+ function Dropdown({ options, value, onChange, placeholder = "", disabled = false, variant = "default", icon, valueLabel, ariaLabel, hint, wrapperClassName, textBoxClassName, listClassName }) {
11
+ const [open, setOpen] = useState(false);
12
+ const wrapperRef = useRef(null);
13
+ const listRef = useRef(null);
14
+ const [position, setPosition] = useState({
15
+ top: 0,
16
+ bottom: 0,
17
+ left: 0,
18
+ width: 0,
19
+ flipped: false
20
+ });
21
+ const updatePosition = useCallback(() => {
22
+ const rect = wrapperRef.current?.getBoundingClientRect();
23
+ if (!rect) return;
24
+ const below = window.innerHeight - rect.bottom;
25
+ const flipped = below < MAX_LIST_HEIGHT_PX && rect.top > below;
26
+ setPosition({
27
+ top: rect.bottom + 4,
28
+ bottom: window.innerHeight - rect.top + 4,
29
+ left: rect.left,
30
+ width: rect.width,
31
+ flipped
32
+ });
33
+ }, []);
34
+ useEffect(() => {
35
+ if (!open) return;
36
+ updatePosition();
37
+ const onPointerDown = (e) => {
38
+ const target = e.target;
39
+ if (!wrapperRef.current?.contains(target) && !listRef.current?.contains(target)) setOpen(false);
40
+ };
41
+ const onKey = (e) => {
42
+ if (e.key === "Escape") setOpen(false);
43
+ };
44
+ window.addEventListener("mousedown", onPointerDown);
45
+ window.addEventListener("keydown", onKey);
46
+ window.addEventListener("scroll", updatePosition, true);
47
+ window.addEventListener("resize", updatePosition);
48
+ return () => {
49
+ window.removeEventListener("mousedown", onPointerDown);
50
+ window.removeEventListener("keydown", onKey);
51
+ window.removeEventListener("scroll", updatePosition, true);
52
+ window.removeEventListener("resize", updatePosition);
53
+ };
54
+ }, [open, updatePosition]);
55
+ const selected = options.find((o) => o.value === value);
56
+ const shown = valueLabel ?? selected?.label;
57
+ const tooltip = hint && !open ? {
58
+ "data-tooltip-id": TOOLTIP_ID,
59
+ "data-tooltip-content": hint
60
+ } : {};
61
+ return /* @__PURE__ */ jsxs("div", {
62
+ ref: wrapperRef,
63
+ className: cx("relative", wrapperClassName),
64
+ children: [/* @__PURE__ */ jsxs("button", {
65
+ type: "button",
66
+ disabled,
67
+ onClick: () => setOpen((o) => !o),
68
+ "aria-haspopup": "listbox",
69
+ "aria-expanded": open,
70
+ "aria-label": ariaLabel,
71
+ ...tooltip,
72
+ className: cx("flex items-center justify-between gap-2 cursor-pointer", variant === "default" && "bg-(--theme-input-bg,#f9fafb) text-(--theme-input,#000000)", "disabled:opacity-50 disabled:cursor-not-allowed", textBoxClassName || "text-sm border border-(--theme-border,#d1d5db) rounded px-2 py-1.5 w-full"),
73
+ children: [
74
+ icon,
75
+ /* @__PURE__ */ jsx("span", {
76
+ className: cx("truncate", !shown && "text-(--theme-muted-text-color,#6c757d)"),
77
+ children: shown || placeholder
78
+ }),
79
+ /* @__PURE__ */ jsx(ChevronDownIcon, { className: "h-3 w-3 shrink-0 opacity-60" })
80
+ ]
81
+ }), open && createPortal(/* @__PURE__ */ jsx("ul", {
82
+ ref: listRef,
83
+ style: {
84
+ position: "fixed",
85
+ ...position.flipped ? { bottom: position.bottom } : { top: position.top },
86
+ left: position.left,
87
+ minWidth: position.width,
88
+ zIndex: 300
89
+ },
90
+ className: cx("max-h-60 overflow-auto rounded-md border border-(--theme-border,#e5e7eb) bg-(--theme-panel-bg,#ffffff) py-1 shadow-lg", listClassName),
91
+ children: options.map((option) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("button", {
92
+ type: "button",
93
+ onClick: () => {
94
+ onChange(option.value);
95
+ setOpen(false);
96
+ },
97
+ className: cx("w-full cursor-pointer px-3 py-1.5 text-left text-sm hover:bg-(--theme-hover,#f3f4f6)", option.muted ? "text-(--theme-muted-text-color,#6c757d)" : "text-(--theme-panel,#000000)", option.value === value && !option.muted && "font-semibold"),
98
+ children: [/* @__PURE__ */ jsx("span", {
99
+ className: "block truncate",
100
+ children: option.label
101
+ }), option.description && /* @__PURE__ */ jsx("span", {
102
+ className: "mt-0.5 block text-xs font-normal leading-snug text-(--theme-muted-text-color,#6c757d)",
103
+ children: option.description
104
+ })]
105
+ }) }, option.value))
106
+ }), document.body)]
107
+ });
108
+ }
109
+ //#endregion
110
+ export { Dropdown as default };
@@ -0,0 +1,79 @@
1
+ import { useChatConfig } from "../core/config.js";
2
+ import { rehypeCodeBreaks } from "./codeBreaks.js";
3
+ import cx from "classnames";
4
+ import { useEffect, useState } from "react";
5
+ import { jsx } from "react/jsx-runtime";
6
+ import { code } from "@streamdown/code";
7
+ import { math } from "@streamdown/math";
8
+ import { Streamdown, defaultRehypePlugins } from "streamdown";
9
+ //#region src/ai/ui/Markdown.tsx
10
+ function schemeIsDark() {
11
+ if (typeof document === "undefined") return false;
12
+ const scheme = getComputedStyle(document.documentElement).colorScheme;
13
+ if (scheme.includes("dark") !== scheme.includes("light")) return scheme.includes("dark");
14
+ return typeof window.matchMedia === "function" && window.matchMedia("(prefers-color-scheme: dark)").matches;
15
+ }
16
+ var basePlugins = {
17
+ code,
18
+ math
19
+ };
20
+ var pluginsWithMermaid;
21
+ var mermaidLoad;
22
+ function loadMermaid() {
23
+ mermaidLoad ??= import("@streamdown/mermaid").then(({ createMermaidPlugin }) => {
24
+ const base = createMermaidPlugin();
25
+ pluginsWithMermaid = {
26
+ ...basePlugins,
27
+ mermaid: {
28
+ ...base,
29
+ getMermaid: (config) => base.getMermaid({
30
+ theme: schemeIsDark() ? "dark" : "neutral",
31
+ ...config
32
+ })
33
+ }
34
+ };
35
+ });
36
+ return mermaidLoad;
37
+ }
38
+ var shikiTheme = ["github-light", "github-dark"];
39
+ var rehypePlugins = [...Object.values(defaultRehypePlugins), rehypeCodeBreaks];
40
+ function Markdown({ children, isStreaming, controls }) {
41
+ const { markdownComponents } = useChatConfig();
42
+ const needsMermaid = !!children && children.includes("```mermaid");
43
+ const [, setMermaidReady] = useState(() => !!pluginsWithMermaid);
44
+ useEffect(() => {
45
+ if (!needsMermaid || pluginsWithMermaid) return;
46
+ let live = true;
47
+ loadMermaid().then(() => {
48
+ if (live) setMermaidReady(true);
49
+ });
50
+ return () => {
51
+ live = false;
52
+ };
53
+ }, [needsMermaid]);
54
+ const plugins = needsMermaid && pluginsWithMermaid ? pluginsWithMermaid : basePlugins;
55
+ return /* @__PURE__ */ jsx("div", {
56
+ className: "markdown whitespace-normal text-start w-full",
57
+ children: /* @__PURE__ */ jsx(Streamdown, {
58
+ mode: isStreaming ? "streaming" : "static",
59
+ ...isStreaming === void 0 ? {} : { isAnimating: isStreaming },
60
+ ...isStreaming ? { caret: "block" } : {},
61
+ ...controls === void 0 ? {} : { controls },
62
+ plugins,
63
+ rehypePlugins,
64
+ shikiTheme,
65
+ components: {
66
+ a: ({ node, ...props }) => /* @__PURE__ */ jsx("a", {
67
+ ...props,
68
+ className: cx("link wrap-break-word", props.className),
69
+ target: "_blank",
70
+ rel: "noopener noreferrer"
71
+ }),
72
+ ...markdownComponents
73
+ },
74
+ children: children || ""
75
+ })
76
+ });
77
+ }
78
+ //#endregion
79
+ export { Markdown as default };
@@ -0,0 +1,77 @@
1
+ import { AlertCircleIcon, CloseIcon, InfoCircleIcon } from "../../icons.js";
2
+ import cx from "classnames";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ //#region src/ai/ui/Notice.tsx
5
+ var barTone = {
6
+ neutral: "theme-border theme-muted-panel",
7
+ info: "border-blue-500/30 bg-blue-500/10",
8
+ warning: "border-amber-500/40 bg-amber-500/10",
9
+ danger: "border-red-500/30 bg-red-500/10"
10
+ };
11
+ var iconTone = {
12
+ neutral: "theme-muted-text",
13
+ info: "text-blue-600 dark:text-blue-400",
14
+ warning: "text-amber-600 dark:text-amber-400",
15
+ danger: "text-red-600 dark:text-red-400"
16
+ };
17
+ var titleTone = {
18
+ neutral: "theme-text",
19
+ info: "text-blue-700 dark:text-blue-300",
20
+ warning: "text-amber-700 dark:text-amber-300",
21
+ danger: "text-red-700 dark:text-red-300"
22
+ };
23
+ var cardTone = {
24
+ neutral: "theme-border theme-muted-panel theme-muted-text",
25
+ info: "border-blue-500/40 bg-blue-500/10 text-blue-800 dark:text-blue-200",
26
+ warning: "border-amber-500/40 bg-amber-500/10 text-amber-800 dark:text-amber-200",
27
+ danger: "border-red-500/40 bg-red-500/10 text-red-700 dark:text-red-300"
28
+ };
29
+ /** A standing condition on the surface: a full-width bar under the header
30
+ * with a title, an explanation, and the one thing the reader can do about it. */
31
+ function NoticeBar({ tone, title, children, action, onAction, testId }) {
32
+ const Icon = tone === "danger" || tone === "warning" ? AlertCircleIcon : InfoCircleIcon;
33
+ return /* @__PURE__ */ jsxs("div", {
34
+ "data-testid": testId,
35
+ className: cx("flex shrink-0 items-start gap-3 border-b px-6 py-3", barTone[tone]),
36
+ children: [
37
+ /* @__PURE__ */ jsx(Icon, { className: cx("mt-0.5 h-4 w-4 shrink-0", iconTone[tone]) }),
38
+ /* @__PURE__ */ jsxs("div", {
39
+ className: "min-w-0 flex-1",
40
+ children: [/* @__PURE__ */ jsx("p", {
41
+ className: cx("text-sm font-medium", titleTone[tone]),
42
+ children: title
43
+ }), children && /* @__PURE__ */ jsx("p", {
44
+ className: "mt-0.5 text-xs theme-muted-text",
45
+ children
46
+ })]
47
+ }),
48
+ action && onAction && /* @__PURE__ */ jsx("button", {
49
+ type: "button",
50
+ onClick: onAction,
51
+ className: "shrink-0 rounded-lg border theme-border bg-(--theme-panel-bg) px-3 py-1.5 text-xs theme-text hover:theme-hover",
52
+ children: action
53
+ })
54
+ ]
55
+ });
56
+ }
57
+ /** An inline notice next to the composer. One with no dismiss states a fact
58
+ * that is still true; one with a dismiss is a message the reader can put away. */
59
+ function NoticeCard({ tone = "neutral", children, onDismiss, dismissLabel, testId }) {
60
+ return /* @__PURE__ */ jsxs("div", {
61
+ "data-testid": testId,
62
+ className: cx("flex items-start gap-2 rounded-lg border px-3 py-2 text-xs", cardTone[tone]),
63
+ children: [/* @__PURE__ */ jsx("span", {
64
+ className: "flex-1",
65
+ children
66
+ }), onDismiss && /* @__PURE__ */ jsx("button", {
67
+ type: "button",
68
+ onClick: onDismiss,
69
+ "aria-label": dismissLabel,
70
+ title: dismissLabel,
71
+ className: "shrink-0 rounded p-0.5 hover:theme-hover",
72
+ children: /* @__PURE__ */ jsx(CloseIcon, { className: "h-3 w-3" })
73
+ })]
74
+ });
75
+ }
76
+ //#endregion
77
+ export { NoticeBar, NoticeCard };
@@ -0,0 +1,36 @@
1
+ //#region src/ai/ui/codeBreaks.ts
2
+ var BREAK_AFTER = /(?<=[/])/;
3
+ function withBreaks(text) {
4
+ const pieces = text.split(BREAK_AFTER);
5
+ const out = [];
6
+ pieces.forEach((piece, i) => {
7
+ if (i > 0) out.push({
8
+ type: "element",
9
+ tagName: "wbr",
10
+ properties: {},
11
+ children: []
12
+ });
13
+ if (piece !== "") out.push({
14
+ type: "text",
15
+ value: piece
16
+ });
17
+ });
18
+ return out;
19
+ }
20
+ function walk(node, insidePre) {
21
+ if (!("children" in node) || !node.children) return;
22
+ const el = node;
23
+ const pre = insidePre || node.type === "element" && el.tagName === "pre";
24
+ if (node.type === "element" && el.tagName === "code" && !pre) {
25
+ el.children = el.children.flatMap((child) => child.type === "text" ? withBreaks(child.value) : [child]);
26
+ return;
27
+ }
28
+ for (const child of node.children) walk(child, pre);
29
+ }
30
+ function rehypeCodeBreaks() {
31
+ return (tree) => {
32
+ walk(tree, false);
33
+ };
34
+ }
35
+ //#endregion
36
+ export { rehypeCodeBreaks };
@@ -0,0 +1,13 @@
1
+ //#region src/ai/ui/prose.ts
2
+ var chatProseClasses = [
3
+ "chat-ink prose max-w-none leading-relaxed",
4
+ "prose-p:my-3 prose-p:leading-relaxed",
5
+ "prose-headings:font-semibold prose-headings:mt-4 prose-headings:mb-1",
6
+ "prose-h1:text-xl prose-h2:text-lg prose-h3:text-[1rem] prose-h4:text-[1rem]",
7
+ "prose-ul:my-3 prose-ol:my-3 prose-li:my-0.5 prose-li:leading-relaxed",
8
+ "[&_li>p]:my-0.5 [&_li>p:first-child]:mt-0 [&_li>p:last-child]:mb-0",
9
+ "prose-strong:font-semibold prose-pre:my-0 prose-table:my-0",
10
+ "[&>.markdown>*:first-child]:mt-0 [&>.markdown>*:last-child]:mb-0"
11
+ ].join(" ");
12
+ //#endregion
13
+ export { chatProseClasses };
@@ -0,0 +1,148 @@
1
+ import { SidebarIcon } from "../../icons.js";
2
+ import { ConfirmModal } from "../../components/ConfirmModal.js";
3
+ import { focusOnMount } from "../../components/focus.js";
4
+ import { MoreButton, rowContextMenuProps, useRowMenuActive } from "../../list/RowContextMenu.js";
5
+ import cx from "classnames";
6
+ import { useState } from "react";
7
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
8
+ var HIGHLIGHT_SELECTED = "bg-[color-mix(in_oklab,var(--theme-panel)_8%,transparent)]";
9
+ var HIGHLIGHT_HOVER = "hover:bg-[color-mix(in_oklab,var(--theme-panel)_5%,transparent)]";
10
+ /** The column a conversation list lives in: it animates between its width
11
+ * and a rail, unless a drag is setting the width right now. */
12
+ function SidebarPanel({ collapsed, width = 256, railWidth = 48, resizing = false, label, className, style, children }) {
13
+ return /* @__PURE__ */ jsx("aside", {
14
+ "aria-label": label,
15
+ className: cx("relative h-full flex shrink-0 flex-col bg-(--theme-muted-panel-bg) overflow-hidden", !resizing && "transition-[width] duration-300 ease-in-out", className),
16
+ style: {
17
+ width: collapsed ? railWidth : width,
18
+ ...style
19
+ },
20
+ children
21
+ });
22
+ }
23
+ function SidebarToggle({ collapsed, onToggle, openLabel, closeLabel, className }) {
24
+ const label = collapsed ? openLabel : closeLabel;
25
+ return /* @__PURE__ */ jsx("button", {
26
+ type: "button",
27
+ onClick: onToggle,
28
+ title: label,
29
+ "aria-label": label,
30
+ "aria-expanded": !collapsed,
31
+ className: cx("flex h-7 w-7 items-center justify-center rounded-md theme-muted-text transition-colors hover:theme-muted-panel hover:theme-text", className),
32
+ children: /* @__PURE__ */ jsx(SidebarIcon, { className: "h-4 w-4" })
33
+ });
34
+ }
35
+ function SidebarGroupHeading({ children, tone = "default", testId }) {
36
+ return /* @__PURE__ */ jsx("h4", {
37
+ "data-testid": testId,
38
+ className: cx("mt-4 mb-1 whitespace-nowrap px-2 text-xs font-medium", tone === "attention" ? "text-amber-700 dark:text-amber-300" : "theme-muted-text"),
39
+ children
40
+ });
41
+ }
42
+ /** One entry in the list: the row highlights while it is the open one or
43
+ * while its menu is up, and the menu opens from its button or a right-click. */
44
+ function SidebarRow({ selected, menuItems, openMenu, className, children }) {
45
+ const { active, openMenu: openRowMenu } = useRowMenuActive(openMenu);
46
+ return /* @__PURE__ */ jsxs("li", {
47
+ className: cx("group relative flex items-center justify-between rounded-lg px-2 py-1 transition-colors duration-150", selected || active ? HIGHLIGHT_SELECTED : HIGHLIGHT_HOVER, className),
48
+ ...rowContextMenuProps(openRowMenu, menuItems),
49
+ children: [children, /* @__PURE__ */ jsx(MoreButton, {
50
+ active,
51
+ onOpen: (x, y) => openRowMenu(x, y, menuItems)
52
+ })]
53
+ });
54
+ }
55
+ /** One entry in the collapsed rail: a square the size of the toggle, with the
56
+ * same highlight as a row, holding whatever stands in for the item. */
57
+ function SidebarRailItem({ selected, label, onSelect, testId, tooltipProps, children }) {
58
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
59
+ type: "button",
60
+ "data-testid": testId,
61
+ "aria-label": label,
62
+ "aria-current": selected ? "page" : void 0,
63
+ onClick: onSelect,
64
+ ...tooltipProps,
65
+ className: cx("flex h-7 w-7 items-center justify-center rounded-md transition-colors", HIGHLIGHT_HOVER, selected && HIGHLIGHT_SELECTED),
66
+ children
67
+ }) });
68
+ }
69
+ function RenameDialog({ open, title, label, placeholder, confirmLabel, value, onChange, onConfirm, onClose }) {
70
+ return /* @__PURE__ */ jsx(ConfirmModal, {
71
+ open,
72
+ onClose,
73
+ align: "center",
74
+ closeOnConfirm: false,
75
+ title,
76
+ confirmLabel,
77
+ confirmDisabled: !value.trim(),
78
+ onConfirm,
79
+ children: /* @__PURE__ */ jsxs("label", {
80
+ className: "block space-y-2",
81
+ children: [/* @__PURE__ */ jsx("span", {
82
+ className: "text-sm font-medium theme-text",
83
+ children: label
84
+ }), /* @__PURE__ */ jsx("input", {
85
+ type: "text",
86
+ value,
87
+ onChange: (e) => onChange(e.target.value),
88
+ onKeyDown: (e) => {
89
+ if (e.key === "Enter" && value.trim()) onConfirm();
90
+ },
91
+ placeholder,
92
+ ref: focusOnMount,
93
+ className: "w-full rounded-md border theme-border bg-(--theme-input-bg) px-3 py-2 text-sm theme-text placeholder:theme-muted-text focus:border-(--theme-element) focus:outline-none"
94
+ })]
95
+ })
96
+ });
97
+ }
98
+ /** The delete confirmation and rename dialog every list of rows needs, with
99
+ * the item they are about. A handler that returns false keeps its dialog
100
+ * open, for a refusal the reader should see before trying again. */
101
+ function useRowDialogs({ strings, nameOf, onDelete, onRename }) {
102
+ const [toDelete, setToDelete] = useState(null);
103
+ const [toRename, setToRename] = useState(null);
104
+ const [name, setName] = useState("");
105
+ const closeRename = () => {
106
+ setToRename(null);
107
+ setName("");
108
+ };
109
+ const confirmDelete = async () => {
110
+ if (toDelete === null) return;
111
+ if (await onDelete(toDelete) !== false) setToDelete(null);
112
+ };
113
+ const confirmRename = async () => {
114
+ const trimmed = name.trim();
115
+ if (toRename === null || !trimmed) return;
116
+ if (await onRename(toRename, trimmed) !== false) closeRename();
117
+ };
118
+ return {
119
+ requestDelete: setToDelete,
120
+ requestRename: (item) => {
121
+ setName(nameOf(item));
122
+ setToRename(item);
123
+ },
124
+ dialogs: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ConfirmModal, {
125
+ open: toDelete !== null,
126
+ onClose: () => setToDelete(null),
127
+ align: "center",
128
+ destructive: true,
129
+ closeOnConfirm: false,
130
+ title: strings.deleteTitle,
131
+ description: toDelete === null ? null : strings.deleteBody(toDelete),
132
+ confirmLabel: strings.deleteAction,
133
+ onConfirm: confirmDelete
134
+ }), /* @__PURE__ */ jsx(RenameDialog, {
135
+ open: toRename !== null,
136
+ onClose: closeRename,
137
+ title: strings.renameTitle,
138
+ label: strings.renameLabel,
139
+ placeholder: strings.renamePlaceholder,
140
+ confirmLabel: strings.renameAction,
141
+ value: name,
142
+ onChange: setName,
143
+ onConfirm: confirmRename
144
+ })] })
145
+ };
146
+ }
147
+ //#endregion
148
+ export { RenameDialog, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarToggle, useRowDialogs };