@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.
- package/dist/{chunks/memoryStore-CC91S5LP.js → ai/adapter/memoryStore.js} +2 -2
- package/dist/ai/adapter/mock/index.js +2 -80
- package/dist/ai/adapter/mock/mockAdapter.js +81 -0
- package/dist/ai/adapter/openai/createOpenAIStream.js +33 -0
- package/dist/ai/adapter/openai/errors.js +37 -0
- package/dist/ai/adapter/openai/index.js +5 -333
- package/dist/ai/adapter/openai/openaiAdapter.js +54 -0
- package/dist/ai/adapter/openai/responses.js +108 -0
- package/dist/ai/adapter/openai/sse.js +112 -0
- package/dist/{chunks/types-Yy0NrerF.js → ai/adapter/types.js} +1 -1
- package/dist/ai/agent/classifier.js +12 -0
- package/dist/ai/agent/diff.js +223 -0
- package/dist/ai/agent/diffHighlight.js +101 -0
- package/dist/ai/agent/permissions.js +29 -0
- package/dist/ai/agent/toolfmt.js +383 -0
- package/dist/ai/agent/transcript.js +127 -0
- package/dist/ai/components/AllocationSelector.js +56 -0
- package/dist/ai/components/AttachmentManager.js +144 -0
- package/dist/ai/components/AttachmentUpload.js +179 -0
- package/dist/ai/components/BlockedGroupBanner.js +29 -0
- package/dist/ai/components/BranchNavigator.js +57 -0
- package/dist/ai/components/ChatEmptyState.js +171 -0
- package/dist/ai/components/ChatInput.js +265 -0
- package/dist/ai/components/ChatLayout.js +15 -0
- package/dist/ai/components/ChatMessage.js +338 -0
- package/dist/ai/components/ChatMessageList.js +301 -0
- package/dist/ai/components/ChatSidebar.js +345 -0
- package/dist/ai/components/ChatThread.js +198 -0
- package/dist/ai/components/ComposerChrome.js +50 -0
- package/dist/ai/components/ComposerFrame.js +33 -0
- package/dist/ai/components/DragOverlay.js +20 -0
- package/dist/ai/components/ModelSelector.js +343 -0
- package/dist/ai/components/ShareDialog.js +260 -0
- package/dist/ai/components/SlashMenu.js +91 -0
- package/dist/ai/components/agent/ActivityLine.js +151 -0
- package/dist/ai/components/agent/AgentMessageParts.js +721 -0
- package/dist/ai/components/agent/PermissionPicker.js +38 -0
- package/dist/ai/components/attachmentKeys.js +10 -0
- package/dist/ai/core/ChatProvider.js +848 -0
- package/dist/ai/core/KeyboardShortcutsProvider.js +9 -0
- package/dist/ai/core/chatReducer.js +223 -0
- package/dist/ai/core/config.js +45 -0
- package/dist/ai/core/events.js +4 -0
- package/dist/ai/core/exportConversation.js +42 -0
- package/dist/ai/core/greeting.js +38 -0
- package/dist/ai/core/parts.js +84 -0
- package/dist/ai/core/useDragDrop.js +36 -0
- package/dist/ai/core/useKeyboardShortcuts.js +77 -0
- package/dist/ai/core/useSSEStream.js +98 -0
- package/dist/ai/index.js +43 -6644
- package/dist/ai/strings.js +175 -0
- package/dist/ai/ui/Avatar.js +35 -0
- package/dist/ai/ui/Dropdown.js +110 -0
- package/dist/ai/ui/Markdown.js +79 -0
- package/dist/ai/ui/Notice.js +77 -0
- package/dist/ai/ui/codeBreaks.js +36 -0
- package/dist/ai/ui/prose.js +13 -0
- package/dist/ai/ui/sidebar.js +148 -0
- package/dist/ai/utils.js +23 -0
- package/dist/components/AddMenu.js +46 -0
- package/dist/components/Avatar.js +46 -0
- package/dist/{chunks/BareModal-BtRiQezg.js → components/BareModal.js} +1 -1
- package/dist/components/Breadcrumbs.js +83 -0
- package/dist/components/Button.js +83 -0
- package/dist/components/Callout.js +23 -0
- package/dist/components/Card.js +31 -0
- package/dist/components/CollapsiblePanel.js +77 -0
- package/dist/{chunks/ConfirmModal-aOR4O1QE.js → components/ConfirmModal.js} +4 -9
- package/dist/components/CopyToClipboard.js +81 -0
- package/dist/{chunks/Markdown-CM-FenNE.js → components/CreateModal.js} +9 -116
- package/dist/components/DescriptionList.js +42 -0
- package/dist/components/Dropdown.js +349 -0
- package/dist/components/EmptyState.js +27 -0
- package/dist/components/Filter.js +32 -0
- package/dist/components/FilterPill.js +13 -0
- package/dist/components/IconButton.js +34 -0
- package/dist/{chunks/Indicator-hBSIxOPK.js → components/Indicator.js} +1 -1
- package/dist/components/Input.js +88 -0
- package/dist/components/LetterBadge.js +13 -0
- package/dist/components/Listbox.js +123 -0
- package/dist/{chunks/Loader-BXoXTZGc.js → components/Loader.js} +1 -1
- package/dist/components/Markdown.js +15 -0
- package/dist/components/MultiSelect.js +171 -0
- package/dist/components/Overview.js +39 -0
- package/dist/components/PageHeader.js +194 -0
- package/dist/components/Pagination.js +35 -0
- package/dist/{chunks/Provider-CDYurraE.js → components/Provider.js} +1 -1
- package/dist/{chunks/RequiredMark-DfLdYbYZ.js → components/RequiredMark.js} +1 -1
- package/dist/components/SectionHeader.js +11 -0
- package/dist/components/SettingModeOption.js +30 -0
- package/dist/components/SettingRow.js +35 -0
- package/dist/components/SettingsCard.js +31 -0
- package/dist/components/SettingsGroup.js +17 -0
- package/dist/components/Sort.js +42 -0
- package/dist/components/StatusBadge.js +63 -0
- package/dist/components/SwitchToggle.js +59 -0
- package/dist/{chunks/SwitchToggleSmall-FcsaYSTs.js → components/SwitchToggleSmall.js} +1 -1
- package/dist/{chunks/Table-DU2MW8xz.js → components/Table.js} +3 -3
- package/dist/components/Toggle.js +20 -0
- package/dist/{chunks/Tooltip-K5HJr4_p.js → components/Tooltip.js} +2 -27
- package/dist/{chunks/StatusBadge-C1Blx_cW.js → components/dropdownUtils.js} +2 -63
- package/dist/{chunks/focus-D_iZJTW1.js → components/focus.js} +1 -1
- package/dist/components/ghostButton.js +7 -0
- package/dist/components/inputClasses.js +4 -0
- package/dist/components/keys.js +27 -0
- package/dist/components/relativeTime.js +32 -0
- package/dist/{chunks/useCssIsDark-DHr05dG2.js → components/useCssIsDark.js} +1 -1
- package/dist/{chunks/duration-BOv8Phl8.js → duration.js} +1 -1
- package/dist/editor/Editor.js +13 -0
- package/dist/editor/EditorField.js +53 -0
- package/dist/editor/FormCodePanel.js +173 -0
- package/dist/{chunks/Monaco-DJ8MW-Dq.js → editor/Monaco.js} +4 -2
- package/dist/editor/index.js +6 -1
- package/dist/editor/themes.js +91 -0
- package/dist/editor/workers.js +29 -0
- package/dist/editor/yaml.js +15 -0
- package/dist/file-explorer/FileExplorer.js +1402 -0
- package/dist/file-explorer/FileExplorerContext.js +370 -0
- package/dist/{chunks/CodePreviewEditor-YqQBz1YL.js → file-explorer/FilePreview/CodePreviewEditor.js} +3 -2
- package/dist/file-explorer/FilePreview/Inline.js +45 -0
- package/dist/file-explorer/FilePreview/StreamedBadge.js +16 -0
- package/dist/file-explorer/FilePreview/Viewer.js +526 -0
- package/dist/file-explorer/FilePreview/index.js +266 -0
- package/dist/file-explorer/FilePreview/parse.js +63 -0
- package/dist/file-explorer/FilePreview/previewCache.js +98 -0
- package/dist/file-explorer/FilePreview/previewType.js +151 -0
- package/dist/file-explorer/FilePreview/readCappedBytes.js +196 -0
- package/dist/file-explorer/FilePreview/useObjectBlobUrl.js +95 -0
- package/dist/file-explorer/FilePreview/useObjectText.js +109 -0
- package/dist/file-explorer/FilePreview/usePresignedUrl.js +73 -0
- package/dist/file-explorer/FileRow.js +165 -0
- package/dist/file-explorer/FileRowsWindow.js +92 -0
- package/dist/file-explorer/FileTable.js +172 -0
- package/dist/file-explorer/FileTableHeaders.js +49 -0
- package/dist/file-explorer/LoadMoreRow.js +46 -0
- package/dist/file-explorer/TreeView.js +376 -0
- package/dist/file-explorer/helpers.js +537 -0
- package/dist/file-explorer/index.js +7 -5671
- package/dist/file-explorer/lib/batch.js +39 -0
- package/dist/file-explorer/lib/errors.js +9 -0
- package/dist/file-explorer/lib/paging.js +124 -0
- package/dist/file-explorer/lib/utils.js +322 -0
- package/dist/file-explorer/toasts.js +400 -0
- package/dist/file-explorer/treeRows.js +73 -0
- package/dist/file-explorer/useNearEnd.js +27 -0
- package/dist/form/FieldWrapper.js +54 -0
- package/dist/form/Fields/BooleanField.js +51 -0
- package/dist/form/Fields/CheckboxGroupField.js +72 -0
- package/dist/form/Fields/ColorField.js +33 -0
- package/dist/form/Fields/DropdownField.js +49 -0
- package/dist/form/Fields/DurationField.js +130 -0
- package/dist/form/Fields/LabelField.js +19 -0
- package/dist/form/Fields/MultiDropdownField.js +40 -0
- package/dist/form/Fields/NumberField.js +51 -0
- package/dist/form/Fields/ObjectField.js +54 -0
- package/dist/form/Fields/RadioField.js +49 -0
- package/dist/form/Fields/RangeField.js +58 -0
- package/dist/form/Fields/ResourceOptionIcon.js +21 -0
- package/dist/form/Fields/StepField.js +6 -0
- package/dist/form/Fields/StringField.js +37 -0
- package/dist/form/Fields/TextAreaField.js +31 -0
- package/dist/form/Fields/durationValidity.js +11 -0
- package/dist/form/Form.js +643 -0
- package/dist/form/SpecialTypes/DropdownWrapper.js +30 -0
- package/dist/form/SpecialTypes/FormikCustomDropdown.js +122 -0
- package/dist/form/SpecialTypes/FormikCustomInput.js +111 -0
- package/dist/form/SpecialTypes/FormikMultiSelectDropdown.js +31 -0
- package/dist/form/SpecialTypes/FormikNumericInput.js +60 -0
- package/dist/form/Wizard/WizardContainer.js +138 -0
- package/dist/form/Wizard/WizardNavigation.js +92 -0
- package/dist/form/Wizard/WizardStepContent.js +35 -0
- package/dist/form/Wizard/WizardStepIndicator.js +108 -0
- package/dist/form/Wizard/useWizardState.js +118 -0
- package/dist/form/Wizard/utils.js +34 -0
- package/dist/form/fieldContext.js +26 -0
- package/dist/form/fieldRegistry.js +50 -0
- package/dist/form/index.js +17 -2321
- package/dist/{chunks/FieldWrapper-Cszwg-4B.js → form/lib.js} +2 -97
- package/dist/form/useParsedOpts.js +62 -0
- package/dist/form/usePrevious.js +11 -0
- package/dist/form/utils/checkShowIfObject.js +14 -0
- package/dist/form/utils/duration.js +69 -0
- package/dist/form/utils/getParentValue.js +23 -0
- package/dist/form/utils/getPlaceholder.js +9 -0
- package/dist/form/utils/getValueUsingPath.js +23 -0
- package/dist/form/utils/secondaryField.js +22 -0
- package/dist/graph/AnnotationBanner.js +141 -0
- package/dist/graph/Collapse.js +41 -0
- package/dist/graph/DependencyGraph.js +1398 -0
- package/dist/graph/JobSummary.js +104 -0
- package/dist/graph/MatrixGroup.js +107 -0
- package/dist/graph/Reveal.js +57 -0
- package/dist/graph/TreeView.js +711 -0
- package/dist/graph/index.js +1 -2519
- package/dist/graph/types.js +6 -0
- package/dist/index.js +46 -599
- package/dist/list/ActionToolbar.js +184 -0
- package/dist/list/HoverCard.js +73 -0
- package/dist/list/ListPage.js +145 -0
- package/dist/list/ListPager.js +59 -0
- package/dist/list/ListTable.js +163 -0
- package/dist/list/ListViewControls.js +372 -0
- package/dist/list/RowContextMenu.js +350 -0
- package/dist/list/UserHoverCard.js +117 -0
- package/dist/list/index.js +11 -2
- package/dist/list/listSearch.js +137 -0
- package/dist/list/listView.js +210 -0
- package/dist/list/textEntry.js +10 -0
- package/dist/{chunks/logviewer-BTKuQWzw.js → logviewer/LogViewer.js} +4 -82
- package/dist/logviewer/index.js +2 -1
- package/dist/logviewer/segments.js +80 -0
- package/package.json +1 -1
- package/dist/chunks/PageHeader-0kG_iypP.js +0 -692
- package/dist/chunks/SwitchToggle-DShNdcao.js +0 -758
- package/dist/chunks/editor-Dk1rbV75.js +0 -361
- package/dist/chunks/list-CJm875m7.js +0 -1418
- /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 };
|