@parallelworks/ui 0.33.0 → 0.35.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 +201 -0
- package/dist/ai/components/ChatEmptyState.js +71 -86
- package/dist/ai/components/ChatInput.d.ts +11 -3
- package/dist/ai/components/ChatInput.js +19 -11
- package/dist/ai/components/ChatLayout.d.ts +23 -2
- package/dist/ai/components/ChatLayout.js +29 -12
- package/dist/ai/components/ChatSidebar.js +18 -137
- package/dist/ai/components/ChatStage.d.ts +11 -0
- package/dist/ai/components/ChatStage.js +26 -0
- package/dist/ai/components/ComposerChrome.d.ts +3 -0
- package/dist/ai/components/ComposerChrome.js +21 -2
- package/dist/ai/components/conversationActions.d.ts +23 -0
- package/dist/ai/components/conversationActions.js +108 -0
- package/dist/ai/index.d.ts +5 -4
- package/dist/ai/index.js +7 -6
- package/dist/ai/strings.d.ts +1 -0
- package/dist/ai/strings.js +2 -1
- package/dist/ai/ui/ConversationSidebar.d.ts +12 -3
- package/dist/ai/ui/ConversationSidebar.js +13 -4
- package/dist/ai/ui/sidebar.d.ts +6 -0
- package/dist/ai/ui/sidebar.js +10 -1
- package/dist/components/Dropdown.js +3 -1
- package/dist/components/FilterPill.js +1 -0
- package/dist/components/Provider.d.ts +14 -0
- package/dist/components/Provider.js +14 -3
- package/dist/components/SegmentedControl.d.ts +24 -0
- package/dist/components/SegmentedControl.js +69 -0
- package/dist/editing.d.ts +32 -0
- package/dist/editor/FormCodePanel.js +1 -1
- package/dist/engine.d.ts +3 -0
- package/dist/form/FieldWrapper.js +1 -1
- package/dist/form/Fields/CheckboxGroupField.d.ts +3 -3
- package/dist/form/Fields/CheckboxGroupField.js +10 -4
- package/dist/form/Fields/RadioField.d.ts +5 -3
- package/dist/form/Fields/RadioField.js +25 -17
- package/dist/form/Fields/fieldOption.d.ts +12 -0
- package/dist/form/Fields/fieldOption.js +17 -0
- package/dist/form/Form.d.ts +4 -2
- package/dist/form/Form.js +170 -68
- package/dist/form/FormLayout.d.ts +7 -0
- package/dist/form/FormLayout.js +144 -0
- package/dist/form/SpecialTypes/FormikCustomDropdown.js +1 -1
- package/dist/form/SpecialTypes/FormikCustomInput.js +7 -0
- package/dist/form/Wizard/WizardContainer.d.ts +1 -1
- package/dist/form/Wizard/WizardContainer.js +244 -49
- package/dist/form/Wizard/WizardNavigation.js +4 -4
- package/dist/form/Wizard/WizardStepContent.d.ts +1 -1
- package/dist/form/Wizard/WizardStepContent.js +19 -10
- package/dist/form/Wizard/WizardStepIndicator.js +35 -7
- package/dist/form/Wizard/types.d.ts +19 -6
- package/dist/form/Wizard/useWizardState.d.ts +1 -0
- package/dist/form/Wizard/useWizardState.js +5 -1
- package/dist/form/Wizard/utils.d.ts +7 -0
- package/dist/form/Wizard/utils.js +18 -1
- package/dist/form/index.d.ts +2 -0
- package/dist/form/index.js +4 -2
- package/dist/form/layout.d.ts +50 -0
- package/dist/form/layout.js +125 -0
- package/dist/form/layoutCSS.d.ts +10 -0
- package/dist/form/layoutCSS.js +234 -0
- package/dist/form/lib.d.ts +2 -0
- package/dist/form/lib.js +17 -2
- package/dist/form/types/fieldTypes.d.ts +8 -23
- package/dist/graph/DependencyGraph.d.ts +20 -1
- package/dist/graph/DependencyGraph.js +437 -142
- package/dist/graph/MatrixGroup.d.ts +3 -1
- package/dist/graph/MatrixGroup.js +31 -28
- package/dist/graph/TreeView.js +6 -6
- package/dist/graph/gridSpacing.d.ts +7 -0
- package/dist/graph/gridSpacing.js +10 -0
- package/dist/graph/util.d.ts +2 -0
- package/dist/graph/util.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -1
- package/dist/list/ListViewControls.d.ts +15 -0
- package/dist/list/ListViewControls.js +9 -3
- package/dist/list/index.d.ts +1 -1
- package/dist/list/index.js +2 -2
- package/dist/styles/base.css +3 -1
- package/dist/styles/theme.css +5 -0
- package/dist/styles.css +1 -1
- package/dist/theme/index.d.ts +2 -0
- package/dist/theme/index.js +3 -2
- package/package.json +4 -2
- package/dist/ai/components/ComposerFrame.d.ts +0 -16
- package/dist/ai/components/ComposerFrame.js +0 -33
package/README.md
CHANGED
|
@@ -87,6 +87,18 @@ The engine converts input definitions into the form's field tree, resolves
|
|
|
87
87
|
grouping, step labels and log paths. The form and graph throw without one; the
|
|
88
88
|
editor and log viewer fall back to plain behavior.
|
|
89
89
|
|
|
90
|
+
An engine can also carry `editing` (`WorkflowEditing`), the workflow model the
|
|
91
|
+
graph defers to for where jobs go: each job's slot from its `position` or a
|
|
92
|
+
stored layout, a matrix drawn as a run lists its jobs, and needs that wait for
|
|
93
|
+
any one of a matrix's jobs. A host built on a workflow package passes that
|
|
94
|
+
package's functions of the same names:
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
const engine = { ...createWorkflowEngine(), editing: workflowPackage }
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Without `editing`, the graph lays its jobs out by their needs alone.
|
|
101
|
+
|
|
90
102
|
## AI chat
|
|
91
103
|
|
|
92
104
|
`@parallelworks/ui/ai` is a chat interface (thread, composer, streaming,
|
|
@@ -141,3 +153,192 @@ animations) and streamdown's classes next to the theme contract:
|
|
|
141
153
|
```
|
|
142
154
|
|
|
143
155
|
The prebuilt `@parallelworks/ui/styles.css` already includes both.
|
|
156
|
+
|
|
157
|
+
## Form layouts
|
|
158
|
+
|
|
159
|
+
`DynamicForm` accepts an optional `$meta.layout` in `formJSONs`. A layout arranges
|
|
160
|
+
existing inputs; it never creates a value or changes an input's name, validation,
|
|
161
|
+
default, or saved-value path. Without a layout, the existing form behavior stays
|
|
162
|
+
unchanged.
|
|
163
|
+
|
|
164
|
+
```yaml
|
|
165
|
+
$meta:
|
|
166
|
+
layout:
|
|
167
|
+
type: grid
|
|
168
|
+
columns: { base: 1, md: [2, 1] }
|
|
169
|
+
gap: lg
|
|
170
|
+
children:
|
|
171
|
+
- type: field
|
|
172
|
+
field: name
|
|
173
|
+
- type: section
|
|
174
|
+
label: Resources
|
|
175
|
+
children:
|
|
176
|
+
- { type: field, field: queue }
|
|
177
|
+
- { type: field, field: nodes }
|
|
178
|
+
name: { type: string, label: Name }
|
|
179
|
+
queue: { type: dropdown, label: Queue, options: [cpu, gpu] }
|
|
180
|
+
nodes: { type: number, label: Nodes }
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Layout nodes can nest:
|
|
184
|
+
|
|
185
|
+
| Type | Properties | Behavior |
|
|
186
|
+
| --- | --- | --- |
|
|
187
|
+
| `field` | `field` | References an input name in the current scope. |
|
|
188
|
+
| `stack` | `children`, optional `gap` | Stacks children vertically. |
|
|
189
|
+
| `grid` | `children`, `columns`, optional `gap`, `align` | Arranges children in equal or weighted columns. |
|
|
190
|
+
| `section` | `children`, `label`, optional `description`, `gap` | Groups controls in a labeled fieldset. |
|
|
191
|
+
|
|
192
|
+
`columns` accepts a count from 1 to 12, or an array of positive relative weights
|
|
193
|
+
such as `[2, 1]`. For responsive layouts, use an object with `base`, `sm`, `md`,
|
|
194
|
+
and/or `lg`. These thresholds refer to the **grid container's width**: 24rem,
|
|
195
|
+
40rem, and 64rem respectively. An omitted base starts with one column; each
|
|
196
|
+
larger threshold inherits the previous value. A plain count or weight array
|
|
197
|
+
applies at every width. Nested grids measure their own available space.
|
|
198
|
+
|
|
199
|
+
Every node can set `span`, as a count or the same responsive object. A span is
|
|
200
|
+
clamped to the current grid's column count. `gap` accepts `none`, `sm`, `md`
|
|
201
|
+
(default), or `lg`. Labels default to the top within an explicit layout;
|
|
202
|
+
`$meta.labelPosition` still overrides them.
|
|
203
|
+
|
|
204
|
+
A grid defaults to `align: independent`: its children start at the top and their
|
|
205
|
+
contents flow independently. Set `align: rows` when every immediate child is a
|
|
206
|
+
section. Adjacent sections then share a header row and one row per direct child.
|
|
207
|
+
A description, help text, or validation message increases the shared row height,
|
|
208
|
+
so the following rows stay aligned. A nested grid counts as one child row.
|
|
209
|
+
The grid's gap controls shared row spacing. Hidden children retain their row slot
|
|
210
|
+
while a neighboring section uses it; empty sections disappear. When the configured
|
|
211
|
+
grid becomes one column, sections flow vertically and hidden rows collapse.
|
|
212
|
+
Tab order follows each section's document order, including when the sections stack.
|
|
213
|
+
Wrapped field labels still belong to their individual field; row alignment aligns
|
|
214
|
+
field groups, not separate label and control baselines.
|
|
215
|
+
|
|
216
|
+
```yaml
|
|
217
|
+
type: grid
|
|
218
|
+
columns: { base: 1, md: [2, 1] }
|
|
219
|
+
align: rows
|
|
220
|
+
children:
|
|
221
|
+
- type: section
|
|
222
|
+
label: Resources
|
|
223
|
+
description: Choose the capacity for this run.
|
|
224
|
+
children:
|
|
225
|
+
- { type: field, field: queue }
|
|
226
|
+
- { type: field, field: nodes }
|
|
227
|
+
- type: section
|
|
228
|
+
label: Accounting
|
|
229
|
+
children:
|
|
230
|
+
- { type: field, field: account }
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
References are local names, not dotted paths. To arrange a group's children,
|
|
234
|
+
put a layout in that group's `items.$meta`; a list uses `template.$meta` and a
|
|
235
|
+
wizard page uses `options.$meta`. Keep `$meta.wizard` on the wizard container
|
|
236
|
+
and place layouts inside its pages. Layout and wizard metadata do not share
|
|
237
|
+
the same scope.
|
|
238
|
+
|
|
239
|
+
Unreferenced inputs render after the layout in schema order. Field visibility
|
|
240
|
+
keeps its existing semantics; empty layout containers disappear, and siblings
|
|
241
|
+
remain in their declared containers. Explicit layouts take precedence over
|
|
242
|
+
field `width` and `anchor-below` hints within that scope. Those hints keep their
|
|
243
|
+
existing behavior when no explicit layout is set.
|
|
244
|
+
|
|
245
|
+
Malformed layouts, unknown references, and duplicate references fall back to
|
|
246
|
+
the existing form layout, showing each input once. Hosts can call
|
|
247
|
+
`resolveFormLayout(layout, fieldNames)` from `@parallelworks/ui/form` to get
|
|
248
|
+
structured `issues` with node paths before saving a layout. Each layout scope is
|
|
249
|
+
limited to 1,024 nodes, 128 children per container, 64 nesting levels below the
|
|
250
|
+
root, and 65,536 total CSS UTF-16 code units. Names and section labels are limited
|
|
251
|
+
to 256 code units; descriptions to 4,096. Oversized layouts fall back to ordinary
|
|
252
|
+
fields. These renderer limits supplement host limits on workflow request size,
|
|
253
|
+
parsing, expression evaluation, and the number of input scopes. Authors can edit
|
|
254
|
+
this metadata in YAML; drag-and-drop layout authoring is a separate consumer
|
|
255
|
+
of the same contract.
|
|
256
|
+
|
|
257
|
+
### Optional layout CSS
|
|
258
|
+
|
|
259
|
+
Hosts can enable `allowLayoutCSS` on `DynamicForm` (default `false`). Each layout
|
|
260
|
+
node can then supply a `css` **declaration block**:
|
|
261
|
+
|
|
262
|
+
```yaml
|
|
263
|
+
type: field
|
|
264
|
+
field: account
|
|
265
|
+
css: "max-width: 28rem; padding: 0.5rem;"
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Columns, spans, responsive breakpoints, and shared rows remain in the structured
|
|
269
|
+
layout. CSS provides spacing, sizing, alignment, surfaces, and typography. It styles
|
|
270
|
+
the content container of grids/stacks, the complete section including its heading,
|
|
271
|
+
and the wrapper of a field. Section titles accept the node's `font-size`,
|
|
272
|
+
`font-weight`, and `line-height`; field controls retain their component sizing.
|
|
273
|
+
No authored selectors target individual inputs, error messages, or surrounding UI.
|
|
274
|
+
|
|
275
|
+
The supported declarations are:
|
|
276
|
+
|
|
277
|
+
- Spacing and sizing: `gap`, `row-gap`, `column-gap`, `padding`, `padding-inline`,
|
|
278
|
+
`padding-block`, the four physical padding longhands, `width`, and `max-width`.
|
|
279
|
+
Nonnegative `px`, `rem`, `em`, `ch`, percentages, and `min()`, `max()`, or `clamp()`
|
|
280
|
+
are accepted where the CSS grammar permits. Lengths are capped at 256 units and
|
|
281
|
+
percentages at 100.
|
|
282
|
+
- Alignment: `align-items`, `justify-items`, `align-self`, `justify-self`, and
|
|
283
|
+
`text-align`. Only the supported keywords accepted by the CSS grammar apply.
|
|
284
|
+
- Surfaces: `color`, `background-color`, `border`, the four physical border
|
|
285
|
+
shorthands, `border-color`, `border-width`, `border-style`, and `border-radius`.
|
|
286
|
+
Colors are opaque three- or six-digit hex values. Borders are limited to 8px
|
|
287
|
+
or 0.5rem; radii to 32px or 2rem. Styles are solid, dashed, dotted, double, or none.
|
|
288
|
+
- Typography: `font-family` (serif, sans-serif, monospace, or the bundled
|
|
289
|
+
`"Geist Sans"` / `"Geist Mono"`), `font-size` (12–96px or 0.75–6rem),
|
|
290
|
+
`font-weight` (100–900), `font-style` (normal/italic), `line-height` (normal or 1–2),
|
|
291
|
+
and `letter-spacing` (0–2px or 0–0.1em).
|
|
292
|
+
|
|
293
|
+
For a complete local theme, supply **both** `--form-surface` and `--form-accent`
|
|
294
|
+
as opaque hex colors in the same block:
|
|
295
|
+
|
|
296
|
+
```yaml
|
|
297
|
+
type: section
|
|
298
|
+
label: Delivery
|
|
299
|
+
css: >-
|
|
300
|
+
--form-surface: #d4ed7a;
|
|
301
|
+
--form-accent: #25320f;
|
|
302
|
+
padding: 1.5rem;
|
|
303
|
+
border-radius: 1rem;
|
|
304
|
+
font-size: 1.5rem;
|
|
305
|
+
children:
|
|
306
|
+
- type: field
|
|
307
|
+
field: output
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
These two reserved declarations are color seeds, consumed by the existing theme
|
|
311
|
+
generator. It derives local text, input, focus, disabled, and status colors, plus
|
|
312
|
+
a color scheme for native controls. The generated variables live on that layout
|
|
313
|
+
node and inherit into its descendants; a child can start its own theme. Nothing
|
|
314
|
+
is written to the page root. Omitting the seeds preserves the host theme. A plain
|
|
315
|
+
`background-color` only paints the node; use the seeds when its controls also need
|
|
316
|
+
to follow the new palette. Arbitrary custom properties and direct theme-token
|
|
317
|
+
writes remain prohibited. With CSS enabled, the layout gets a paint containment boundary, so an oversized
|
|
318
|
+
surface cannot paint over adjacent application UI. A small inset preserves focus
|
|
319
|
+
rings. Popovers, tooltips, or dialogs that need to extend outside that boundary
|
|
320
|
+
must use a host-controlled portal and carry their own theme context into it.
|
|
321
|
+
Authors cannot change the boundary or portal destination through layout CSS.
|
|
322
|
+
|
|
323
|
+
CSS is parsed into an AST, checked against both this allowlist and CSS property
|
|
324
|
+
grammars, then applied through React style properties. No authored stylesheet,
|
|
325
|
+
selector, HTML, or style element is inserted. Imports, URLs, arbitrary custom properties,
|
|
326
|
+
`var()`, `attr()`, arbitrary functions, positioning, transforms, negative lengths,
|
|
327
|
+
and `!important` are rejected. Blocks are limited to 4,096 characters and 32
|
|
328
|
+
declarations. Any unsupported or malformed declaration discards the entire node's
|
|
329
|
+
CSS block; the structured layout and all input bindings continue to work.
|
|
330
|
+
`resolveLayoutCSS(css)` returns the accepted style object and diagnostic `issues`
|
|
331
|
+
for authoring tools; validate before saving and surface those issues to authors.
|
|
332
|
+
The workflow schema validates the string's shape and size; it does not authorize
|
|
333
|
+
CSS or replace runtime validation in the renderer.
|
|
334
|
+
|
|
335
|
+
This restricts the styling surface; it is not a sandbox for arbitrary CSS or a
|
|
336
|
+
security certification. Permitted sizing and color overrides can still produce an awkward or unreadable
|
|
337
|
+
form; validate authored designs for contrast and usability.
|
|
338
|
+
The host retains its CSP and its normal workflow publishing permissions. Enabling
|
|
339
|
+
this feature does not require allowing external stylesheets or weakening script
|
|
340
|
+
policy. Treat authors as trusted to design the contents of their own form: permitted
|
|
341
|
+
colors and sizing can obscure labels or make controls hard to use. Keep trusted
|
|
342
|
+
confirmation summaries, permissions, and security notices outside the authored
|
|
343
|
+
layout. Full stylesheets or mutually untrusted embedded applications require a
|
|
344
|
+
separate isolation design.
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { FaCloud } from "../../icons/fa/FaCloud.js";
|
|
2
|
-
import { FaExclamationTriangle } from "../../icons/fa/FaExclamationTriangle.js";
|
|
3
2
|
import { FaPlus } from "../../icons/fa/FaPlus.js";
|
|
4
|
-
import { FaTimes } from "../../icons/fa/FaTimes.js";
|
|
5
3
|
import { keyedByContent } from "../../components/keys.js";
|
|
6
4
|
import { useChatConfig } from "../core/config.js";
|
|
7
5
|
import { useChat } from "../core/ChatProvider.js";
|
|
8
6
|
import BlockedGroupBanner from "./BlockedGroupBanner.js";
|
|
9
7
|
import { getGreeting } from "../core/greeting.js";
|
|
10
8
|
import useDragDrop from "../core/useDragDrop.js";
|
|
9
|
+
import { NoticeCard } from "../ui/Notice.js";
|
|
11
10
|
import { ComposerControls, ConnectToolsLink } from "./ComposerChrome.js";
|
|
12
11
|
import DragOverlay from "./DragOverlay.js";
|
|
13
12
|
import ChatInput from "./ChatInput.js";
|
|
13
|
+
import ChatStage from "./ChatStage.js";
|
|
14
14
|
import ProviderIssueBanner from "./ProviderIssueBanner.js";
|
|
15
15
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
16
16
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -76,99 +76,84 @@ function ChatEmptyState({ targetSession }) {
|
|
|
76
76
|
children: [
|
|
77
77
|
/* @__PURE__ */ jsx(BlockedGroupBanner, {}),
|
|
78
78
|
/* @__PURE__ */ jsx(ProviderIssueBanner, {}),
|
|
79
|
-
sessionUnreachable && !sessionBannerDismissed && /* @__PURE__ */
|
|
80
|
-
className: "mx-4 mt-2
|
|
81
|
-
children:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
79
|
+
sessionUnreachable && !sessionBannerDismissed && /* @__PURE__ */ jsx("div", {
|
|
80
|
+
className: "mx-4 mt-2",
|
|
81
|
+
children: /* @__PURE__ */ jsxs(NoticeCard, {
|
|
82
|
+
tone: "warning",
|
|
83
|
+
onDismiss: () => setSessionBannerDismissed(true),
|
|
84
|
+
dismissLabel: strings.chrome.dismiss,
|
|
85
|
+
children: [
|
|
86
|
+
/* @__PURE__ */ jsx("span", {
|
|
87
|
+
className: "block font-medium",
|
|
87
88
|
children: t.sessionUnreachableTitle(targetSession?.split(":").pop() ?? "")
|
|
88
|
-
}),
|
|
89
|
-
|
|
89
|
+
}),
|
|
90
|
+
/* @__PURE__ */ jsx("span", {
|
|
91
|
+
className: "mt-0.5 block",
|
|
90
92
|
children: t.sessionUnreachableBody
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
/* @__PURE__ */ jsxs("div", {
|
|
94
|
-
className: "flex items-center gap-2 flex-shrink-0",
|
|
95
|
-
children: [/* @__PURE__ */ jsx("button", {
|
|
93
|
+
}),
|
|
94
|
+
/* @__PURE__ */ jsx("button", {
|
|
96
95
|
type: "button",
|
|
97
96
|
onClick: () => refreshModels(),
|
|
98
|
-
className: "
|
|
97
|
+
className: "mt-1 font-medium underline-offset-2 hover:underline",
|
|
99
98
|
children: t.retry
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
className: "text-amber-400 hover:text-amber-600 transition-colors cursor-pointer",
|
|
104
|
-
children: /* @__PURE__ */ jsx(FaTimes, { className: "w-3 h-3" })
|
|
105
|
-
})]
|
|
106
|
-
})
|
|
107
|
-
]
|
|
99
|
+
})
|
|
100
|
+
]
|
|
101
|
+
})
|
|
108
102
|
}),
|
|
109
103
|
/* @__PURE__ */ jsxs("div", {
|
|
110
104
|
className: "flex-1 flex flex-col relative bg-(--theme-panel-bg)",
|
|
111
105
|
...attachmentsAvailable ? dragHandlers : {},
|
|
112
|
-
children: [isDragging && attachmentsAvailable && /* @__PURE__ */ jsx(DragOverlay, { className: "rounded-2xl m-4" }), /* @__PURE__ */
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
className: "
|
|
124
|
-
children: t.
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
onClick: () => handleSuggestedPrompt(prompt),
|
|
164
|
-
disabled: isLoading || isLoadingModels || models.length === 0,
|
|
165
|
-
className: "-mx-3 my-1 block w-[calc(100%+1.5rem)] text-pretty rounded-lg px-3 py-2 text-left text-sm theme-muted-text transition-colors hover:chat-tint hover:theme-text disabled:opacity-50 disabled:cursor-not-allowed",
|
|
166
|
-
children: prompt
|
|
167
|
-
})
|
|
168
|
-
}, key))
|
|
169
|
-
})
|
|
170
|
-
]
|
|
171
|
-
})
|
|
106
|
+
children: [isDragging && attachmentsAvailable && /* @__PURE__ */ jsx(DragOverlay, { className: "rounded-2xl m-4" }), /* @__PURE__ */ jsxs(ChatStage, {
|
|
107
|
+
...hasLoadedModels && models.length === 0 ? {} : {
|
|
108
|
+
heading: greeting,
|
|
109
|
+
subheading: t.modernSubtitle
|
|
110
|
+
},
|
|
111
|
+
children: [
|
|
112
|
+
hasLoadedModels && models.length === 0 && /* @__PURE__ */ jsxs("div", {
|
|
113
|
+
className: "mx-auto mb-6 flex max-w-md flex-col items-center gap-3 px-8 text-center",
|
|
114
|
+
children: [
|
|
115
|
+
/* @__PURE__ */ jsx(FaCloud, { className: "h-10 w-10 theme-muted-text opacity-60" }),
|
|
116
|
+
/* @__PURE__ */ jsx("p", {
|
|
117
|
+
className: "text-lg font-medium theme-text",
|
|
118
|
+
children: t.noProvidersTitle
|
|
119
|
+
}),
|
|
120
|
+
/* @__PURE__ */ jsx("p", {
|
|
121
|
+
className: "text-sm theme-muted-text",
|
|
122
|
+
children: t.noProvidersBody
|
|
123
|
+
}),
|
|
124
|
+
extraLinks.addProvider && /* @__PURE__ */ jsxs(LinkComponent, {
|
|
125
|
+
target: {
|
|
126
|
+
kind: "external",
|
|
127
|
+
href: extraLinks.addProvider
|
|
128
|
+
},
|
|
129
|
+
className: "mt-1 inline-flex items-center gap-2 px-4 py-2 rounded-full transition-opacity text-sm font-medium theme-element hover:opacity-90",
|
|
130
|
+
children: [/* @__PURE__ */ jsx(FaPlus, { className: "h-3 w-3" }), t.addProvider]
|
|
131
|
+
})
|
|
132
|
+
]
|
|
133
|
+
}),
|
|
134
|
+
/* @__PURE__ */ jsx(ChatInput, {
|
|
135
|
+
ref: inputRef,
|
|
136
|
+
reserveContext: true,
|
|
137
|
+
onSend: handleSendMessage,
|
|
138
|
+
disabled: isLoading || isLoadingModels || models.length === 0,
|
|
139
|
+
placeholder: isLoadingModels ? tInput.placeholderLoading : hasLoadedModels && models.length === 0 ? tInput.placeholderNoProviders : tInput.placeholder,
|
|
140
|
+
settingsLeft: /* @__PURE__ */ jsx(ConnectToolsLink, {}),
|
|
141
|
+
settingsRight: /* @__PURE__ */ jsxs(Fragment$1, { children: [slots.composerUsage, /* @__PURE__ */ jsx(ComposerControls, { targetSession })] })
|
|
142
|
+
}),
|
|
143
|
+
prompts.length > 0 && !(hasLoadedModels && models.length === 0) && /* @__PURE__ */ jsx("ul", {
|
|
144
|
+
className: "mx-auto max-w-[var(--chat-column,50rem)] px-8",
|
|
145
|
+
children: keyedByContent(prompts, (p) => p).map(({ key, item: prompt }) => /* @__PURE__ */ jsx("li", {
|
|
146
|
+
className: "border-b theme-border last:border-b-0",
|
|
147
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
148
|
+
type: "button",
|
|
149
|
+
onClick: () => handleSuggestedPrompt(prompt),
|
|
150
|
+
disabled: isLoading || isLoadingModels || models.length === 0,
|
|
151
|
+
className: "-mx-3 my-1 block w-[calc(100%+1.5rem)] text-pretty rounded-lg px-3 py-2 text-left text-sm theme-muted-text transition-colors hover:chat-tint hover:theme-text disabled:opacity-50 disabled:cursor-not-allowed",
|
|
152
|
+
children: prompt
|
|
153
|
+
})
|
|
154
|
+
}, key))
|
|
155
|
+
})
|
|
156
|
+
]
|
|
172
157
|
})]
|
|
173
158
|
})
|
|
174
159
|
]
|
|
@@ -8,9 +8,6 @@ interface ChatInputProps {
|
|
|
8
8
|
placeholder?: string | undefined;
|
|
9
9
|
conversationId?: string;
|
|
10
10
|
onEditLastMessage?: () => void;
|
|
11
|
-
/** Only the box: no page padding, width cap, or settings row, for a host
|
|
12
|
-
* that lays out its own controls around it. */
|
|
13
|
-
flush?: boolean;
|
|
14
11
|
/** Send with nothing typed, for a surface where the text is optional. */
|
|
15
12
|
allowEmpty?: boolean;
|
|
16
13
|
/** Attach, paste, and drop files. Off for a target that takes text only. */
|
|
@@ -23,10 +20,21 @@ interface ChatInputProps {
|
|
|
23
20
|
sendTestId?: string;
|
|
24
21
|
/** Offers the host's slash commands as the input starts with /. */
|
|
25
22
|
slash?: SlashMenuConfig | undefined;
|
|
23
|
+
/** Cards above everything else, such as a warning about this turn. */
|
|
24
|
+
notices?: ReactNode;
|
|
25
|
+
/** Sits where a terminal's spinner line would, just above the box. */
|
|
26
|
+
activity?: ReactNode;
|
|
26
27
|
/** Pills naming where the turn runs, rendered above the input. */
|
|
27
28
|
context?: ReactNode;
|
|
29
|
+
/** Keeps the row of pills above the box even with none, so the box sits
|
|
30
|
+
* where it does on a surface that has them. */
|
|
31
|
+
reserveContext?: boolean;
|
|
32
|
+
/** Under the box, on the left. */
|
|
28
33
|
settingsLeft?: ReactNode;
|
|
34
|
+
/** In the box beside send. Defaults to the model and allocation pickers. */
|
|
29
35
|
settingsRight?: ReactNode;
|
|
36
|
+
/** One centered line under everything. */
|
|
37
|
+
hint?: string | undefined;
|
|
30
38
|
/** A turn the chat provider does not run, such as an agent session's: it
|
|
31
39
|
* drives the Stop button in place of the provider's stream and queue. */
|
|
32
40
|
turn?: {
|
|
@@ -5,7 +5,7 @@ import { useChatConfig } from "../core/config.js";
|
|
|
5
5
|
import { useChat } from "../core/ChatProvider.js";
|
|
6
6
|
import AttachmentUpload from "./AttachmentUpload.js";
|
|
7
7
|
import useDragDrop from "../core/useDragDrop.js";
|
|
8
|
-
import { ComposerContext, ComposerSettings } from "./ComposerChrome.js";
|
|
8
|
+
import { ComposerContext, ComposerControls, ComposerSettings } from "./ComposerChrome.js";
|
|
9
9
|
import DragOverlay from "./DragOverlay.js";
|
|
10
10
|
import { PasteFileCard } from "./PastedText.js";
|
|
11
11
|
import { SlashMenu, useSlashMenu } from "./SlashMenu.js";
|
|
@@ -88,7 +88,7 @@ function streamControls(chat, turn) {
|
|
|
88
88
|
queuedCount: chat.queuedMessages.length
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
|
-
var ChatInput = forwardRef(function ChatInput({ onSend, disabled = false, placeholder, conversationId, onEditLastMessage,
|
|
91
|
+
var ChatInput = forwardRef(function ChatInput({ onSend, disabled = false, placeholder, conversationId, onEditLastMessage, allowEmpty = false, attachments: attachmentsWanted = true, requireModel = true, inputLabel, sendLabel, sendTestId, slash, notices, activity, context, reserveContext = false, settingsLeft, settingsRight = /* @__PURE__ */ jsx(ComposerControls, {}), hint, turn, pastes }, ref) {
|
|
92
92
|
const [input, setInput] = useState(() => readDraft(conversationId));
|
|
93
93
|
const { cards, handleTextPaste, removeCard, takeCards, blocking: pastesBlocking } = usePasteCards(pastes, input, (value) => {
|
|
94
94
|
setInput(value);
|
|
@@ -200,7 +200,7 @@ var ChatInput = forwardRef(function ChatInput({ onSend, disabled = false, placeh
|
|
|
200
200
|
if (pastes && handleTextPaste(e.clipboardData.getData("text/plain"))) e.preventDefault();
|
|
201
201
|
};
|
|
202
202
|
const { isDragging, dragHandlers } = useDragDrop({ onFilesDropped: handleFilesDropped });
|
|
203
|
-
const toolbar = attachmentsAvailable ||
|
|
203
|
+
const toolbar = attachmentsAvailable || !!settingsRight;
|
|
204
204
|
const action = isStreaming && !input.trim() ? /* @__PURE__ */ jsx("button", {
|
|
205
205
|
type: "button",
|
|
206
206
|
onClick: stopStreaming,
|
|
@@ -217,12 +217,16 @@ var ChatInput = forwardRef(function ChatInput({ onSend, disabled = false, placeh
|
|
|
217
217
|
testId: sendTestId
|
|
218
218
|
});
|
|
219
219
|
return /* @__PURE__ */ jsxs("div", {
|
|
220
|
-
className:
|
|
220
|
+
className: "chat-composer-dock relative bg-(--theme-panel-bg) px-4 pt-2 pb-5",
|
|
221
221
|
...attachmentsAvailable ? dragHandlers : {},
|
|
222
222
|
children: [isDragging && /* @__PURE__ */ jsx(DragOverlay, { className: "rounded-2xl" }), /* @__PURE__ */ jsxs("div", {
|
|
223
|
-
className:
|
|
224
|
-
children: [
|
|
225
|
-
|
|
223
|
+
className: "mx-auto w-full max-w-[var(--chat-column,50rem)] space-y-2 px-4",
|
|
224
|
+
children: [notices, /* @__PURE__ */ jsxs("div", { children: [
|
|
225
|
+
activity && /* @__PURE__ */ jsx("div", {
|
|
226
|
+
className: "mb-2",
|
|
227
|
+
children: activity
|
|
228
|
+
}),
|
|
229
|
+
(context || reserveContext) && /* @__PURE__ */ jsx(ComposerContext, { children: context }),
|
|
226
230
|
cards.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
227
231
|
className: "mb-2 flex flex-wrap gap-2",
|
|
228
232
|
"data-testid": "composer-pastes",
|
|
@@ -285,7 +289,7 @@ var ChatInput = forwardRef(function ChatInput({ onSend, disabled = false, placeh
|
|
|
285
289
|
onClick: () => setShowAttachments(!showAttachments)
|
|
286
290
|
}), /* @__PURE__ */ jsxs("div", {
|
|
287
291
|
className: "ml-auto flex min-w-0 items-center gap-1",
|
|
288
|
-
children: [
|
|
292
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
289
293
|
className: "flex min-w-0 items-center justify-end gap-1",
|
|
290
294
|
children: settingsRight
|
|
291
295
|
}), /* @__PURE__ */ jsx("div", {
|
|
@@ -297,11 +301,15 @@ var ChatInput = forwardRef(function ChatInput({ onSend, disabled = false, placeh
|
|
|
297
301
|
]
|
|
298
302
|
})]
|
|
299
303
|
}),
|
|
300
|
-
|
|
304
|
+
(settingsLeft || uploadedCount > 0) && /* @__PURE__ */ jsx(ComposerSettings, { left: /* @__PURE__ */ jsxs(Fragment$1, { children: [settingsLeft, uploadedCount > 0 && /* @__PURE__ */ jsx("span", {
|
|
301
305
|
className: "text-[11px] theme-muted-text",
|
|
302
306
|
children: t.filesAttached(uploadedCount)
|
|
303
|
-
})] }) })
|
|
304
|
-
|
|
307
|
+
})] }) }),
|
|
308
|
+
hint && /* @__PURE__ */ jsx("p", {
|
|
309
|
+
className: "mt-1 text-center text-[11px] theme-muted-text",
|
|
310
|
+
children: hint
|
|
311
|
+
})
|
|
312
|
+
] })]
|
|
305
313
|
})]
|
|
306
314
|
});
|
|
307
315
|
});
|
|
@@ -2,12 +2,31 @@ import { type ReactNode } from 'react';
|
|
|
2
2
|
/** Where the conversation list goes: beside the thread, in a drawer over it,
|
|
3
3
|
* or whichever fits the width the chat is given. */
|
|
4
4
|
export type SidebarMode = 'inline' | 'drawer' | 'auto';
|
|
5
|
-
|
|
5
|
+
/** The list at its default width beside the narrowest thread that still
|
|
6
|
+
* reads comfortably; narrower than that, 'auto' moves the list to a drawer. */
|
|
7
|
+
export declare const DRAWER_BELOW_PX: number;
|
|
6
8
|
/** Put on the element the thread and composer sit in: their shared column
|
|
7
9
|
* grows with it, from 50rem to 64rem, instead of staying 50rem on a wide
|
|
8
10
|
* screen. */
|
|
9
11
|
export declare const CHAT_COLUMN_SCOPE = "@container [--chat-column:clamp(50rem,60cqi,64rem)]";
|
|
10
|
-
|
|
12
|
+
/** A conversation list beside a thread, or in a drawer over it when the
|
|
13
|
+
* space is narrow. The list reads where it goes from `useChat`
|
|
14
|
+
* (`sidebarPresentation`, `drawerOpen`); `ChatLayout` is this with the chat
|
|
15
|
+
* list, and a host with its own list uses it directly. Must be mounted
|
|
16
|
+
* inside a ChatProvider. */
|
|
17
|
+
export declare function ConversationLayout({ sidebar, children, sidebarMode, drawerBelowPx, drawerToggle, drawerBar, }: {
|
|
18
|
+
sidebar: ReactNode;
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
sidebarMode?: SidebarMode;
|
|
21
|
+
/** The width under which 'auto' shows the drawer. */
|
|
22
|
+
drawerBelowPx?: number;
|
|
23
|
+
/** False when the host's own header opens the drawer. */
|
|
24
|
+
drawerToggle?: boolean;
|
|
25
|
+
/** Beside the drawer's toggle, while the list is a drawer: what the top of
|
|
26
|
+
* the list would otherwise keep in view. */
|
|
27
|
+
drawerBar?: ReactNode;
|
|
28
|
+
}): import("react").JSX.Element;
|
|
29
|
+
export default function ChatLayout({ children, sidebarActions, sidebarTop, sidebarMode, drawerBelowPx, drawerToggle, drawerBar, }: {
|
|
11
30
|
children: ReactNode;
|
|
12
31
|
sidebarActions?: boolean;
|
|
13
32
|
/** Above the conversation list, given whether it is collapsed to the rail. */
|
|
@@ -19,4 +38,6 @@ export default function ChatLayout({ children, sidebarActions, sidebarTop, sideb
|
|
|
19
38
|
drawerBelowPx?: number;
|
|
20
39
|
/** False when the host's own header opens the drawer. */
|
|
21
40
|
drawerToggle?: boolean;
|
|
41
|
+
/** Beside the drawer's toggle, while the list is a drawer. */
|
|
42
|
+
drawerBar?: ReactNode;
|
|
22
43
|
}): import("react").JSX.Element;
|
|
@@ -8,7 +8,9 @@ import cx from "classnames";
|
|
|
8
8
|
import { useLayoutEffect, useRef, useState } from "react";
|
|
9
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
10
|
//#region src/ai/components/ChatLayout.tsx
|
|
11
|
-
|
|
11
|
+
/** The list at its default width beside the narrowest thread that still
|
|
12
|
+
* reads comfortably; narrower than that, 'auto' moves the list to a drawer. */
|
|
13
|
+
var DRAWER_BELOW_PX = 896;
|
|
12
14
|
/** Put on the element the thread and composer sit in: their shared column
|
|
13
15
|
* grows with it, from 50rem to 64rem, instead of staying 50rem on a wide
|
|
14
16
|
* screen. */
|
|
@@ -37,34 +39,49 @@ function useSidebarPresentation(ref, mode, drawerBelowPx) {
|
|
|
37
39
|
useLayoutEffect(() => () => report("inline"), [report]);
|
|
38
40
|
return presentation;
|
|
39
41
|
}
|
|
40
|
-
|
|
42
|
+
/** A conversation list beside a thread, or in a drawer over it when the
|
|
43
|
+
* space is narrow. The list reads where it goes from `useChat`
|
|
44
|
+
* (`sidebarPresentation`, `drawerOpen`); `ChatLayout` is this with the chat
|
|
45
|
+
* list, and a host with its own list uses it directly. Must be mounted
|
|
46
|
+
* inside a ChatProvider. */
|
|
47
|
+
function ConversationLayout({ sidebar, children, sidebarMode = "inline", drawerBelowPx = DRAWER_BELOW_PX, drawerToggle = true, drawerBar }) {
|
|
41
48
|
const ref = useRef(null);
|
|
42
49
|
const drawer = useSidebarPresentation(ref, sidebarMode, drawerBelowPx) === "drawer";
|
|
43
50
|
const { drawerOpen, toggleSidebar } = useChat();
|
|
44
51
|
const { strings } = useChatConfig();
|
|
45
|
-
return /* @__PURE__ */
|
|
52
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
46
53
|
ref,
|
|
47
54
|
className: cx("flex h-full", drawer && "relative overflow-hidden"),
|
|
48
|
-
children: [/* @__PURE__ */
|
|
49
|
-
actions: sidebarActions,
|
|
50
|
-
top: sidebarTop
|
|
51
|
-
}), /* @__PURE__ */ jsxs("main", {
|
|
55
|
+
children: [sidebar, /* @__PURE__ */ jsxs("main", {
|
|
52
56
|
inert: drawer && drawerOpen,
|
|
53
57
|
className: cx("flex min-w-0 flex-1 flex-col", CHAT_COLUMN_SCOPE),
|
|
54
|
-
children: [drawer && drawerToggle && /* @__PURE__ */
|
|
55
|
-
className: "flex shrink-0 items-center bg-(--theme-panel-bg) px-2 pt-2",
|
|
56
|
-
children: /* @__PURE__ */ jsx(SidebarToggle, {
|
|
58
|
+
children: [drawer && (drawerToggle || drawerBar) && /* @__PURE__ */ jsxs("div", {
|
|
59
|
+
className: "flex shrink-0 items-center gap-2 bg-(--theme-panel-bg) px-2 pt-2",
|
|
60
|
+
children: [drawerToggle && /* @__PURE__ */ jsx(SidebarToggle, {
|
|
57
61
|
collapsed: !drawerOpen,
|
|
58
62
|
onToggle: toggleSidebar,
|
|
59
63
|
openLabel: strings.sidebar.openSidebar,
|
|
60
64
|
closeLabel: strings.sidebar.closeSidebar
|
|
61
|
-
})
|
|
65
|
+
}), drawerBar]
|
|
62
66
|
}), /* @__PURE__ */ jsx("div", {
|
|
63
67
|
className: "flex min-h-0 flex-1 flex-col",
|
|
64
68
|
children
|
|
65
69
|
})]
|
|
66
70
|
})]
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function ChatLayout({ children, sidebarActions = true, sidebarTop, sidebarMode = "inline", drawerBelowPx = DRAWER_BELOW_PX, drawerToggle = true, drawerBar }) {
|
|
74
|
+
return /* @__PURE__ */ jsx(KeyboardShortcutsProvider, { children: /* @__PURE__ */ jsx(ConversationLayout, {
|
|
75
|
+
sidebar: /* @__PURE__ */ jsx(ChatSidebar, {
|
|
76
|
+
actions: sidebarActions,
|
|
77
|
+
top: sidebarTop
|
|
78
|
+
}),
|
|
79
|
+
sidebarMode,
|
|
80
|
+
drawerBelowPx,
|
|
81
|
+
drawerToggle,
|
|
82
|
+
drawerBar,
|
|
83
|
+
children
|
|
67
84
|
}) });
|
|
68
85
|
}
|
|
69
86
|
//#endregion
|
|
70
|
-
export { CHAT_COLUMN_SCOPE, DRAWER_BELOW_PX, ChatLayout as default };
|
|
87
|
+
export { CHAT_COLUMN_SCOPE, ConversationLayout, DRAWER_BELOW_PX, ChatLayout as default };
|