@parallelworks/ui 0.51.0 → 0.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +155 -14
- package/dist/ai/agent/toolStrings.d.ts +2 -0
- package/dist/ai/agent/toolStrings.js +20 -18
- package/dist/ai/ai.css +12 -0
- package/dist/ai/components/AllocationSelector.js +5 -1
- package/dist/ai/components/AttachmentManager.js +51 -42
- package/dist/ai/components/AttachmentUpload.d.ts +4 -2
- package/dist/ai/components/AttachmentUpload.js +9 -9
- package/dist/ai/components/BlockedGroupBanner.js +8 -14
- package/dist/ai/components/BranchNavigator.js +11 -12
- package/dist/ai/components/ChatEmptyState.js +12 -19
- package/dist/ai/components/ChatInput.d.ts +4 -3
- package/dist/ai/components/ChatInput.js +27 -13
- package/dist/ai/components/ChatMessage.js +45 -121
- package/dist/ai/components/ChatMessageList.d.ts +5 -2
- package/dist/ai/components/ChatMessageList.js +89 -51
- package/dist/ai/components/ChatSidebar.js +11 -7
- package/dist/ai/components/ChatThread.js +1 -13
- package/dist/ai/components/DragOverlay.js +3 -3
- package/dist/ai/components/MessageAttachments.d.ts +10 -0
- package/dist/ai/components/MessageAttachments.js +78 -0
- package/dist/ai/components/ModelSelector.js +3 -3
- package/dist/ai/components/PastedText.js +2 -2
- package/dist/ai/components/ProviderIssueBanner.js +19 -23
- package/dist/ai/components/ShareDialog.js +2 -2
- package/dist/ai/components/agent/ActivityLine.d.ts +0 -2
- package/dist/ai/components/agent/ActivityLine.js +8 -51
- package/dist/ai/components/agent/AgentMessageParts.js +114 -53
- package/dist/ai/core/ChatProvider.d.ts +16 -5
- package/dist/ai/core/ChatProvider.js +41 -8
- package/dist/ai/core/config.js +4 -3
- package/dist/ai/index.d.ts +1 -1
- package/dist/ai/index.js +3 -3
- package/dist/ai/strings.d.ts +29 -3
- package/dist/ai/strings.js +81 -26
- package/dist/ai/types.d.ts +4 -0
- package/dist/ai/ui/Notice.d.ts +2 -2
- package/dist/ai/ui/Notice.js +29 -62
- package/dist/ai/ui/sidebar.d.ts +5 -3
- package/dist/ai/ui/sidebar.js +21 -9
- package/dist/components/ActionMenu.d.ts +1 -0
- package/dist/components/ActionMenu.js +6 -1
- package/dist/components/AppLink.d.ts +26 -0
- package/dist/components/AppLink.js +51 -0
- package/dist/components/Avatar.d.ts +2 -1
- package/dist/components/Avatar.js +4 -2
- package/dist/components/Button.d.ts +15 -5
- package/dist/components/Button.js +47 -40
- package/dist/components/Callout.d.ts +31 -7
- package/dist/components/Callout.js +50 -19
- package/dist/components/Card.d.ts +11 -6
- package/dist/components/Card.js +14 -8
- package/dist/components/Checkbox.js +2 -1
- package/dist/components/ColorInput.js +5 -3
- package/dist/components/Combobox.js +49 -19
- package/dist/components/ConfirmModal.d.ts +19 -8
- package/dist/components/ConfirmModal.js +31 -53
- package/dist/components/CopyButton.d.ts +38 -0
- package/dist/components/CopyButton.js +68 -0
- package/dist/components/CreateModal.js +118 -111
- package/dist/components/DatePicker.js +12 -8
- package/dist/components/DescriptionList.d.ts +25 -5
- package/dist/components/DescriptionList.js +56 -22
- package/dist/components/Disclosure.js +1 -1
- package/dist/components/Drawer.js +10 -16
- package/dist/components/Field.d.ts +15 -5
- package/dist/components/Field.js +40 -8
- package/dist/components/IconButton.d.ts +14 -5
- package/dist/components/IconButton.js +25 -14
- package/dist/components/InfoTip.d.ts +4 -1
- package/dist/components/InfoTip.js +2 -1
- package/dist/components/Input.d.ts +14 -8
- package/dist/components/Input.js +46 -31
- package/dist/components/Loader.js +1 -1
- package/dist/components/MenuEntries.d.ts +4 -0
- package/dist/components/MenuEntries.js +11 -12
- package/dist/components/Meter.js +5 -4
- package/dist/components/Modal.d.ts +28 -4
- package/dist/components/Modal.js +37 -14
- package/dist/components/Overview.d.ts +20 -12
- package/dist/components/Overview.js +14 -9
- package/dist/components/PageHeader.d.ts +47 -65
- package/dist/components/PageHeader.js +275 -153
- package/dist/components/PageHeaderAction.d.ts +67 -0
- package/dist/components/PageHeaderAction.js +107 -0
- package/dist/components/Pagination.js +6 -16
- package/dist/components/Popover.js +3 -2
- package/dist/components/Provider.d.ts +77 -19
- package/dist/components/Provider.js +139 -130
- package/dist/components/Radio.js +2 -1
- package/dist/components/RefreshButton.js +7 -8
- package/dist/components/RequiredMark.js +1 -1
- package/dist/components/Section.js +1 -1
- package/dist/components/Select.d.ts +4 -9
- package/dist/components/Select.js +24 -11
- package/dist/components/SettingModeOption.d.ts +1 -1
- package/dist/components/SettingModeOption.js +7 -1
- package/dist/components/SettingsCard.d.ts +5 -3
- package/dist/components/SettingsCard.js +27 -20
- package/dist/components/Slider.js +1 -1
- package/dist/components/StatusBadge.js +2 -2
- package/dist/components/Switch.js +5 -4
- package/dist/components/Table.d.ts +30 -20
- package/dist/components/Table.js +50 -73
- package/dist/components/Tabs.d.ts +3 -7
- package/dist/components/Tabs.js +6 -10
- package/dist/components/ThemeEditor.js +18 -8
- package/dist/components/Toast.d.ts +11 -3
- package/dist/components/Toast.js +45 -20
- package/dist/components/buttonClasses.js +1 -1
- package/dist/components/comboboxItems.d.ts +4 -0
- package/dist/components/comboboxItems.js +6 -1
- package/dist/components/dismiss.d.ts +12 -0
- package/dist/components/dismiss.js +19 -0
- package/dist/components/fieldBox.d.ts +4 -0
- package/dist/components/fieldBox.js +7 -3
- package/dist/components/floating.d.ts +3 -3
- package/dist/components/floating.js +1 -1
- package/dist/components/markdownCodeBreaks.js +3 -4
- package/dist/components/pageHeaderStore.d.ts +41 -0
- package/dist/components/pageHeaderStore.js +67 -0
- package/dist/components/pathBreaks.d.ts +8 -0
- package/dist/components/pathBreaks.js +28 -0
- package/dist/components/segmented.js +1 -1
- package/dist/components/shortcutKey.d.ts +2 -1
- package/dist/components/shortcutKey.js +19 -2
- package/dist/components/splitter.js +1 -1
- package/dist/components/useCssIsDark.js +40 -18
- package/dist/components/useTheme.js +9 -10
- package/dist/dev.d.ts +9 -0
- package/dist/dev.js +14 -0
- package/dist/editor/Editor.d.ts +3 -2
- package/dist/editor/Editor.js +11 -2
- package/dist/editor/EditorField.d.ts +2 -1
- package/dist/editor/EditorField.js +11 -29
- package/dist/editor/FormCodePanel.d.ts +7 -3
- package/dist/editor/FormCodePanel.js +14 -7
- package/dist/editor/Monaco.d.ts +14 -5
- package/dist/editor/Monaco.js +163 -112
- package/dist/editor/index.d.ts +5 -5
- package/dist/editor/index.js +3 -3
- package/dist/editor/themes.d.ts +8 -5
- package/dist/editor/themes.js +58 -30
- package/dist/editor/yaml.d.ts +6 -2
- package/dist/editor/yaml.js +11 -2
- package/dist/file-explorer/FileExplorer.js +143 -124
- package/dist/file-explorer/FileExplorerContext.js +1 -1
- package/dist/file-explorer/FilePreview/StreamedBadge.js +3 -1
- package/dist/file-explorer/FilePreview/Viewer.js +2 -2
- package/dist/file-explorer/FilePreview/index.js +4 -4
- package/dist/file-explorer/FileRow.js +1 -1
- package/dist/file-explorer/FileRowsWindow.js +34 -3
- package/dist/file-explorer/FileTable.js +3 -1
- package/dist/file-explorer/TreeView.js +37 -14
- package/dist/file-explorer/helpers.d.ts +2 -1
- package/dist/file-explorer/helpers.js +21 -20
- package/dist/file-explorer/toasts.js +27 -19
- package/dist/file-explorer/treeRows.d.ts +6 -0
- package/dist/file-explorer/treeRows.js +19 -1
- package/dist/file-explorer/useNearEnd.js +3 -6
- package/dist/form/Fields/CheckboxGroupField.js +12 -5
- package/dist/form/Fields/DurationField.js +1 -1
- package/dist/form/Fields/LabelField.js +1 -1
- package/dist/form/Fields/ObjectField.d.ts +1 -1
- package/dist/form/Fields/ObjectField.js +3 -2
- package/dist/form/Fields/StringField.js +16 -29
- package/dist/form/Fields/TextAreaField.js +3 -4
- package/dist/form/Form.js +3 -8
- package/dist/form/Wizard/WizardNavigation.js +0 -3
- package/dist/form/Wizard/utils.js +1 -1
- package/dist/form/fieldFrame.d.ts +1 -1
- package/dist/form/fieldFrame.js +2 -2
- package/dist/form/types/fieldComponentTypes.d.ts +2 -1
- package/dist/graph/AnnotationBanner.d.ts +1 -2
- package/dist/graph/AnnotationBanner.js +30 -32
- package/dist/graph/Connectors.js +4 -1
- package/dist/graph/DependencyGraph.js +2 -2
- package/dist/graph/GraphEditor.js +11 -9
- package/dist/graph/GraphEditorDialogs.js +1 -1
- package/dist/graph/GraphNode.js +7 -4
- package/dist/graph/InputDialog.js +1 -1
- package/dist/graph/InputsEditor.js +14 -11
- package/dist/graph/MatrixGroup.js +9 -3
- package/dist/graph/Reveal.js +18 -10
- package/dist/graph/ShortcutsButton.d.ts +11 -2
- package/dist/graph/ShortcutsButton.js +14 -3
- package/dist/graph/TreeView.js +35 -18
- package/dist/graph/WorkflowSettingsDialog.js +1 -1
- package/dist/graph/editorFields.js +1 -1
- package/dist/graph/editorRows.js +16 -10
- package/dist/graph/editorStrings.d.ts +7 -0
- package/dist/graph/editorStrings.js +8 -1
- package/dist/graph/inputRefs.js +1 -1
- package/dist/graph/settingsViews.js +11 -18
- package/dist/graph/stepWith.js +1 -1
- package/dist/icons.d.ts +2 -0
- package/dist/icons.js +4 -2
- package/dist/index.d.ts +12 -12
- package/dist/index.js +16 -16
- package/dist/list/ActionToolbar.d.ts +42 -6
- package/dist/list/ActionToolbar.js +297 -101
- package/dist/list/ListPage.d.ts +15 -23
- package/dist/list/ListPage.js +42 -43
- package/dist/list/ListTable.d.ts +32 -7
- package/dist/list/ListTable.js +48 -12
- package/dist/list/ListViewControls.js +1 -1
- package/dist/list/UserHoverCard.js +7 -15
- package/dist/list/index.d.ts +2 -2
- package/dist/list/index.js +2 -2
- package/dist/list/listSearch.js +12 -10
- package/dist/list/listView.d.ts +10 -1
- package/dist/list/listView.js +15 -1
- package/dist/logviewer/LogViewer.d.ts +4 -2
- package/dist/logviewer/LogViewer.js +87 -69
- package/dist/logviewer/logviewer.css +3 -2
- package/dist/reducedMotion.d.ts +5 -0
- package/dist/reducedMotion.js +20 -0
- package/dist/safeUrl.d.ts +1 -1
- package/dist/safeUrl.js +4 -2
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/dist/components/Breadcrumbs.d.ts +0 -37
- package/dist/components/Breadcrumbs.js +0 -90
- package/dist/components/CopyToClipboard.d.ts +0 -17
- package/dist/components/CopyToClipboard.js +0 -66
- package/dist/components/Sort.d.ts +0 -9
- package/dist/components/Sort.js +0 -42
package/README.md
CHANGED
|
@@ -18,7 +18,27 @@ so an application ships only what it imports:
|
|
|
18
18
|
| `./theme.css`, `./base.css`, `./fonts.css`, `./styles.css`, `./logviewer.css`, `./ai.css` | stylesheets |
|
|
19
19
|
|
|
20
20
|
Hosts bind strings, navigation, data hooks and the workflow engine through
|
|
21
|
-
`UIProvider`, which also shows the package's toasts
|
|
21
|
+
`UIProvider`, which also shows the package's toasts:
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
<UIProvider
|
|
25
|
+
strings={{ common: { save: 'Speichern' }, fileExplorer: { preview: { open: 'Öffnen' } } }}
|
|
26
|
+
navigation={{ goTo: navigate }}
|
|
27
|
+
data={{ useRunFile, usePersistedState }}
|
|
28
|
+
slots={{ link: RouterLink }}
|
|
29
|
+
engine={() => import('./engine').then((m) => m.engine)}
|
|
30
|
+
>
|
|
31
|
+
{app}
|
|
32
|
+
</UIProvider>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Every string has an English default, and `strings` overrides any of them at
|
|
36
|
+
any depth. Each concern has its own context, compared by value, so a host may
|
|
37
|
+
pass object literals: a render that passes equal ones re-renders nothing, and
|
|
38
|
+
new `data` re-renders only what reads data. `navigation` is read when the
|
|
39
|
+
package navigates and never re-renders anything. Define slot components and
|
|
40
|
+
string functions outside a component: one made each render is a new value, and
|
|
41
|
+
a new slot component remounts what it renders.
|
|
22
42
|
|
|
23
43
|
## Which component?
|
|
24
44
|
|
|
@@ -27,7 +47,8 @@ Each need has one component. Reach for it before styling markup by hand.
|
|
|
27
47
|
| Need | Use |
|
|
28
48
|
| --- | --- |
|
|
29
49
|
| An action | `Button`; `IconButton` for an icon alone, named by its tooltip |
|
|
30
|
-
|
|
|
50
|
+
| Navigation, in any form | the component's `href` (see [Links](#links)); `AppLink` for a link of your own |
|
|
51
|
+
| Another component's trigger that looks like a button | `buttonClasses()` |
|
|
31
52
|
| A link out of the app | `ExternalLink` |
|
|
32
53
|
| Work under way | `Loader`; `Skeleton` in a `LoadingRegion` for the shape of content still loading |
|
|
33
54
|
| Nothing to show yet, or nothing matches | `EmptyState` |
|
|
@@ -35,7 +56,7 @@ Each need has one component. Reach for it before styling markup by hand.
|
|
|
35
56
|
| A number, a date, a color | `NumberInput`, `DatePicker`, `ColorInput` |
|
|
36
57
|
| A one-time code, such as a sign-in check | `OneTimeCodeInput` |
|
|
37
58
|
| A labeled value nobody edits, among fields | `FieldValue` in a `Field` |
|
|
38
|
-
| A record's details, such as in a drawer or a card: names and values on their own | `DescriptionList` of `DescriptionListItem`s; `copy` on an ID, `mono` on a path, `variant="card"` to frame them |
|
|
59
|
+
| A record's details, such as in a drawer or a card: names and values on their own | `DescriptionList` of `DescriptionListItem`s; `copy` on an ID, `mono` on a path, `variant="card"` to frame them; titled lists in a `DescriptionListGroup` to share one label column |
|
|
39
60
|
| An error beside a control outside a `Field` | `ErrorText` |
|
|
40
61
|
| A dense field in a toolbar or a table cell | any field control with `size="sm"`; a width in `className` wins, and `cx` merges classes the same way |
|
|
41
62
|
| On or off | `Switch`; `Checkbox` in a list of things to tick, several of which may be on |
|
|
@@ -45,6 +66,8 @@ Each need has one component. Reach for it before styling markup by hand.
|
|
|
45
66
|
| A value from a list | `Select`, or `Combobox` when the list needs filtering |
|
|
46
67
|
| Free text with suggestions, or tags | `Autocomplete`; `Combobox` with `multiple creatable` |
|
|
47
68
|
| A range | `Slider` |
|
|
69
|
+
| A page's breadcrumbs, title and commands | `PageHeader`, shown by the shell's `PageHeaderBar` (see [Page header](#page-header)) |
|
|
70
|
+
| An action in the page header | `PageHeaderAction`; `PageHeaderAddButton` for "+ Add" or "+ Create"; `PageHeaderMenu` for the "…" menu |
|
|
48
71
|
| A titled part of a page or form | `Section` |
|
|
49
72
|
| More detail under a line | `Disclosure` |
|
|
50
73
|
| Views of one thing | `Tabs` |
|
|
@@ -52,8 +75,9 @@ Each need has one component. Reach for it before styling markup by hand.
|
|
|
52
75
|
| A panel of controls anchored to a trigger | `Popover` |
|
|
53
76
|
| A hint for a control | `Tooltip`; `InfoTip` for help everyone must reach |
|
|
54
77
|
| A preview of what a link leads to | `HoverCard` |
|
|
55
|
-
| A task that blocks the page | `Modal
|
|
78
|
+
| A task that blocks the page | `Modal`, laid out with `title`, `description` and `footer` as a `Drawer` is; `ConfirmModal` to confirm one action; `CreateModal` to name and create one thing |
|
|
56
79
|
| A panel beside the page | `Drawer` |
|
|
80
|
+
| A message set into the page: guidance, a warning, an error | `Callout`; `variant="bar"` across a surface under its header |
|
|
57
81
|
| A passing notice, or the progress of work in the background | `toast` |
|
|
58
82
|
| How full something is, how far a task has come | `Meter`, `ProgressBar` |
|
|
59
83
|
| Pages of a long list | `Pagination` |
|
|
@@ -61,9 +85,12 @@ Each need has one component. Reach for it before styling markup by hand.
|
|
|
61
85
|
| A person | `Avatar` |
|
|
62
86
|
| A status in words | `StatusBadge`, or `StatusDot` beside text that says it |
|
|
63
87
|
| A run's, job's or step's status beside its name | `RunStatusIcon`; `StatusBadge` with `runStatusTone` to say it in words |
|
|
64
|
-
| A copy action | `
|
|
88
|
+
| A copy action | `CopyButton`; `CopyCodeBlock` for a command or snippet; `useCopy` for a menu row or a control of your own |
|
|
65
89
|
| A live relative time or duration | `RelativeTime`, `Elapsed` |
|
|
66
90
|
| Rendered Markdown | `Markdown` |
|
|
91
|
+
| Code or YAML to read or edit | `Editor` from `./editor`; `EditorField` as a form field, `FormCodePanel` for a form's values as JSON |
|
|
92
|
+
| A run's jobs, steps, logs and annotations | `DependencyGraph` from `./graph`, as a graph or a list |
|
|
93
|
+
| A workflow's jobs drawn from its YAML, or edited by hand | `DependencyGraphPreview` from `./graph`, editable with an `editor`; `InputsFormEditor` for its run form |
|
|
67
94
|
| A value that survives a reload | `usePersistedState` |
|
|
68
95
|
| Choosing the app's colors | `ThemeEditor`, applied with `useApplyTheme` |
|
|
69
96
|
|
|
@@ -102,6 +129,71 @@ Some pairs look alike and are split on purpose:
|
|
|
102
129
|
A `Combobox` filters as you type, for a list long enough to search, and is the
|
|
103
130
|
one that takes several values, new ones, or free text (`Autocomplete`).
|
|
104
131
|
|
|
132
|
+
## Page header
|
|
133
|
+
|
|
134
|
+
A page declares its header in one place, and the shell shows it:
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
// The shell, once: the provider above the bar and the routes.
|
|
138
|
+
<PageHeaderProvider>
|
|
139
|
+
<Sidebar />
|
|
140
|
+
<div className="flex min-w-0 flex-1 flex-col">
|
|
141
|
+
<PageHeaderBar compact={fullBleed} persistent={isMobile} />
|
|
142
|
+
<main><Outlet /></main>
|
|
143
|
+
</div>
|
|
144
|
+
</PageHeaderProvider>
|
|
145
|
+
|
|
146
|
+
// A page.
|
|
147
|
+
<PageHeader
|
|
148
|
+
breadcrumbs={[{ label: 'Clusters', href: '/clusters' }, { label: cluster?.name ?? '' }]}
|
|
149
|
+
titleActions={
|
|
150
|
+
<>
|
|
151
|
+
<PageHeaderAction icon={EditIcon} iconOnly label="Rename" onClick={rename} />
|
|
152
|
+
<PageHeaderMenu items={menu} />
|
|
153
|
+
</>
|
|
154
|
+
}
|
|
155
|
+
actions={
|
|
156
|
+
<PageHeaderAction icon={SaveIcon} label="Save" onClick={save} disabled={!dirty} disabledReason="No changes" />
|
|
157
|
+
}
|
|
158
|
+
/>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
- **`breadcrumbs`** is the whole trail, ending with the page itself, which the
|
|
162
|
+
bar makes the page's one `h1` (`aria-current="page"`). Don't render another
|
|
163
|
+
`h1`. An empty label, such as a name still loading, shows a placeholder. The
|
|
164
|
+
trail is a list in a `nav` landmark named by the strings' `breadcrumb`. A
|
|
165
|
+
trail too wide for its bar folds its ancestors into a "…" menu of links
|
|
166
|
+
(named by `showFullPath`), keeping the parent when it fits beside the page's
|
|
167
|
+
name; the name truncates last, with the full name on hover.
|
|
168
|
+
- **`titleActions`** sit beside the title, for actions on the page's subject.
|
|
169
|
+
**`actions`** sit at the end of the bar, for the page's commands: a detail
|
|
170
|
+
page's Start, Stop or Terminal go here, not in a row of buttons below it.
|
|
171
|
+
`ActionToolbar` (from `./list`) is for the controls of a panel on the page,
|
|
172
|
+
such as a log's follow and refresh: its buttons are outlined, and `primary`
|
|
173
|
+
fills the one, if any, the panel leads to.
|
|
174
|
+
**`toolbar`** is a row under the bar, such as tabs or a time range.
|
|
175
|
+
- **`hidden`** takes the bar away while the page is mounted, for a page that
|
|
176
|
+
fills the window.
|
|
177
|
+
|
|
178
|
+
The actions render in the page's React tree, so they keep its context and
|
|
179
|
+
state, and show in the bar through portals; they come before the page's content
|
|
180
|
+
in focus order. Any component may render a `PageHeader`, such as a form adding
|
|
181
|
+
its Save button: the bar shows the actions of all of them, outermost first,
|
|
182
|
+
and the trail of the last one rendered that has one. A layout renders its own
|
|
183
|
+
before its `Outlet`, so a page under it that names a trail replaces the
|
|
184
|
+
layout's, and a page that doesn't keeps it. Changing pages swaps the header
|
|
185
|
+
before paint, and a page's actions go when it unmounts.
|
|
186
|
+
|
|
187
|
+
`PageHeaderBar` renders nothing while the page declares nothing, unless it is
|
|
188
|
+
`persistent`, as on a narrow screen where it holds the `navToggle` slot. The
|
|
189
|
+
`breadcrumbPin` slot renders after the trail. Without a `PageHeaderProvider`, a
|
|
190
|
+
`PageHeader` renders its bar in place, for a page with no shell.
|
|
191
|
+
|
|
192
|
+
The header's actions share one control: a link (`href`, with `external` for a
|
|
193
|
+
new tab), a button (`onClick`, with `pressed` for a toggle) or a menu (`menu`).
|
|
194
|
+
`destructive` takes the error color. A `disabled` one stays focusable and says
|
|
195
|
+
its `disabledReason` in its tooltip and name.
|
|
196
|
+
|
|
105
197
|
## Menu and row actions
|
|
106
198
|
|
|
107
199
|
A `MenuAction`, and the `RowAction` and `ToolbarAction` built on it, does
|
|
@@ -317,13 +409,42 @@ Every pressable thing has one look, from `buttonClasses({ variant, size, iconOnl
|
|
|
317
409
|
|
|
318
410
|
Sizes are `xs`, `sm` and `md`. `Button` and `IconButton` apply it. An
|
|
319
411
|
`IconButton` is named by its tooltip, and takes `pressed` for a toggle and
|
|
320
|
-
`dot` for an indicator.
|
|
321
|
-
component's trigger rather than styling a button
|
|
412
|
+
`dot` for an indicator. Either takes `href` to be a link in the same look. Pass
|
|
413
|
+
`buttonClasses()` to another component's trigger rather than styling a button
|
|
414
|
+
by hand. A pressed toggle
|
|
322
415
|
(`aria-pressed`) and an open popup's trigger read as selected.
|
|
323
416
|
|
|
324
417
|
`Loader` is the one spinner. Inside a button, or beside text that already
|
|
325
418
|
says what is happening, use `<Loader inline decorative />`.
|
|
326
419
|
|
|
420
|
+
## Links
|
|
421
|
+
|
|
422
|
+
Navigation is a link, never a button whose `onClick` navigates: a link opens
|
|
423
|
+
in a new tab with a middle or modified click, can be copied, shows its URL on
|
|
424
|
+
hover, and reads as a link. Every component that can navigate takes `href`,
|
|
425
|
+
with `reloadDocument` for a path the host's router doesn't serve and `target`
|
|
426
|
+
where it opens elsewhere, and renders it through `AppLink`:
|
|
427
|
+
|
|
428
|
+
| Surface | Navigates with |
|
|
429
|
+
| --- | --- |
|
|
430
|
+
| `Button`, `IconButton`, `Card` | `href` |
|
|
431
|
+
| Menu rows, `RowAction`s (a list row's menu and pinned buttons), `ToolbarAction`s | `href` on the action, with `target: '_blank'` for a new tab |
|
|
432
|
+
| `PageHeaderAction`, `PageHeaderAddButton`, `PageHeader` breadcrumbs | `href`; `external` opens a new tab |
|
|
433
|
+
| `Tabs` that are pages, `Pagination` | `href` on the item, `pageHref` |
|
|
434
|
+
| A list row | `ListRow` `href` with a `NameCell` of the same `href`: the name's link covers the row |
|
|
435
|
+
| A toast's action | `action: { label, href }` |
|
|
436
|
+
| `SidebarRailItem` | `href`; `onSelect` then runs only on a plain click |
|
|
437
|
+
| `EmptyState`, `SettingsCard` actions | a `Button` with `href` |
|
|
438
|
+
|
|
439
|
+
`AppLink` routes in-app URLs through the provider's `link` slot, so a host
|
|
440
|
+
binds its router's `Link` there once. That link receives the whole URL, search
|
|
441
|
+
and hash included, and routes it client-side on a plain click, leaving
|
|
442
|
+
modified and middle clicks to the browser. `AppLink` passes every href through
|
|
443
|
+
`safeUrl`, gives a `reloadDocument` or another origin a plain anchor, and
|
|
444
|
+
renders a disabled link with no href and `aria-disabled`, still focusable for
|
|
445
|
+
its tooltip. Work a link's `onClick` does beside navigating, such as closing a
|
|
446
|
+
drawer, belongs to a plain click only: check `isPlainClick(event)`.
|
|
447
|
+
|
|
327
448
|
## Toasts
|
|
328
449
|
|
|
329
450
|
`toast` shows a notice from anywhere, a component or not. `UIProvider` renders
|
|
@@ -358,6 +479,7 @@ root:
|
|
|
358
479
|
| ------------------- | ----------------------------------------------------------------------- |
|
|
359
480
|
| `--app-inset-top` | The height of a fixed top bar. Drawers and toasts start below it. |
|
|
360
481
|
| `--app-inset-start` | The width of a fixed sidebar on the start side. Toasts center over the page beside it, and a drawer at its widest leaves that strip, and never less than 2.5rem. Update it when the sidebar collapses. |
|
|
482
|
+
| `--table-header-top` | How far below the top of its scrolling box a table's sticky header row stops (`Table stickyHeader`, which list tables use). Set it on the scrolling box, or an ancestor, when something else sticks at that box's top, such as a page header in a page that scrolls as a whole. Inside `ListPage` the rows scroll in their own box below the header and toolbar, so it stays 0. |
|
|
361
483
|
|
|
362
484
|
```ts
|
|
363
485
|
document.documentElement.style.setProperty('--app-inset-top', '56px')
|
|
@@ -413,6 +535,9 @@ meaning across the apps.
|
|
|
413
535
|
record. `RemoveUserIcon` takes someone out of a group or project, while
|
|
414
536
|
`DisableUserIcon` turns off their account. `ChangesRequestedIcon` asks for
|
|
415
537
|
changes to something sent, while `FlaggedIcon` marks an item for attention.
|
|
538
|
+
`ProjectIcon` is a project, while `FolderIcon` is a folder of files.
|
|
539
|
+
`LibraryIcon` is a collection of write-ups to browse, while `NotebookIcon`
|
|
540
|
+
is one notebook.
|
|
416
541
|
`PendingIcon` is a requirement not met yet and `NotRunningIcon` a run not
|
|
417
542
|
started; they share the empty circle, so keep them out of the same list.
|
|
418
543
|
- **Adding an icon:** pick the [Lucide glyph](https://lucide.dev/icons) and add
|
|
@@ -477,36 +602,52 @@ configureEditorYaml([
|
|
|
477
602
|
])
|
|
478
603
|
```
|
|
479
604
|
|
|
480
|
-
Without them, those views still edit, but check nothing against the schema.
|
|
605
|
+
Without them, those views still edit, but check nothing against the schema. Registering a schema
|
|
606
|
+
under a `uri` already registered replaces it, and `configureEditorYaml` returns a function that
|
|
607
|
+
removes what it added.
|
|
608
|
+
|
|
609
|
+
`Editor` is controlled like an input: pass `value` and `onChange`, and hand back the text it sent.
|
|
610
|
+
Text that comes back unchanged leaves the cursor alone, and a reset from outside keeps the cursor
|
|
611
|
+
and scroll where they were. Each editor has a model of its own unless it is given a `path`, and it
|
|
612
|
+
is named `UIStrings.common.codeEditor` unless given an `ariaLabel`.
|
|
481
613
|
|
|
482
614
|
## AI chat
|
|
483
615
|
|
|
484
616
|
`@parallelworks/ui/ai` is a chat interface (thread, composer, streaming,
|
|
485
617
|
sidebar, model selector, attachments, sharing, branching) decoupled from any
|
|
486
618
|
backend through a `ChatAdapter`. State lives in `ChatProvider` and is read
|
|
487
|
-
with `useChat
|
|
619
|
+
with `useChat`; the reply being streamed is read with `useChatStream`, apart
|
|
620
|
+
from the rest, so only what draws the reply re-renders for each token:
|
|
488
621
|
|
|
489
622
|
```tsx
|
|
490
|
-
import {
|
|
623
|
+
import { ChatEmptyState, ChatLayout, ChatProvider, ChatThread } from '@parallelworks/ui/ai'
|
|
491
624
|
import { createMockChatAdapter } from '@parallelworks/ui/ai/mock'
|
|
492
625
|
|
|
493
|
-
export function Chat() {
|
|
626
|
+
export function Chat({ conversationId }: { conversationId: string | null }) {
|
|
494
627
|
return (
|
|
495
628
|
<ChatProvider
|
|
496
629
|
adapter={createMockChatAdapter()}
|
|
497
630
|
currentUser={{ id: 'u1', username: 'you', name: 'You' }}
|
|
498
|
-
navigation={{ toConversation: id => {}, toNewChat: () => {}
|
|
631
|
+
navigation={{ toConversation: id => {}, toNewChat: () => {} }}
|
|
632
|
+
activeConversationId={conversationId}
|
|
499
633
|
>
|
|
500
634
|
<ChatLayout>
|
|
501
|
-
<ChatThread />
|
|
635
|
+
{conversationId ? <ChatThread conversationId={conversationId} /> : <ChatEmptyState />}
|
|
502
636
|
</ChatLayout>
|
|
503
637
|
</ChatProvider>
|
|
504
638
|
)
|
|
505
639
|
}
|
|
506
640
|
```
|
|
507
641
|
|
|
642
|
+
The chat's links (each conversation, a new chat, attachments, and the
|
|
643
|
+
`extraLinks` pages) render through the config's `LinkComponent`, which gets a
|
|
644
|
+
`ChatLinkTarget`; without one they go to `/chat/:id`, `/chat` and
|
|
645
|
+
`/chat/attachments` through `UIProvider`'s link slot. Navigation in the chat is
|
|
646
|
+
these links, so `navigation` handles only the moves that aren't clicks, such as
|
|
647
|
+
keyboard shortcuts.
|
|
648
|
+
|
|
508
649
|
`ChatLayout sidebarMode="auto"` shows the conversation list as a drawer over
|
|
509
|
-
the thread when the chat is narrower than `drawerBelowPx` (
|
|
650
|
+
the thread when the chat is narrower than `drawerBelowPx` (`DRAWER_BELOW_PX`, 896, by default),
|
|
510
651
|
measured on the chat's own box; `"drawer"` always does, and `"inline"` (the
|
|
511
652
|
default) never does. The reader's toggle drives the list unless the host
|
|
512
653
|
passes `sidebar` (`'expanded' | 'collapsed' | 'hidden'`) and `drawerOpen` to
|
|
@@ -34,4 +34,6 @@ export interface ToolStrings {
|
|
|
34
34
|
sentence: (clauses: string[]) => string;
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
+
/** A count and its noun in English: "1 file", "1,024 files". */
|
|
38
|
+
export declare const countOf: (n: number, one: string, other: string) => string;
|
|
37
39
|
export declare const defaultToolStrings: ToolStrings;
|
|
@@ -1,30 +1,32 @@
|
|
|
1
1
|
//#region src/ai/agent/toolStrings.ts
|
|
2
|
-
var
|
|
2
|
+
var englishPlural = new Intl.PluralRules("en");
|
|
3
|
+
/** A count and its noun in English: "1 file", "1,024 files". */
|
|
4
|
+
var countOf = (n, one, other) => `${n.toLocaleString()} ${englishPlural.select(n) === "one" ? one : other}`;
|
|
3
5
|
var defaultToolStrings = {
|
|
4
6
|
noOutput: "(no output)",
|
|
5
|
-
firstLine: (line, moreLines) => moreLines > 0 ? `${line} (+${
|
|
7
|
+
firstLine: (line, moreLines) => moreLines > 0 ? `${line} (+${countOf(moreLines, "line", "lines")})` : line,
|
|
6
8
|
emptyFile: "Empty file",
|
|
7
9
|
readImage: "Read image",
|
|
8
|
-
readLines: (lines, more) => more > 0 ? `Read ${
|
|
10
|
+
readLines: (lines, more) => more > 0 ? `Read ${countOf(lines, "line", "lines")} (${more.toLocaleString()} more)` : `Read ${countOf(lines, "line", "lines")}`,
|
|
9
11
|
noMatches: "No matches",
|
|
10
|
-
foundMatches: (matches, files) => `Found ${
|
|
11
|
-
foundFiles: (files) => `Found ${
|
|
12
|
+
foundMatches: (matches, files) => `Found ${countOf(matches, "match", "matches")} in ${countOf(files, "file", "files")}`,
|
|
13
|
+
foundFiles: (files) => `Found ${countOf(files, "file", "files")}`,
|
|
12
14
|
noFilesMatched: "No files matched",
|
|
13
15
|
truncated: (summary) => `${summary} (truncated)`,
|
|
14
|
-
exitCode: (code, lines) => lines > 0 ? `Exit ${code} · ${
|
|
15
|
-
httpStatus: (status, lines) => `HTTP ${status} · ${
|
|
16
|
+
exitCode: (code, lines) => lines > 0 ? `Exit ${code} · ${countOf(lines, "line", "lines")}` : `Exit ${code}`,
|
|
17
|
+
httpStatus: (status, lines) => `HTTP ${status} · ${countOf(lines, "line", "lines")}`,
|
|
16
18
|
outputTruncated: (summary) => `${summary} (output truncated)`,
|
|
17
|
-
edited: (added, removed) => added > 0 && removed > 0 ? `Added ${
|
|
18
|
-
wrote: (lines, path) => path ? `Wrote ${
|
|
19
|
+
edited: (added, removed) => added > 0 && removed > 0 ? `Added ${countOf(added, "line", "lines")}, removed ${countOf(removed, "line", "lines")}` : added > 0 ? `Added ${countOf(added, "line", "lines")}` : removed > 0 ? `Removed ${countOf(removed, "line", "lines")}` : "",
|
|
20
|
+
wrote: (lines, path) => path ? `Wrote ${countOf(lines, "line", "lines")} to ${path}` : `Wrote ${countOf(lines, "line", "lines")}`,
|
|
19
21
|
rollup: {
|
|
20
|
-
searched: (n) => `searched for ${
|
|
21
|
-
read: (n) => `read ${
|
|
22
|
-
ranCommands: (n) => `ran ${
|
|
23
|
-
fetched: (n) => `fetched ${
|
|
24
|
-
loadedSkills: (n) => `loaded ${
|
|
25
|
-
sentMessages: (n) => `sent ${
|
|
26
|
-
calledMcpTools: (n) => `called ${
|
|
27
|
-
ranTools: (n) => `ran ${
|
|
22
|
+
searched: (n) => `searched for ${countOf(n, "pattern", "patterns")}`,
|
|
23
|
+
read: (n) => `read ${countOf(n, "file", "files")}`,
|
|
24
|
+
ranCommands: (n) => `ran ${countOf(n, "shell command", "shell commands")}`,
|
|
25
|
+
fetched: (n) => `fetched ${countOf(n, "URL", "URLs")}`,
|
|
26
|
+
loadedSkills: (n) => `loaded ${countOf(n, "skill", "skills")}`,
|
|
27
|
+
sentMessages: (n) => `sent ${countOf(n, "message", "messages")}`,
|
|
28
|
+
calledMcpTools: (n) => `called ${countOf(n, "MCP tool", "MCP tools")}`,
|
|
29
|
+
ranTools: (n) => `ran ${countOf(n, "tool", "tools")}`,
|
|
28
30
|
sentence: (clauses) => {
|
|
29
31
|
const s = clauses.join(", ");
|
|
30
32
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
@@ -32,4 +34,4 @@ var defaultToolStrings = {
|
|
|
32
34
|
}
|
|
33
35
|
};
|
|
34
36
|
//#endregion
|
|
35
|
-
export { defaultToolStrings };
|
|
37
|
+
export { countOf, defaultToolStrings };
|
package/dist/ai/ai.css
CHANGED
|
@@ -338,6 +338,18 @@
|
|
|
338
338
|
color: transparent;
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
+
/* The shimmer sweeps forever, so under reduced motion it holds still: the
|
|
342
|
+
* text drawn plainly in the accent, the color the sweep passes through. */
|
|
343
|
+
@media (prefers-reduced-motion: reduce) {
|
|
344
|
+
.animate-shimmer {
|
|
345
|
+
animation: none;
|
|
346
|
+
}
|
|
347
|
+
.text-shimmer {
|
|
348
|
+
background-image: none;
|
|
349
|
+
color: var(--theme-accent, #06354f);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
341
353
|
/* Entrance animation for chat messages (CSS replacement for a JS motion lib).
|
|
342
354
|
* Only `from` is defined so elements settle at their natural styles (queued
|
|
343
355
|
* messages render at reduced opacity). */
|
|
@@ -8,7 +8,11 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
import { useEffect } from "react";
|
|
9
9
|
import useSWR from "swr";
|
|
10
10
|
//#region src/ai/components/AllocationSelector.tsx
|
|
11
|
-
var
|
|
11
|
+
var dollars = new Intl.NumberFormat(void 0, {
|
|
12
|
+
style: "currency",
|
|
13
|
+
currency: "USD"
|
|
14
|
+
});
|
|
15
|
+
var money = (amount) => dollars.format(amount);
|
|
12
16
|
/** Which allocation an organization model bills, with its spend so far. Shows
|
|
13
17
|
* only for an organization model, and picks the first allocation for it. */
|
|
14
18
|
function AllocationSelector() {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { cx } from "../../cx.js";
|
|
2
2
|
import { DownloadIcon, FileIcon, TrashIcon } from "../../icons.js";
|
|
3
3
|
import { safeUrl } from "../../safeUrl.js";
|
|
4
|
-
import { EmptyState } from "../../components/EmptyState.js";
|
|
5
4
|
import { Loader } from "../../components/Loader.js";
|
|
6
5
|
import { toast } from "../../components/Toast.js";
|
|
6
|
+
import { Tooltip } from "../../components/Tooltip.js";
|
|
7
7
|
import { useStrings } from "../../components/Provider.js";
|
|
8
|
+
import { EmptyState } from "../../components/EmptyState.js";
|
|
8
9
|
import { formatBytes } from "../../format.js";
|
|
9
10
|
import { RelativeTime } from "../../components/relativeTime.js";
|
|
10
11
|
import { useChatConfig } from "../core/config.js";
|
|
@@ -14,16 +15,6 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
14
15
|
import { useCallback, useMemo, useState } from "react";
|
|
15
16
|
import useSWRInfinite from "swr/infinite";
|
|
16
17
|
//#region src/ai/components/AttachmentManager.tsx
|
|
17
|
-
function triggerDownload(url, filename) {
|
|
18
|
-
const href = safeUrl(url);
|
|
19
|
-
if (!href) return;
|
|
20
|
-
const a = document.createElement("a");
|
|
21
|
-
a.href = href;
|
|
22
|
-
a.download = filename;
|
|
23
|
-
document.body.appendChild(a);
|
|
24
|
-
a.click();
|
|
25
|
-
document.body.removeChild(a);
|
|
26
|
-
}
|
|
27
18
|
function AttachmentManager() {
|
|
28
19
|
const { adapter } = useChat();
|
|
29
20
|
const t = useChatConfig().strings.attachmentManager;
|
|
@@ -76,54 +67,72 @@ function AttachmentManager() {
|
|
|
76
67
|
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
77
68
|
className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4",
|
|
78
69
|
children: attachments.map((att) => {
|
|
79
|
-
const downloadUrl = attachmentsAdapter.downloadUrl(att.id);
|
|
70
|
+
const downloadUrl = safeUrl(attachmentsAdapter.downloadUrl(att.id));
|
|
71
|
+
const name = att.filename || t.unnamedFile;
|
|
80
72
|
const isDeleting = deleting === att.id;
|
|
81
73
|
const thumbnail = thumbnails[att.id];
|
|
82
74
|
const showThumbnail = att.contentType?.startsWith("image/") && thumbnail !== "broken";
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
75
|
+
const preview = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(FileIcon, {
|
|
76
|
+
"aria-hidden": "true",
|
|
77
|
+
className: "w-8 h-8 theme-muted-text"
|
|
78
|
+
}), showThumbnail && /* @__PURE__ */ jsx("img", {
|
|
79
|
+
src: downloadUrl,
|
|
80
|
+
alt: "",
|
|
81
|
+
className: cx("absolute inset-0 w-full h-full object-cover", thumbnail !== "loaded" && "opacity-0"),
|
|
82
|
+
loading: "lazy",
|
|
83
|
+
onLoad: () => markThumbnail(att.id, "loaded"),
|
|
84
|
+
onError: () => markThumbnail(att.id, "broken")
|
|
85
|
+
})] });
|
|
86
|
+
const previewClasses = "relative aspect-square w-full bg-(--theme-muted-panel-bg) flex items-center justify-center";
|
|
88
87
|
return /* @__PURE__ */ jsxs("div", {
|
|
89
88
|
className: cx("rounded-lg border theme-border overflow-hidden", isDeleting && "opacity-50 pointer-events-none"),
|
|
90
|
-
children: [/* @__PURE__ */
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
onError: () => markThumbnail(att.id, "broken")
|
|
100
|
-
})]
|
|
89
|
+
children: [attachmentsAdapter.onOpen ? /* @__PURE__ */ jsx("button", {
|
|
90
|
+
type: "button",
|
|
91
|
+
onClick: () => attachmentsAdapter.onOpen?.(att),
|
|
92
|
+
"aria-label": t.open(name),
|
|
93
|
+
className: cx(previewClasses, "cursor-pointer"),
|
|
94
|
+
children: preview
|
|
95
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
96
|
+
className: previewClasses,
|
|
97
|
+
children: preview
|
|
101
98
|
}), /* @__PURE__ */ jsxs("div", {
|
|
102
99
|
className: "p-3 text-xs bg-(--theme-panel-bg) flex items-center justify-between gap-1",
|
|
103
100
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
104
101
|
className: "min-w-0 flex-1",
|
|
105
102
|
children: [/* @__PURE__ */ jsx("p", {
|
|
106
103
|
className: "truncate font-medium text-(--theme-app)",
|
|
107
|
-
children:
|
|
104
|
+
children: name
|
|
108
105
|
}), /* @__PURE__ */ jsxs("p", {
|
|
109
106
|
className: "truncate theme-muted-text",
|
|
110
107
|
children: [att.size ? formatBytes(att.size) : "", att.uploadedAt && /* @__PURE__ */ jsxs(Fragment, { children: [att.size ? " · " : "", /* @__PURE__ */ jsx(RelativeTime, { value: att.uploadedAt })] })]
|
|
111
108
|
})]
|
|
112
109
|
}), /* @__PURE__ */ jsxs("div", {
|
|
113
110
|
className: "flex items-center gap-0.5 flex-shrink-0",
|
|
114
|
-
children: [/* @__PURE__ */ jsx(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
111
|
+
children: [/* @__PURE__ */ jsx(Tooltip, {
|
|
112
|
+
content: common.download,
|
|
113
|
+
children: /* @__PURE__ */ jsx("a", {
|
|
114
|
+
href: downloadUrl,
|
|
115
|
+
download: att.filename || true,
|
|
116
|
+
"aria-label": t.download(name),
|
|
117
|
+
className: "p-1.5 rounded-lg hover:theme-hover theme-muted-text transition-colors",
|
|
118
|
+
children: /* @__PURE__ */ jsx(DownloadIcon, {
|
|
119
|
+
"aria-hidden": "true",
|
|
120
|
+
className: "w-4 h-4"
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
}), /* @__PURE__ */ jsx(Tooltip, {
|
|
124
|
+
content: common.delete,
|
|
125
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
126
|
+
type: "button",
|
|
127
|
+
onClick: () => handleDelete(att.id),
|
|
128
|
+
disabled: isDeleting,
|
|
129
|
+
"aria-label": t.remove(name),
|
|
130
|
+
className: "p-1.5 rounded-lg hover:bg-(--theme-error-muted) theme-muted-text hover:text-(--theme-error-text) transition-colors",
|
|
131
|
+
children: /* @__PURE__ */ jsx(TrashIcon, {
|
|
132
|
+
"aria-hidden": "true",
|
|
133
|
+
className: "w-4 h-4"
|
|
134
|
+
})
|
|
135
|
+
})
|
|
127
136
|
})]
|
|
128
137
|
})]
|
|
129
138
|
})]
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Ref } from 'react';
|
|
1
2
|
export interface AttachmentUploadHandle {
|
|
2
3
|
addFiles: (files: FileList | File[]) => void;
|
|
3
4
|
}
|
|
@@ -19,6 +20,7 @@ interface AttachmentUploadProps {
|
|
|
19
20
|
disabled?: boolean;
|
|
20
21
|
maxFiles?: number;
|
|
21
22
|
maxFileSize?: number;
|
|
23
|
+
ref?: Ref<AttachmentUploadHandle> | undefined;
|
|
22
24
|
}
|
|
23
|
-
|
|
24
|
-
export
|
|
25
|
+
export default function AttachmentUpload({ conversationId, attachments, onAttachmentsChange, disabled, maxFiles, maxFileSize, ref, }: AttachmentUploadProps): import("react").JSX.Element;
|
|
26
|
+
export {};
|
|
@@ -7,7 +7,7 @@ import { useChatConfig } from "../core/config.js";
|
|
|
7
7
|
import { useChat } from "../core/ChatProvider.js";
|
|
8
8
|
import { ATTACHMENTS_INFINITE_KEY } from "./attachmentKeys.js";
|
|
9
9
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
-
import {
|
|
10
|
+
import { useCallback, useImperativeHandle, useRef, useState } from "react";
|
|
11
11
|
import { mutate } from "swr";
|
|
12
12
|
//#region src/ai/components/AttachmentUpload.tsx
|
|
13
13
|
var DEFAULT_MAX_FILE_SIZE = 26214400;
|
|
@@ -18,7 +18,7 @@ function getFileIcon(mimeType) {
|
|
|
18
18
|
if (mimeType === "application/pdf") return PdfIcon;
|
|
19
19
|
return FileIcon;
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
function AttachmentUpload({ conversationId, attachments, onAttachmentsChange, disabled = false, maxFiles = DEFAULT_MAX_FILES, maxFileSize = DEFAULT_MAX_FILE_SIZE, ref }) {
|
|
22
22
|
const { adapter } = useChat();
|
|
23
23
|
const t = useChatConfig().strings.attachmentUpload;
|
|
24
24
|
const { common } = useStrings();
|
|
@@ -125,7 +125,7 @@ var AttachmentUpload = forwardRef(function AttachmentUpload({ conversationId, at
|
|
|
125
125
|
children: attachments.map((attachment, index) => {
|
|
126
126
|
const Icon = getFileIcon(attachment.mimeType);
|
|
127
127
|
return /* @__PURE__ */ jsxs("div", {
|
|
128
|
-
className: cx("flex items-center gap-2 px-2 py-1 rounded border theme-border theme-muted-panel", attachment.error && "border-
|
|
128
|
+
className: cx("flex items-center gap-2 px-2 py-1 rounded border theme-border theme-muted-panel", attachment.error && "border-(--theme-error)"),
|
|
129
129
|
children: [
|
|
130
130
|
/* @__PURE__ */ jsx(Icon, { className: "w-4 h-4 theme-muted-text" }),
|
|
131
131
|
/* @__PURE__ */ jsx("span", {
|
|
@@ -137,22 +137,22 @@ var AttachmentUpload = forwardRef(function AttachmentUpload({ conversationId, at
|
|
|
137
137
|
children: formatBytes(attachment.size)
|
|
138
138
|
}),
|
|
139
139
|
attachment.uploading && /* @__PURE__ */ jsx("span", {
|
|
140
|
-
className: "text-xs
|
|
140
|
+
className: "text-xs theme-muted-text animate-pulse",
|
|
141
141
|
children: t.uploading
|
|
142
142
|
}),
|
|
143
143
|
attachment.error && /* @__PURE__ */ jsx("span", {
|
|
144
144
|
role: "img",
|
|
145
145
|
"aria-label": attachment.error,
|
|
146
146
|
title: attachment.error,
|
|
147
|
-
children: /* @__PURE__ */ jsx(WarningIcon, { className: "h-3.5 w-3.5 text-
|
|
147
|
+
children: /* @__PURE__ */ jsx(WarningIcon, { className: "h-3.5 w-3.5 text-(--theme-error)" })
|
|
148
148
|
}),
|
|
149
149
|
/* @__PURE__ */ jsx("button", {
|
|
150
150
|
type: "button",
|
|
151
151
|
onClick: () => handleRemove(index),
|
|
152
|
-
className: "p-0.5 rounded hover:bg-
|
|
152
|
+
className: "p-0.5 rounded hover:bg-(--theme-error-muted)",
|
|
153
153
|
title: common.remove,
|
|
154
154
|
disabled,
|
|
155
|
-
children: /* @__PURE__ */ jsx(TrashIcon, { className: "w-3 h-3 text-
|
|
155
|
+
children: /* @__PURE__ */ jsx(TrashIcon, { className: "w-3 h-3 text-(--theme-error)" })
|
|
156
156
|
})
|
|
157
157
|
]
|
|
158
158
|
}, attachment.id || index);
|
|
@@ -162,7 +162,7 @@ var AttachmentUpload = forwardRef(function AttachmentUpload({ conversationId, at
|
|
|
162
162
|
onDragOver: handleDragOver,
|
|
163
163
|
onDragLeave: handleDragLeave,
|
|
164
164
|
onDrop: handleDrop,
|
|
165
|
-
className: cx("relative block w-full border-2 border-dashed rounded-lg p-3 text-center transition-colors", isDragging ? "border-
|
|
165
|
+
className: cx("relative block w-full border-2 border-dashed rounded-lg p-3 text-center transition-colors", isDragging ? "border-(--theme-accent) bg-(--theme-accent)/10" : "theme-border hover:theme-border", disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"),
|
|
166
166
|
onClick: disabled ? void 0 : handleClick,
|
|
167
167
|
children: [/* @__PURE__ */ jsxs("span", {
|
|
168
168
|
className: "flex items-center justify-center gap-2 text-sm theme-muted-text",
|
|
@@ -173,6 +173,6 @@ var AttachmentUpload = forwardRef(function AttachmentUpload({ conversationId, at
|
|
|
173
173
|
})]
|
|
174
174
|
})]
|
|
175
175
|
})] });
|
|
176
|
-
}
|
|
176
|
+
}
|
|
177
177
|
//#endregion
|
|
178
178
|
export { AttachmentUpload as default };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Callout } from "../../components/Callout.js";
|
|
2
2
|
import { useChatConfig } from "../core/config.js";
|
|
3
3
|
import { useChat } from "../core/ChatProvider.js";
|
|
4
4
|
import { getProviderKeyFromModelId } from "./ModelSelector.js";
|
|
5
|
-
import { jsx
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
6
|
//#region src/ai/components/BlockedGroupBanner.tsx
|
|
7
7
|
function BlockedGroupBanner() {
|
|
8
8
|
const { strings } = useChatConfig();
|
|
@@ -11,18 +11,12 @@ function BlockedGroupBanner() {
|
|
|
11
11
|
const providerKey = getProviderKeyFromModelId(selectedProvider);
|
|
12
12
|
const provider = providers.find((p) => `${p.user}:${p.name}` === providerKey);
|
|
13
13
|
if (!provider?.groupBlocked || !provider.group) return null;
|
|
14
|
-
return /* @__PURE__ */
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
children: strings.blockedGroup.title.replace("{group}", provider.group)
|
|
21
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
22
|
-
className: "text-xs text-amber-600/80 mt-0.5",
|
|
23
|
-
children: strings.blockedGroup.message
|
|
24
|
-
})]
|
|
25
|
-
})]
|
|
14
|
+
return /* @__PURE__ */ jsx(Callout, {
|
|
15
|
+
tone: "warning",
|
|
16
|
+
size: "sm",
|
|
17
|
+
className: "mx-4 mt-2",
|
|
18
|
+
title: strings.blockedGroup.title(provider.group),
|
|
19
|
+
children: strings.blockedGroup.message
|
|
26
20
|
});
|
|
27
21
|
}
|
|
28
22
|
//#endregion
|