@calliopelabs/ui 0.12.0 → 0.14.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 +68 -3
- package/dist/agent/AgentChat.d.ts +56 -0
- package/dist/agent/AgentChat.js +76 -0
- package/dist/agent/AgentLauncher.d.ts +25 -0
- package/dist/agent/AgentLauncher.js +25 -0
- package/dist/agent/AgentProvider.d.ts +82 -0
- package/dist/agent/AgentProvider.js +176 -0
- package/dist/agent/CalliopeAgent.d.ts +43 -0
- package/dist/agent/CalliopeAgent.js +111 -0
- package/dist/agent/controller.d.ts +148 -0
- package/dist/agent/controller.js +463 -0
- package/dist/agent/copilotkit/CopilotKitAgentProvider.d.ts +41 -0
- package/dist/agent/copilotkit/CopilotKitAgentProvider.js +145 -0
- package/dist/agent/copilotkit/index.d.ts +6 -0
- package/dist/agent/copilotkit/index.js +6 -0
- package/dist/agent/engine.d.ts +41 -0
- package/dist/agent/engine.js +93 -0
- package/dist/agent/index.d.ts +15 -0
- package/dist/agent/index.js +15 -0
- package/dist/agent/mock.d.ts +34 -0
- package/dist/agent/mock.js +112 -0
- package/dist/agent/surface.d.ts +30 -0
- package/dist/agent/surface.js +90 -0
- package/dist/agent/tool.d.ts +98 -0
- package/dist/agent/tool.js +81 -0
- package/dist/appearance/brands.d.ts +14 -12
- package/dist/appearance/brands.js +18 -51
- package/dist/chat/AgentProgress.d.ts +19 -0
- package/dist/chat/AgentProgress.js +34 -0
- package/dist/chat/Composer.d.ts +31 -0
- package/dist/chat/Composer.js +44 -0
- package/dist/chat/MessageBubble.d.ts +23 -0
- package/dist/chat/MessageBubble.js +26 -0
- package/dist/chat/MessageList.d.ts +36 -0
- package/dist/chat/MessageList.js +68 -0
- package/dist/chat/ModelPicker.d.ts +17 -0
- package/dist/chat/ModelPicker.js +13 -0
- package/dist/chat/ToolCallCard.d.ts +27 -0
- package/dist/chat/ToolCallCard.js +52 -0
- package/dist/chat/model.d.ts +123 -0
- package/dist/chat/model.js +337 -0
- package/dist/chat/patch.d.ts +12 -0
- package/dist/chat/patch.js +149 -0
- package/dist/chat/useAgUiRun.d.ts +52 -0
- package/dist/chat/useAgUiRun.js +137 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.js +11 -1
- package/dist/primitives/Button.d.ts +1 -1
- package/dist/provider/messages.d.ts +83 -1
- package/dist/provider/messages.js +91 -0
- package/dist/theme/mappings/palette.d.ts +52 -0
- package/dist/theme/mappings/palette.js +208 -0
- package/dist/theme/mappings/render.d.ts +9 -0
- package/dist/theme/mappings/render.js +520 -0
- package/dist/theme/mappings/targets.d.ts +43 -0
- package/dist/theme/mappings/targets.js +222 -0
- package/dist/theme/tokens.d.ts +10 -9
- package/dist/theme/tokens.generated.d.ts +353 -0
- package/dist/theme/tokens.generated.js +357 -0
- package/dist/theme/tokens.js +20 -264
- package/dist/tokens/README.jupyterlab.md +125 -0
- package/dist/tokens/README.md +15 -0
- package/dist/tokens/README.native.md +29 -0
- package/dist/tokens/README.plain.md +84 -0
- package/dist/tokens/README.python.md +33 -0
- package/dist/tokens/README.vscode.md +114 -0
- package/dist/tokens/jupyterlab.css +1710 -0
- package/dist/tokens/plain.css +930 -0
- package/dist/tokens/tokens.native.d.ts +85 -0
- package/dist/tokens/tokens.native.js +773 -0
- package/dist/tokens/tokens.py +1043 -0
- package/dist/tokens/vscode.css +1138 -0
- package/dist/workflow/canvas/WorkflowCanvas.js +41 -1
- package/dist/workflow/definition.d.ts +20 -0
- package/dist/workflow/definition.js +44 -0
- package/dist/workflow/editor/WorkflowInspector.d.ts +21 -3
- package/dist/workflow/editor/WorkflowInspector.js +13 -5
- package/dist/workflow/editor/WorkflowWorkspace.d.ts +28 -3
- package/dist/workflow/editor/WorkflowWorkspace.js +75 -16
- package/package.json +35 -2
|
@@ -191,6 +191,35 @@ function CanvasInner({ definition, flow: flowProp, registry, onChange, readOnly:
|
|
|
191
191
|
const y = b.height * zoom <= h - pad * 2 ? (h - b.height * zoom) / 2 - b.y * zoom : pad - b.y * zoom;
|
|
192
192
|
void view.setViewport({ x, y, zoom });
|
|
193
193
|
}, [view]);
|
|
194
|
+
/**
|
|
195
|
+
* Centers a node that is out of view at the current zoom. `whole`: unless
|
|
196
|
+
* all of it shows (a deep link); otherwise unless its middle shows, so
|
|
197
|
+
* choosing a node at the edge does not move the canvas.
|
|
198
|
+
*/
|
|
199
|
+
const reveal = React.useCallback((id, whole = false) => {
|
|
200
|
+
const el = pane.current;
|
|
201
|
+
const n = view.getInternalNode(id);
|
|
202
|
+
if (!el || !n || !el.clientWidth || !el.clientHeight)
|
|
203
|
+
return;
|
|
204
|
+
const { x, y, zoom } = view.getViewport();
|
|
205
|
+
const w = n.measured.width ?? 0;
|
|
206
|
+
const h = n.measured.height ?? 0;
|
|
207
|
+
const left = n.internals.positionAbsolute.x * zoom + x;
|
|
208
|
+
const top = n.internals.positionAbsolute.y * zoom + y;
|
|
209
|
+
const [x0, y0, x1, y1] = whole
|
|
210
|
+
? [left, top, left + w * zoom, top + h * zoom]
|
|
211
|
+
: [
|
|
212
|
+
left + (w * zoom) / 2,
|
|
213
|
+
top + (h * zoom) / 2,
|
|
214
|
+
left + (w * zoom) / 2,
|
|
215
|
+
top + (h * zoom) / 2,
|
|
216
|
+
];
|
|
217
|
+
if (x0 >= 0 && y0 >= 0 && x1 <= el.clientWidth && y1 <= el.clientHeight)
|
|
218
|
+
return;
|
|
219
|
+
const cx = n.internals.positionAbsolute.x + w / 2;
|
|
220
|
+
const cy = n.internals.positionAbsolute.y + h / 2;
|
|
221
|
+
void view.setCenter(cx, cy, { zoom });
|
|
222
|
+
}, [view]);
|
|
194
223
|
const flowName = flowProp && definition.flows[flowProp] ? flowProp : definition.main;
|
|
195
224
|
const flow = definition.flows[flowName] ?? EMPTY_FLOW;
|
|
196
225
|
const readOnly = readOnlyProp || !onChange;
|
|
@@ -218,6 +247,17 @@ function CanvasInner({ definition, flow: flowProp, registry, onChange, readOnly:
|
|
|
218
247
|
const selectedNode = selection?.type === "node" && selection.flow === flowName
|
|
219
248
|
? flow.nodes.find((n) => n.id === selection.id)
|
|
220
249
|
: undefined;
|
|
250
|
+
// A node selected from outside the canvas (a deep link, a problem) comes into view.
|
|
251
|
+
const selectedId = selectedNode?.id;
|
|
252
|
+
React.useEffect(() => {
|
|
253
|
+
if (selectedId)
|
|
254
|
+
reveal(selectedId);
|
|
255
|
+
}, [selectedId, reveal]);
|
|
256
|
+
const init = React.useCallback(() => {
|
|
257
|
+
place();
|
|
258
|
+
if (selectedId)
|
|
259
|
+
reveal(selectedId, true);
|
|
260
|
+
}, [place, reveal, selectedId]);
|
|
221
261
|
const selectedEdge = selection?.type === "edge" && selection.flow === flowName
|
|
222
262
|
? flow.edges.find((e) => edgeId(e) === selection.id)
|
|
223
263
|
: undefined;
|
|
@@ -624,7 +664,7 @@ function CanvasInner({ definition, flow: flowProp, registry, onChange, readOnly:
|
|
|
624
664
|
: m.kind(item.template.kind)), item.available ? null : (_jsx("span", { className: "ml-auto text-2xs text-muted-foreground", children: m.notRun }))] }, item.key)))] }, g.key))) })] }), _jsxs(Button, { size: "sm", variant: "outline", disabled: !selectedNode, onClick: () => setConnecting(true), children: [_jsx(CableIcon, { "aria-hidden": true }), m.connectAction] }), _jsxs(Button, { size: "sm", variant: "outline", disabled: !selectedNode && !selectedEdge, onClick: () => remove(selectedNode ? [selectedNode.id] : [], selectedEdge ? [edgeId(selectedEdge)] : []), children: [_jsx(Trash2Icon, { "aria-hidden": true }), m.deleteSelection] }), _jsxs(Button, { size: "sm", variant: "outline", disabled: !flow.nodes.length, onClick: () => {
|
|
625
665
|
change(arrangeFlow(flow, registry, definition));
|
|
626
666
|
window.requestAnimationFrame(place);
|
|
627
|
-
}, children: [_jsx(LayoutGridIcon, { "aria-hidden": true }), m.arrange] })] })), _jsxs("span", { className: "ml-auto flex items-center gap-1", children: [_jsx(Button, { size: "icon-sm", variant: "ghost", "aria-label": m.zoomOut, title: m.zoomOut, onClick: () => void view.zoomOut(), children: _jsx(ZoomOutIcon, { "aria-hidden": true }) }), _jsx(Button, { size: "icon-sm", variant: "ghost", "aria-label": m.zoomIn, title: m.zoomIn, onClick: () => void view.zoomIn(), children: _jsx(ZoomInIcon, { "aria-hidden": true }) }), _jsx(Button, { size: "icon-sm", variant: "ghost", "aria-label": m.fit, title: m.fit, onClick: place, children: _jsx(MaximizeIcon, { "aria-hidden": true }) })] })] }), _jsx("div", { ref: pane, className: "relative min-h-0 flex-1", children: _jsx(ReactFlow, { nodes: nodes, edges: built.edges, nodeTypes: NODE_TYPES, onInit:
|
|
667
|
+
}, children: [_jsx(LayoutGridIcon, { "aria-hidden": true }), m.arrange] })] })), _jsxs("span", { className: "ml-auto flex items-center gap-1", children: [_jsx(Button, { size: "icon-sm", variant: "ghost", "aria-label": m.zoomOut, title: m.zoomOut, onClick: () => void view.zoomOut(), children: _jsx(ZoomOutIcon, { "aria-hidden": true }) }), _jsx(Button, { size: "icon-sm", variant: "ghost", "aria-label": m.zoomIn, title: m.zoomIn, onClick: () => void view.zoomIn(), children: _jsx(ZoomInIcon, { "aria-hidden": true }) }), _jsx(Button, { size: "icon-sm", variant: "ghost", "aria-label": m.fit, title: m.fit, onClick: place, children: _jsx(MaximizeIcon, { "aria-hidden": true }) })] })] }), _jsx("div", { ref: pane, className: "relative min-h-0 flex-1", children: _jsx(ReactFlow, { nodes: nodes, edges: built.edges, nodeTypes: NODE_TYPES, onInit: init, minZoom: READABLE_ZOOM, maxZoom: 1.5, panOnScroll: true, snapToGrid: true, snapGrid: [GRID, GRID], nodesDraggable: !readOnly, nodesConnectable: !readOnly, nodesFocusable: true, edgesFocusable: true, elementsSelectable: true, multiSelectionKeyCode: null, selectionKeyCode: null, deleteKeyCode: readOnly ? null : ["Backspace", "Delete"], ariaLabelConfig: {
|
|
628
668
|
"node.a11yDescription.default": m.nodeHelp,
|
|
629
669
|
"node.a11yDescription.keyboardDisabled": m.nodeHelp,
|
|
630
670
|
"node.a11yDescription.ariaLiveMessage": ({ direction, x, y }) => m.movedLive(direction, x, y),
|
|
@@ -405,4 +405,24 @@ export interface WorkflowSelection {
|
|
|
405
405
|
flow: string;
|
|
406
406
|
id: string;
|
|
407
407
|
}
|
|
408
|
+
/** A flow's name, its key in `flows`: a lowercase slug of up to 32 characters ("build", "escalation"). */
|
|
409
|
+
export declare const WORKFLOW_FLOW_NAME: RegExp;
|
|
410
|
+
/** The most flows one definition holds. */
|
|
411
|
+
export declare const WORKFLOW_MAX_FLOWS = 20;
|
|
412
|
+
/** Why a flow name is refused. The editor's copy names each one. */
|
|
413
|
+
export type WorkflowFlowNameProblem = "empty" | "format" | "taken";
|
|
414
|
+
/**
|
|
415
|
+
* Whether `name` can name a flow of the definition: a slug by
|
|
416
|
+
* `WORKFLOW_FLOW_NAME` that no other flow has. `current` is the flow being
|
|
417
|
+
* renamed, which may keep its own name. Null when it can.
|
|
418
|
+
*/
|
|
419
|
+
export declare function flowNameProblem(definition: WorkflowDefinition, name: string, current?: string): WorkflowFlowNameProblem | null;
|
|
420
|
+
/** The definition with a new, empty flow. A label that is empty is left out. */
|
|
421
|
+
export declare function addFlow(definition: WorkflowDefinition, name: string, label?: string): WorkflowDefinition;
|
|
422
|
+
/**
|
|
423
|
+
* Renames a flow everywhere the definition names it: its key, `main`, and
|
|
424
|
+
* the sub-flow nodes that run it. `label` replaces the flow's label; an empty
|
|
425
|
+
* one removes it; omitted, the label stays. Flows keep their order.
|
|
426
|
+
*/
|
|
427
|
+
export declare function renameFlow(definition: WorkflowDefinition, from: string, to: string, label?: string): WorkflowDefinition;
|
|
408
428
|
export {};
|
|
@@ -409,3 +409,47 @@ export function updateEdge(flow, id, patch) {
|
|
|
409
409
|
export function withFlow(definition, name, flow) {
|
|
410
410
|
return { ...definition, flows: { ...definition.flows, [name]: flow } };
|
|
411
411
|
}
|
|
412
|
+
// ---------------------------------------------------------------- flows
|
|
413
|
+
/** A flow's name, its key in `flows`: a lowercase slug of up to 32 characters ("build", "escalation"). */
|
|
414
|
+
export const WORKFLOW_FLOW_NAME = /^[a-z][a-z0-9-]{0,31}$/;
|
|
415
|
+
/** The most flows one definition holds. */
|
|
416
|
+
export const WORKFLOW_MAX_FLOWS = 20;
|
|
417
|
+
/**
|
|
418
|
+
* Whether `name` can name a flow of the definition: a slug by
|
|
419
|
+
* `WORKFLOW_FLOW_NAME` that no other flow has. `current` is the flow being
|
|
420
|
+
* renamed, which may keep its own name. Null when it can.
|
|
421
|
+
*/
|
|
422
|
+
export function flowNameProblem(definition, name, current) {
|
|
423
|
+
if (!name)
|
|
424
|
+
return "empty";
|
|
425
|
+
if (!WORKFLOW_FLOW_NAME.test(name))
|
|
426
|
+
return "format";
|
|
427
|
+
if (name !== current && Object.hasOwn(definition.flows, name))
|
|
428
|
+
return "taken";
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
/** The definition with a new, empty flow. A label that is empty is left out. */
|
|
432
|
+
export function addFlow(definition, name, label) {
|
|
433
|
+
const flow = { ...(label ? { label } : {}), nodes: [], edges: [] };
|
|
434
|
+
return withFlow(definition, name, flow);
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Renames a flow everywhere the definition names it: its key, `main`, and
|
|
438
|
+
* the sub-flow nodes that run it. `label` replaces the flow's label; an empty
|
|
439
|
+
* one removes it; omitted, the label stays. Flows keep their order.
|
|
440
|
+
*/
|
|
441
|
+
export function renameFlow(definition, from, to, label) {
|
|
442
|
+
if (!definition.flows[from])
|
|
443
|
+
return definition;
|
|
444
|
+
const flows = {};
|
|
445
|
+
for (const [name, f] of Object.entries(definition.flows)) {
|
|
446
|
+
const nodes = f.nodes.map((n) => n.kind === "subflow" && n.flow === from ? { ...n, flow: to } : n);
|
|
447
|
+
let flow = { ...f, nodes };
|
|
448
|
+
if (name === from && label !== undefined) {
|
|
449
|
+
const { label: _old, ...rest } = flow;
|
|
450
|
+
flow = label ? { ...rest, label } : rest;
|
|
451
|
+
}
|
|
452
|
+
flows[name === from ? to : name] = flow;
|
|
453
|
+
}
|
|
454
|
+
return { ...definition, main: definition.main === from ? to : definition.main, flows };
|
|
455
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { type WorkflowCondition, type WorkflowDefinition, type WorkflowFlow, type WorkflowGatePolicy, type WorkflowRegistry, type WorkflowSelection } from "../definition.js";
|
|
2
|
+
import { type WorkflowCondition, type WorkflowDefinition, type WorkflowFlow, type WorkflowGatePolicy, type WorkflowNode, type WorkflowRegistry, type WorkflowSelection } from "../definition.js";
|
|
3
3
|
export interface WorkflowInspectorProps {
|
|
4
4
|
definition: WorkflowDefinition;
|
|
5
5
|
registry: WorkflowRegistry;
|
|
@@ -9,8 +9,25 @@ export interface WorkflowInspectorProps {
|
|
|
9
9
|
/** After a delete from the inspector, the selection is cleared through this. */
|
|
10
10
|
onSelectionChange?: (selection: WorkflowSelection | null) => void;
|
|
11
11
|
readOnly?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* The host's own fields for the selected node, after the fields of its
|
|
14
|
+
* kind: a runner picker, what a step inherits. Switch on `node.kind`;
|
|
15
|
+
* return null to add nothing. It is called while the inspector renders, so
|
|
16
|
+
* fields that need hooks go in a component it returns.
|
|
17
|
+
*/
|
|
18
|
+
renderNodeFields?: (context: WorkflowNodeFieldsContext) => React.ReactNode;
|
|
12
19
|
className?: string;
|
|
13
20
|
}
|
|
21
|
+
/** What the inspector hands `renderNodeFields`. */
|
|
22
|
+
export interface WorkflowNodeFieldsContext {
|
|
23
|
+
node: WorkflowNode;
|
|
24
|
+
/** The flow the node is in. */
|
|
25
|
+
flow: string;
|
|
26
|
+
definition: WorkflowDefinition;
|
|
27
|
+
/** Patches the node, as the inspector's own fields do. Does nothing when read only. */
|
|
28
|
+
onChange: (patch: Partial<WorkflowNode>) => void;
|
|
29
|
+
readOnly: boolean;
|
|
30
|
+
}
|
|
14
31
|
/** One comparison on a run fact: the fact, its node when it is a node's, the operator and the value. */
|
|
15
32
|
export declare function ConditionEditor({ condition, onChange, flow, registry, label, disabled, }: {
|
|
16
33
|
condition: WorkflowCondition;
|
|
@@ -34,7 +51,8 @@ export declare function GatePolicyEditor({ policy, onChange, registry, approvers
|
|
|
34
51
|
* The selected node's or connection's fields, edited in place: per-kind
|
|
35
52
|
* fields, a condition editor over the host's facts and operators, a gate
|
|
36
53
|
* policy editor (roles, members, quorum, timeout), and the host's model,
|
|
37
|
-
* harness and effort pickers
|
|
54
|
+
* harness and effort pickers, then any fields the host adds for the node
|
|
55
|
+
* (`renderNodeFields`). With nothing selected, the workflow's name and
|
|
38
56
|
* description. Every edit goes out as the whole next definition.
|
|
39
57
|
*/
|
|
40
|
-
export declare function WorkflowInspector({ definition, registry, selection, onChange, onSelectionChange, readOnly, className, }: WorkflowInspectorProps): React.JSX.Element;
|
|
58
|
+
export declare function WorkflowInspector({ definition, registry, selection, onChange, onSelectionChange, readOnly, renderNodeFields, className, }: WorkflowInspectorProps): React.JSX.Element;
|
|
@@ -149,10 +149,11 @@ function PortsEditor({ legend, ports, onChange, registry, disabled, }) {
|
|
|
149
149
|
* The selected node's or connection's fields, edited in place: per-kind
|
|
150
150
|
* fields, a condition editor over the host's facts and operators, a gate
|
|
151
151
|
* policy editor (roles, members, quorum, timeout), and the host's model,
|
|
152
|
-
* harness and effort pickers
|
|
152
|
+
* harness and effort pickers, then any fields the host adds for the node
|
|
153
|
+
* (`renderNodeFields`). With nothing selected, the workflow's name and
|
|
153
154
|
* description. Every edit goes out as the whole next definition.
|
|
154
155
|
*/
|
|
155
|
-
export function WorkflowInspector({ definition, registry, selection = null, onChange, onSelectionChange, readOnly = false, className, }) {
|
|
156
|
+
export function WorkflowInspector({ definition, registry, selection = null, onChange, onSelectionChange, readOnly = false, renderNodeFields, className, }) {
|
|
156
157
|
const { messages } = useUI();
|
|
157
158
|
const m = messages.editor;
|
|
158
159
|
const disabled = readOnly || !onChange;
|
|
@@ -165,7 +166,14 @@ export function WorkflowInspector({ definition, registry, selection = null, onCh
|
|
|
165
166
|
let title;
|
|
166
167
|
if (node) {
|
|
167
168
|
title = m.inspectorFor(m.kind(node.kind), node.label);
|
|
168
|
-
|
|
169
|
+
const patch = (p) => setFlow(updateNode(flow, node.id, p));
|
|
170
|
+
body = (_jsx(NodeFields, { node: node, flow: flow, definition: definition, registry: registry, disabled: disabled, host: renderNodeFields?.({
|
|
171
|
+
node,
|
|
172
|
+
flow: flowName,
|
|
173
|
+
definition,
|
|
174
|
+
onChange: disabled ? () => { } : patch,
|
|
175
|
+
readOnly: disabled,
|
|
176
|
+
}), onPatch: patch, onDelete: () => {
|
|
169
177
|
setFlow(removeNode(flow, node.id));
|
|
170
178
|
onSelectionChange?.(null);
|
|
171
179
|
} }));
|
|
@@ -187,7 +195,7 @@ export function WorkflowInspector({ definition, registry, selection = null, onCh
|
|
|
187
195
|
}
|
|
188
196
|
return (_jsxs("section", { "aria-label": m.inspector, "data-slot": "workflow-inspector", className: cn("flex min-h-0 flex-col gap-3", className), children: [_jsx("h2", { className: "truncate text-sm font-medium", children: title }), _jsx("div", { className: "flex min-h-0 flex-col gap-4 overflow-y-auto pb-2", children: body })] }));
|
|
189
197
|
}
|
|
190
|
-
function NodeFields({ node, flow, definition, registry, disabled, onPatch, onDelete, }) {
|
|
198
|
+
function NodeFields({ node, flow, definition, registry, disabled, host, onPatch, onDelete, }) {
|
|
191
199
|
const { messages } = useUI();
|
|
192
200
|
const m = messages.editor;
|
|
193
201
|
const presetOptions = registry.modules.flatMap((mod) => mod.presets.map((p) => ({ value: `${mod.id}/${p.id}`, label: `${mod.label} · ${p.label}` })));
|
|
@@ -327,7 +335,7 @@ function NodeFields({ node, flow, definition, registry, disabled, onPatch, onDel
|
|
|
327
335
|
fields = (_jsxs(_Fragment, { children: [_jsx(Field, { label: m.question, children: _jsx(Textarea, { value: node.question, disabled: disabled, onChange: (e) => onPatch({ question: e.target.value }) }) }), _jsx(Field, { label: m.yesAt, children: _jsx(PercentInput, { value: node.threshold, disabled: disabled, onValue: (threshold) => onPatch({ threshold: threshold ?? 0.5 }) }) })] }));
|
|
328
336
|
break;
|
|
329
337
|
}
|
|
330
|
-
return (_jsxs("div", { className: "flex flex-col gap-4", children: [common, fields, _jsx(Field, { label: m.note, children: _jsx(Textarea, { value: node.note ?? "", disabled: disabled, onChange: (e) => onPatch({ note: e.target.value || undefined }) }) }), disabled ? null : (_jsxs(Button, { type: "button", variant: "destructive", size: "sm", className: "self-start", onClick: onDelete, children: [_jsx(Trash2Icon, { "aria-hidden": true }), m.deleteNode(node.label)] }))] }));
|
|
338
|
+
return (_jsxs("div", { className: "flex flex-col gap-4", children: [common, fields, host == null || host === false ? null : (_jsx("div", { "data-slot": "workflow-inspector-host-fields", className: "flex flex-col gap-4", children: host })), _jsx(Field, { label: m.note, children: _jsx(Textarea, { value: node.note ?? "", disabled: disabled, onChange: (e) => onPatch({ note: e.target.value || undefined }) }) }), disabled ? null : (_jsxs(Button, { type: "button", variant: "destructive", size: "sm", className: "self-start", onClick: onDelete, children: [_jsx(Trash2Icon, { "aria-hidden": true }), m.deleteNode(node.label)] }))] }));
|
|
331
339
|
}
|
|
332
340
|
/** A preset's contract, read from the registry. */
|
|
333
341
|
function PortSummary({ node, definition, registry, }) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import type
|
|
2
|
+
import { type WorkflowDefinition, type WorkflowRegistry, type WorkflowSelection } from "../definition.js";
|
|
3
3
|
import { type WorkflowImportSummary as ImportPreview } from "../portable.js";
|
|
4
4
|
import { type WorkflowRevision } from "../workspace.js";
|
|
5
|
+
import { type WorkflowInspectorProps } from "./WorkflowInspector.js";
|
|
5
6
|
/** A host action: may return a promise; a rejection's message is shown. */
|
|
6
7
|
type HostAction<A extends unknown[]> = (...args: A) => Promise<unknown> | unknown;
|
|
7
8
|
export interface WorkflowWorkspaceProps {
|
|
@@ -26,6 +27,28 @@ export interface WorkflowWorkspaceProps {
|
|
|
26
27
|
importPreview?: ImportPreview | null;
|
|
27
28
|
onImportConfirm?: HostAction<[]>;
|
|
28
29
|
onImportCancel?: () => void;
|
|
30
|
+
/**
|
|
31
|
+
* What is selected, held by the host: a deep link to a node opens its flow
|
|
32
|
+
* with the node selected and in view. Uncontrolled when omitted.
|
|
33
|
+
*/
|
|
34
|
+
selection?: WorkflowSelection | null;
|
|
35
|
+
/** Where the editor opens when the host does not hold the selection. */
|
|
36
|
+
defaultSelection?: WorkflowSelection | null;
|
|
37
|
+
onSelectionChange?: (selection: WorkflowSelection | null) => void;
|
|
38
|
+
/**
|
|
39
|
+
* Offers New flow. The workspace asks for a name (`WORKFLOW_FLOW_NAME`, not
|
|
40
|
+
* taken) and an optional label, and hands the host the definition with the
|
|
41
|
+
* flow added. The host applies it, with anything it keeps beside it.
|
|
42
|
+
*/
|
|
43
|
+
onFlowCreate?: HostAction<[definition: WorkflowDefinition, name: string]>;
|
|
44
|
+
/**
|
|
45
|
+
* Offers Rename flow, for the open flow. The definition handed over has the
|
|
46
|
+
* flow renamed everywhere it names it (`renameFlow`): its key, `main` and
|
|
47
|
+
* the sub-flow nodes that run it. Anything else that names flows is the host's.
|
|
48
|
+
*/
|
|
49
|
+
onFlowRename?: HostAction<[definition: WorkflowDefinition, from: string, to: string]>;
|
|
50
|
+
/** The host's own fields for the selected node in the inspector. */
|
|
51
|
+
renderNodeFields?: WorkflowInspectorProps["renderNodeFields"];
|
|
29
52
|
readOnly?: boolean;
|
|
30
53
|
loading?: boolean;
|
|
31
54
|
error?: string | {
|
|
@@ -42,8 +65,10 @@ export interface WorkflowWorkspaceProps {
|
|
|
42
65
|
* there are no edits, and when the saved revision moves under unsaved edits
|
|
43
66
|
* it stops and asks (use theirs, keep mine, compare). Publishing shows the
|
|
44
67
|
* draft against live first and is held while anything blocks. Save, publish,
|
|
45
|
-
* save as, import and export are the host's; it never persists anything.
|
|
46
|
-
*
|
|
68
|
+
* save as, import and export are the host's; it never persists anything. A
|
|
69
|
+
* host may hold the selection (deep links), let people add and rename flows,
|
|
70
|
+
* and add its own fields to the inspector. On narrow containers the palette
|
|
71
|
+
* and the side panel open as drawers.
|
|
47
72
|
*/
|
|
48
73
|
export declare function WorkflowWorkspace(props: WorkflowWorkspaceProps): React.JSX.Element;
|
|
49
74
|
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { CopyPlusIcon, DownloadIcon, ListTreeIcon, PanelRightIcon, RocketIcon, SaveIcon, UndoIcon, UploadIcon, } from "lucide-react";
|
|
3
|
+
import { CopyPlusIcon, DownloadIcon, ListTreeIcon, PanelRightIcon, PencilIcon, PlusIcon, RocketIcon, SaveIcon, UndoIcon, UploadIcon, } from "lucide-react";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import { cn } from "../../lib/cn.js";
|
|
6
6
|
import { ConfirmDialog } from "../../overlays/ConfirmDialog.js";
|
|
@@ -16,6 +16,7 @@ import { ErrorState } from "../../states/ErrorState.js";
|
|
|
16
16
|
import { LoadingState } from "../../states/LoadingState.js";
|
|
17
17
|
import { StatusBadge } from "../../status/StatusBadge.js";
|
|
18
18
|
import { WorkflowCanvas } from "../canvas/WorkflowCanvas.js";
|
|
19
|
+
import { WORKFLOW_MAX_FLOWS, addFlow, flowNameProblem, renameFlow, } from "../definition.js";
|
|
19
20
|
import { insertPaletteItem } from "../palette.js";
|
|
20
21
|
import { diffDefinitions, sameDefinition, } from "../portable.js";
|
|
21
22
|
import { blockingProblems, checkDefinition } from "../problems.js";
|
|
@@ -26,6 +27,7 @@ import { WorkflowInspector } from "./WorkflowInspector.js";
|
|
|
26
27
|
import { WorkflowJsonView } from "./WorkflowJsonView.js";
|
|
27
28
|
import { WorkflowPalette } from "./WorkflowPalette.js";
|
|
28
29
|
import { WorkflowProblems } from "./WorkflowProblems.js";
|
|
30
|
+
const sameSelection = (a, b) => a === b || (!!a && !!b && a.type === b.type && a.flow === b.flow && a.id === b.id);
|
|
29
31
|
const messageOf = (e) => (e instanceof Error ? e.message : typeof e === "string" ? e : "");
|
|
30
32
|
/**
|
|
31
33
|
* The whole editor on one page: the palette, the canvas and a side panel
|
|
@@ -35,8 +37,10 @@ const messageOf = (e) => (e instanceof Error ? e.message : typeof e === "string"
|
|
|
35
37
|
* there are no edits, and when the saved revision moves under unsaved edits
|
|
36
38
|
* it stops and asks (use theirs, keep mine, compare). Publishing shows the
|
|
37
39
|
* draft against live first and is held while anything blocks. Save, publish,
|
|
38
|
-
* save as, import and export are the host's; it never persists anything.
|
|
39
|
-
*
|
|
40
|
+
* save as, import and export are the host's; it never persists anything. A
|
|
41
|
+
* host may hold the selection (deep links), let people add and rename flows,
|
|
42
|
+
* and add its own fields to the inspector. On narrow containers the palette
|
|
43
|
+
* and the side panel open as drawers.
|
|
40
44
|
*/
|
|
41
45
|
export function WorkflowWorkspace(props) {
|
|
42
46
|
const { messages } = useUI();
|
|
@@ -46,14 +50,22 @@ export function WorkflowWorkspace(props) {
|
|
|
46
50
|
return (_jsx(ErrorState, { variant: "page", title: messages.workspace.loadFailed, error: props.error ?? null, onRetry: props.onRetry, className: props.className }));
|
|
47
51
|
return _jsx(Workspace, { ...props, definition: props.definition });
|
|
48
52
|
}
|
|
49
|
-
function Workspace({ definition, onChange, registry, saved = null, published = null, onSave, onPublish, onSaveAs, onExport, onImport, importPreview = null, onImportConfirm, onImportCancel, readOnly: readOnlyProp = false, className, }) {
|
|
53
|
+
function Workspace({ definition, onChange, registry, saved = null, published = null, onSave, onPublish, onSaveAs, onExport, onImport, importPreview = null, onImportConfirm, onImportCancel, selection: selectionProp, defaultSelection = null, onSelectionChange, onFlowCreate, onFlowRename, renderNodeFields, readOnly: readOnlyProp = false, className, }) {
|
|
50
54
|
const { messages } = useUI();
|
|
51
55
|
const m = messages.workspace;
|
|
52
56
|
const readOnly = readOnlyProp || !onChange;
|
|
53
57
|
const [base, setBase] = React.useState(saved ?? published);
|
|
54
|
-
const [
|
|
58
|
+
const [ownSelection, setOwnSelection] = React.useState(defaultSelection);
|
|
59
|
+
const selection = selectionProp === undefined ? ownSelection : selectionProp;
|
|
60
|
+
const [flowName, setFlowName] = React.useState(selection?.flow ?? definition.main);
|
|
55
61
|
const flow = definition.flows[flowName] ? flowName : definition.main;
|
|
56
|
-
|
|
62
|
+
// A selection in another flow (the host's, or a problem's) opens that flow.
|
|
63
|
+
const [shown, setShown] = React.useState(selection);
|
|
64
|
+
if (!sameSelection(shown, selection)) {
|
|
65
|
+
setShown(selection);
|
|
66
|
+
if (selection && selection.flow !== flowName)
|
|
67
|
+
setFlowName(selection.flow);
|
|
68
|
+
}
|
|
57
69
|
const [panel, setPanel] = React.useState("inspector");
|
|
58
70
|
const [drawer, setDrawer] = React.useState(null);
|
|
59
71
|
const [dialog, setDialog] = React.useState(null);
|
|
@@ -103,9 +115,15 @@ function Workspace({ definition, onChange, registry, saved = null, published = n
|
|
|
103
115
|
}
|
|
104
116
|
};
|
|
105
117
|
const select = (s) => {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
118
|
+
if (sameSelection(s, selection))
|
|
119
|
+
return;
|
|
120
|
+
if (selectionProp === undefined)
|
|
121
|
+
setOwnSelection(s);
|
|
122
|
+
onSelectionChange?.(s);
|
|
123
|
+
};
|
|
124
|
+
const openFlow = (next) => {
|
|
125
|
+
setFlowName(next);
|
|
126
|
+
select(null);
|
|
109
127
|
};
|
|
110
128
|
const add = (item) => {
|
|
111
129
|
const label = item.label ||
|
|
@@ -120,7 +138,7 @@ function Workspace({ definition, onChange, registry, saved = null, published = n
|
|
|
120
138
|
const flows = Object.entries(definition.flows);
|
|
121
139
|
const edit = readOnly ? undefined : onChange;
|
|
122
140
|
const palette = (_jsx(WorkflowPalette, { registry: registry, onAdd: readOnly ? undefined : add, className: "min-h-0 flex-1" }));
|
|
123
|
-
const side = (_jsxs(Tabs, { value: panel, onValueChange: (v) => setPanel(v), className: "flex min-h-0 flex-1 flex-col", children: [_jsxs(TabsList, { "aria-label": m.panels, children: [_jsx(TabsTrigger, { value: "inspector", children: messages.editor.inspector }), _jsx(TabsTrigger, { value: "problems", children: m.problemsTab(blocking + warnings) }), _jsx(TabsTrigger, { value: "json", children: m.jsonTab })] }), _jsx(TabsContent, { value: "inspector", className: "min-h-0 flex-1 overflow-y-auto", children: _jsx(WorkflowInspector, { definition: definition, registry: registry, selection: selection, onSelectionChange: select, onChange: edit, readOnly: readOnly }) }), _jsx(TabsContent, { value: "problems", className: "min-h-0 flex-1 overflow-y-auto", children: _jsx(WorkflowProblems, { problems: problems, definition: definition, onSelect: (s) => {
|
|
141
|
+
const side = (_jsxs(Tabs, { value: panel, onValueChange: (v) => setPanel(v), className: "flex min-h-0 flex-1 flex-col", children: [_jsxs(TabsList, { "aria-label": m.panels, children: [_jsx(TabsTrigger, { value: "inspector", children: messages.editor.inspector }), _jsx(TabsTrigger, { value: "problems", children: m.problemsTab(blocking + warnings) }), _jsx(TabsTrigger, { value: "json", children: m.jsonTab })] }), _jsx(TabsContent, { value: "inspector", className: "min-h-0 flex-1 overflow-y-auto", children: _jsx(WorkflowInspector, { definition: definition, registry: registry, selection: selection, onSelectionChange: select, onChange: edit, readOnly: readOnly, renderNodeFields: renderNodeFields }) }), _jsx(TabsContent, { value: "problems", className: "min-h-0 flex-1 overflow-y-auto", children: _jsx(WorkflowProblems, { problems: problems, definition: definition, onSelect: (s) => {
|
|
124
142
|
select(s);
|
|
125
143
|
setPanel("inspector");
|
|
126
144
|
setDrawer(null);
|
|
@@ -131,10 +149,13 @@ function Workspace({ definition, onChange, registry, saved = null, published = n
|
|
|
131
149
|
}, children: [_jsx(CopyPlusIcon, { "aria-hidden": true }), m.saveAs] })) : null, onSave && !readOnly ? (_jsxs(Button, { size: "sm", variant: "outline", disabled: !status.dirty || status.conflict || busy !== null, onClick: () => void run("save", () => onSave(definition, base), m.savedNotice), children: [_jsx(SaveIcon, { "aria-hidden": true }), busy === "save" ? m.saving : m.save] })) : null, onPublish && !readOnly ? (_jsxs(Button, { size: "sm", disabled: status.conflict || busy !== null || (!status.unpublished && !!published), onClick: () => setDialog("publish"), children: [_jsx(RocketIcon, { "aria-hidden": true }), m.publish] })) : null] })] }), failure ? (_jsx("p", { role: "alert", className: "rounded-lg border border-danger-border bg-danger-bg px-3 py-2 text-sm text-danger-fg", children: failure })) : null, notice ? (_jsx("p", { className: "sr-only", role: "status", children: notice })) : null, status.conflict && saved ? (_jsxs("div", { role: "alert", className: "flex flex-wrap items-center gap-2 rounded-lg border border-warning-border bg-warning-bg px-3 py-2 text-sm text-warning-fg", children: [_jsx("span", { className: "min-w-0 flex-1", children: m.conflictDescription(saved.by, saved.label) }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => setDialog("compare"), children: m.compare }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => {
|
|
132
150
|
setBase(saved);
|
|
133
151
|
onChange?.(saved.definition);
|
|
134
|
-
}, children: m.useTheirs }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => setBase(saved), children: m.keepMine })] })) : null, _jsxs("div", { className: "flex min-h-0 flex-1 gap-3", children: [_jsx("aside", { "aria-label": m.paletteRegion, className: "hidden w-60 shrink-0 flex-col @5xl:flex", children: palette }), _jsxs("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col gap-2", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [!readOnly ? (_jsxs(Button, { size: "sm", variant: "outline", className: "@5xl:hidden", onClick: () => setDrawer("palette"), children: [_jsx(ListTreeIcon, { "aria-hidden": true }), messages.editor.palette] })) : null, _jsxs(Button, { size: "sm", variant: "outline", className: "@5xl:hidden", onClick: () => setDrawer("panel"), children: [_jsx(PanelRightIcon, { "aria-hidden": true }), m.panels] }), flows.length > 1 ? (_jsx(Segmented, { label: m.flow, size: "sm", value: flow, onValueChange: (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
},
|
|
152
|
+
}, children: m.useTheirs }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => setBase(saved), children: m.keepMine })] })) : null, _jsxs("div", { className: "flex min-h-0 flex-1 gap-3", children: [_jsx("aside", { "aria-label": m.paletteRegion, className: "hidden w-60 shrink-0 flex-col @5xl:flex", children: palette }), _jsxs("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col gap-2", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [!readOnly ? (_jsxs(Button, { size: "sm", variant: "outline", className: "@5xl:hidden", onClick: () => setDrawer("palette"), children: [_jsx(ListTreeIcon, { "aria-hidden": true }), messages.editor.palette] })) : null, _jsxs(Button, { size: "sm", variant: "outline", className: "@5xl:hidden", onClick: () => setDrawer("panel"), children: [_jsx(PanelRightIcon, { "aria-hidden": true }), m.panels] }), flows.length > 1 ? (_jsx(Segmented, { label: m.flow, size: "sm", value: flow, onValueChange: openFlow, options: flows.map(([key, f]) => ({ value: key, label: f.label ?? key })) })) : null, onFlowRename && !readOnly ? (_jsxs(Button, { size: "sm", variant: "ghost", disabled: busy !== null, onClick: () => {
|
|
153
|
+
setFailure(null);
|
|
154
|
+
setDialog("rename-flow");
|
|
155
|
+
}, children: [_jsx(PencilIcon, { "aria-hidden": true }), m.renameFlow] })) : null, onFlowCreate && !readOnly ? (_jsxs(Button, { size: "sm", variant: "ghost", disabled: busy !== null, onClick: () => {
|
|
156
|
+
setFailure(null);
|
|
157
|
+
setDialog("new-flow");
|
|
158
|
+
}, children: [_jsx(PlusIcon, { "aria-hidden": true }), m.newFlow] })) : null] }), _jsx(WorkflowCanvas, { definition: definition, flow: flow, registry: registry, onChange: edit, readOnly: readOnly, selection: selection, onSelectionChange: select, problems: problems, className: "min-h-96 flex-1" }, flow)] }), _jsx("aside", { "aria-label": m.panels, className: "hidden w-88 shrink-0 flex-col @5xl:flex", children: side })] }), _jsx(Drawer, { open: drawer !== null, onOpenChange: (open) => !open && setDrawer(null), children: _jsxs(DrawerContent, { side: drawer === "palette" ? "left" : "right", className: "p-4", children: [_jsx(DrawerTitle, { children: drawer === "palette" ? messages.editor.palette : m.panels }), _jsx("div", { className: "flex min-h-0 flex-1 flex-col", children: drawer === "palette" ? palette : side })] }) }), _jsx(Dialog, { open: dialog === "publish", onOpenChange: (open) => !open && setDialog(null), children: _jsxs(DialogContent, { className: "max-w-xl", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: m.publishTitle }), _jsx(DialogDescription, { children: published ? m.publishDescription(published.label) : m.firstPublish })] }), published ? (_jsx(WorkflowDiff, { diff: diffDefinitions(published.definition, definition), flows: definition.flows, label: m.draftAgainstLive, className: "max-h-80 overflow-y-auto" })) : null, blocking ? (_jsx("p", { role: "alert", className: "text-sm text-danger-fg", children: m.publishBlocked(blocking) })) : null, _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "outline", onClick: () => setDialog(null), children: messages.editor.cancel }), _jsx(Button, { type: "button", disabled: blocking > 0 || busy !== null, onClick: async () => {
|
|
138
159
|
if (await run("publish", () => onPublish?.(definition), m.publishedNotice))
|
|
139
160
|
setDialog(null);
|
|
140
161
|
}, children: busy === "publish" ? m.publishing : m.publish })] })] }) }), _jsx(Dialog, { open: dialog === "save-as", onOpenChange: (open) => !open && setDialog(null), children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: m.saveAsTitle }), _jsx(DialogDescription, { children: m.saveAsDescription })] }), _jsxs("form", { className: "flex flex-col gap-4", onSubmit: async (e) => {
|
|
@@ -147,8 +168,46 @@ function Workspace({ definition, onChange, registry, saved = null, published = n
|
|
|
147
168
|
}, children: [_jsx(Field, { label: messages.editor.name, required: true, children: _jsx(Input, { value: name, onChange: (e) => setName(e.target.value) }) }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "outline", onClick: () => setDialog(null), children: messages.editor.cancel }), _jsx(Button, { type: "submit", disabled: !name.trim() || busy !== null, children: busy === "save-as" ? m.saving : m.saveAs })] })] })] }) }), _jsx(Dialog, { open: dialog === "compare", onOpenChange: (open) => !open && setDialog(null), children: _jsxs(DialogContent, { className: "max-w-xl", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: m.compareTitle }), _jsx(DialogDescription, { children: m.compareDescription })] }), saved ? (_jsx(WorkflowDiff, { diff: diffDefinitions(saved.definition, definition), flows: definition.flows, label: m.compareTitle, className: "max-h-80 overflow-y-auto" })) : null] }) }), _jsx(ConfirmDialog, { open: dialog === "discard", onOpenChange: (open) => !open && setDialog(null), title: m.discardTitle, description: m.discardDescription, confirmLabel: m.discard, destructive: true, onConfirm: () => {
|
|
148
169
|
if (base)
|
|
149
170
|
onChange?.(base.definition);
|
|
150
|
-
|
|
151
|
-
} }), _jsx(Dialog, { open:
|
|
171
|
+
select(null);
|
|
172
|
+
} }), _jsx(Dialog, { open: dialog === "new-flow" || dialog === "rename-flow", onOpenChange: (open) => !open && setDialog(null), children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: dialog === "rename-flow"
|
|
173
|
+
? m.renameFlowTitle(definition.flows[flow]?.label ?? flow)
|
|
174
|
+
: m.newFlowTitle }), _jsx(DialogDescription, { children: dialog === "rename-flow" ? m.renameFlowDescription : m.newFlowDescription })] }), dialog === "new-flow" || dialog === "rename-flow" ? (_jsx(FlowForm, { definition: definition, current: dialog === "rename-flow" ? flow : undefined, busy: busy !== null, failure: failure, onCancel: () => setDialog(null), onSubmit: async (name, label) => {
|
|
175
|
+
const renaming = dialog === "rename-flow";
|
|
176
|
+
const done = renaming
|
|
177
|
+
? await run("flow", () => onFlowRename?.(renameFlow(definition, flow, name, label), flow, name), m.flowRenamedNotice(name))
|
|
178
|
+
: await run("flow", () => onFlowCreate?.(addFlow(definition, name, label), name), m.flowCreatedNotice(name));
|
|
179
|
+
if (!done)
|
|
180
|
+
return;
|
|
181
|
+
setDialog(null);
|
|
182
|
+
if (renaming && selection?.flow === flow)
|
|
183
|
+
select({ ...selection, flow: name });
|
|
184
|
+
else if (!renaming)
|
|
185
|
+
select(null);
|
|
186
|
+
setFlowName(name);
|
|
187
|
+
} })) : null] }) }), _jsx(Dialog, { open: !!importPreview, onOpenChange: (open) => !open && onImportCancel?.(), children: _jsxs(DialogContent, { className: "max-w-xl", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: m.importTitle }), _jsx(DialogDescription, { children: m.importDescription })] }), importPreview ? (_jsx(WorkflowImportPreview, { preview: importPreview, busy: busy === "import", onCancel: onImportCancel, onConfirm: onImportConfirm
|
|
152
188
|
? () => void run("import", () => onImportConfirm(), m.importedNotice)
|
|
153
189
|
: undefined, className: "max-h-[70vh] overflow-y-auto" })) : null] }) })] }));
|
|
154
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* A flow's name and label, checked as they are typed: a new flow, or the
|
|
193
|
+
* open one (`current`) being renamed. Holds its own state so typing does not
|
|
194
|
+
* redraw the canvas.
|
|
195
|
+
*/
|
|
196
|
+
function FlowForm({ definition, current, busy, failure, onCancel, onSubmit, }) {
|
|
197
|
+
const { messages } = useUI();
|
|
198
|
+
const m = messages.workspace;
|
|
199
|
+
const currentLabel = current ? (definition.flows[current]?.label ?? "") : "";
|
|
200
|
+
const [name, setName] = React.useState(current ?? "");
|
|
201
|
+
const [label, setLabel] = React.useState(currentLabel);
|
|
202
|
+
const [touched, setTouched] = React.useState(false);
|
|
203
|
+
const trimmed = name.trim();
|
|
204
|
+
const problem = flowNameProblem(definition, trimmed, current);
|
|
205
|
+
const full = !current && Object.keys(definition.flows).length >= WORKFLOW_MAX_FLOWS;
|
|
206
|
+
const unchanged = !!current && trimmed === current && label.trim() === currentLabel;
|
|
207
|
+
return (_jsxs("form", { className: "flex flex-col gap-4", noValidate: true, onSubmit: (e) => {
|
|
208
|
+
e.preventDefault();
|
|
209
|
+
setTouched(true);
|
|
210
|
+
if (!problem && !full && !unchanged)
|
|
211
|
+
onSubmit(trimmed, label.trim());
|
|
212
|
+
}, children: [_jsx(Field, { label: m.flowName, hint: m.flowNameHint, error: touched && problem ? m.flowNameProblem(problem) : undefined, required: true, children: _jsx(Input, { value: name, autoComplete: "off", spellCheck: false, onChange: (e) => setName(e.target.value), onBlur: () => setTouched(true) }) }), _jsx(Field, { label: m.flowLabel, hint: m.flowLabelHint, children: _jsx(Input, { value: label, onChange: (e) => setLabel(e.target.value) }) }), full ? (_jsx("p", { role: "alert", className: "text-sm text-danger-fg", children: m.flowsFull(WORKFLOW_MAX_FLOWS) })) : null, failure ? (_jsx("p", { role: "alert", className: "text-sm text-danger-fg", children: failure })) : null, _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "outline", onClick: onCancel, children: messages.editor.cancel }), _jsx(Button, { type: "submit", disabled: full || unchanged || busy, children: busy ? m.saving : current ? m.rename : m.createFlow })] })] }));
|
|
213
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@calliopelabs/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Calliope AI shared operator UI: app shell, navigation, page frame, data table and list patterns, status colours, design tokens and appearance for Astrolift AI and Zentinelle AI.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"calliope-ai",
|
|
@@ -30,12 +30,32 @@
|
|
|
30
30
|
"types": "./dist/index.d.ts",
|
|
31
31
|
"default": "./dist/index.js"
|
|
32
32
|
},
|
|
33
|
+
"./agent": {
|
|
34
|
+
"types": "./dist/agent/index.d.ts",
|
|
35
|
+
"default": "./dist/agent/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./agent/copilotkit": {
|
|
38
|
+
"types": "./dist/agent/copilotkit/index.d.ts",
|
|
39
|
+
"default": "./dist/agent/copilotkit/index.js"
|
|
40
|
+
},
|
|
33
41
|
"./theme.css": "./dist/styles/theme.css",
|
|
34
42
|
"./preset.css": "./dist/styles/preset.css",
|
|
35
43
|
"./css/tokens.css": "./dist/css/tokens.css",
|
|
36
44
|
"./css/appearance.js": "./dist/css/appearance.js",
|
|
37
45
|
"./css/components.css": "./dist/css/components.css",
|
|
38
46
|
"./css/calliope.css": "./dist/css/calliope.css",
|
|
47
|
+
"./tokens/jupyterlab.css": "./dist/tokens/jupyterlab.css",
|
|
48
|
+
"./tokens/vscode.css": "./dist/tokens/vscode.css",
|
|
49
|
+
"./tokens/plain.css": "./dist/tokens/plain.css",
|
|
50
|
+
"./tokens/tokens.py": "./dist/tokens/tokens.py",
|
|
51
|
+
"./tokens/native": {
|
|
52
|
+
"types": "./dist/tokens/tokens.native.d.ts",
|
|
53
|
+
"default": "./dist/tokens/tokens.native.js"
|
|
54
|
+
},
|
|
55
|
+
"./tokens/tokens.native.js": {
|
|
56
|
+
"types": "./dist/tokens/tokens.native.d.ts",
|
|
57
|
+
"default": "./dist/tokens/tokens.native.js"
|
|
58
|
+
},
|
|
39
59
|
"./package.json": "./package.json"
|
|
40
60
|
},
|
|
41
61
|
"main": "./dist/index.js",
|
|
@@ -59,6 +79,7 @@
|
|
|
59
79
|
"build-storybook": "storybook build --quiet"
|
|
60
80
|
},
|
|
61
81
|
"dependencies": {
|
|
82
|
+
"@ag-ui/core": "~1.0.3",
|
|
62
83
|
"@xyflow/react": "^12.12.1",
|
|
63
84
|
"class-variance-authority": "^0.7.1",
|
|
64
85
|
"clsx": "^2.1.1",
|
|
@@ -67,11 +88,14 @@
|
|
|
67
88
|
"tailwind-merge": "^3.7.0"
|
|
68
89
|
},
|
|
69
90
|
"devDependencies": {
|
|
91
|
+
"@ag-ui/client": "1.0.2",
|
|
92
|
+
"@copilotkit/react-core": "1.88.0",
|
|
70
93
|
"@eslint/js": "^9.39.5",
|
|
71
94
|
"@fontsource-variable/dm-sans": "^5.3.0",
|
|
72
95
|
"@fontsource/ibm-plex-mono": "^5.3.0",
|
|
73
96
|
"@fontsource/ibm-plex-sans": "^5.3.0",
|
|
74
97
|
"@storybook/addon-a11y": "^10.6.2",
|
|
98
|
+
"@storybook/addon-docs": "^10.6.2",
|
|
75
99
|
"@storybook/react-vite": "^10.6.2",
|
|
76
100
|
"@tailwindcss/cli": "^4.3.3",
|
|
77
101
|
"@tailwindcss/vite": "^4.3.3",
|
|
@@ -101,14 +125,23 @@
|
|
|
101
125
|
"typescript": "~6.0.3",
|
|
102
126
|
"typescript-eslint": "^8.71.2",
|
|
103
127
|
"vite": "^8.3.4",
|
|
104
|
-
"vitest": "^5.0.5"
|
|
128
|
+
"vitest": "^5.0.5",
|
|
129
|
+
"zod": "^4.6.7"
|
|
105
130
|
},
|
|
106
131
|
"peerDependencies": {
|
|
132
|
+
"@ag-ui/client": "^1.0.2",
|
|
133
|
+
"@copilotkit/react-core": ">=1.79.1 <2",
|
|
107
134
|
"react": "^19.0.0",
|
|
108
135
|
"react-dom": "^19.0.0",
|
|
109
136
|
"tailwindcss": "^4.0.0"
|
|
110
137
|
},
|
|
111
138
|
"peerDependenciesMeta": {
|
|
139
|
+
"@ag-ui/client": {
|
|
140
|
+
"optional": true
|
|
141
|
+
},
|
|
142
|
+
"@copilotkit/react-core": {
|
|
143
|
+
"optional": true
|
|
144
|
+
},
|
|
112
145
|
"tailwindcss": {
|
|
113
146
|
"optional": true
|
|
114
147
|
}
|