Flow inputs carry an origin hint, run or panel
The Inputs editor and the canvas label show it, and the run forms leave panel inputs out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WgBfkm4rCmtd1T4vHWjEPx
This commit is contained in:
@@ -267,6 +267,15 @@ class FlowInput(BaseModel):
|
||||
|
||||
spec: MessageSpec
|
||||
initial: Any | None = None
|
||||
origin: Literal["run", "panel"] = Field(
|
||||
default="run",
|
||||
description=(
|
||||
"What writes it. A run input is a parameter a run passes in, a "
|
||||
"panel input is written by a dashboard or panel control. A hint "
|
||||
"only: run forms leave panel inputs out, the engine treats both "
|
||||
"the same."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class FlowDef(BaseModel):
|
||||
|
||||
@@ -96,13 +96,18 @@ Declare messages supplied by dashboard controls, the API or run parameters as
|
||||
the flow's **Inputs**. Give live inputs a starting value:
|
||||
|
||||
```json
|
||||
{"inputs": [{"spec": {"name": "setpoint", "dtype": "float"}, "initial": 21.0}]}
|
||||
{"inputs": [{"spec": {"name": "setpoint", "dtype": "float"}, "initial": 21.0, "origin": "panel"}]}
|
||||
```
|
||||
|
||||
This seeds `house.setpoint` in flow `house` if it has no value yet. Later
|
||||
publications replace it. Batch inputs are run parameters, and batch outputs
|
||||
select the run's result. See [Where a node's values come from](values.md).
|
||||
|
||||
`origin` says what writes an input: `run` (the default) for a parameter a run
|
||||
passes in, `panel` for a dashboard or panel control. The canvas labels each
|
||||
input with it, and the run form leaves panel inputs out, so a run keeps their
|
||||
starting value. The engine treats both the same.
|
||||
|
||||
## Drafts and publishing
|
||||
|
||||
- **Saving** stores a draft; the engine keeps running the published version.
|
||||
|
||||
@@ -47,8 +47,10 @@ The initial value seeds an unset live message. Later publications take over;
|
||||
changing the starting value does not overwrite a value already in state.
|
||||
Use **Publish this value now** in the input row to send it immediately.
|
||||
|
||||
The canvas draws flow inputs as labelled endpoints. Without a producer or
|
||||
declared input, a consuming node reports `unconnected_input`.
|
||||
The canvas draws flow inputs as labelled endpoints, marked `run` or `panel`
|
||||
by what writes them. A panel input is one a dashboard or panel control writes,
|
||||
and the run form does not ask for it. Without a producer or declared input, a
|
||||
consuming node reports `unconnected_input`.
|
||||
|
||||
A batch flow's inputs are run parameters. Its **Result** section selects the
|
||||
messages returned when the run finishes. See [Runs](runs.md).
|
||||
|
||||
@@ -80,7 +80,7 @@ Open **Flow settings** from the pencil button in the dock.
|
||||
|---|---|
|
||||
| **Running** | starts or stops the published live flow's subscriptions, schedules and webhooks |
|
||||
| **Mode** | chooses continuous `live` operation or requested `batch` runs |
|
||||
| **Inputs** | declares external messages and starting values; batch inputs become run parameters |
|
||||
| **Inputs** | declares external messages, their starting values and whether a run or a panel writes them; run inputs become run parameters |
|
||||
| **Result** | selects batch output messages |
|
||||
| **Contents** | shows the node count and message namespace |
|
||||
|
||||
|
||||
@@ -1355,6 +1355,13 @@ export const FlowInput_InputSchema = {
|
||||
}
|
||||
],
|
||||
title: 'Initial'
|
||||
},
|
||||
origin: {
|
||||
type: 'string',
|
||||
enum: ['run', 'panel'],
|
||||
title: 'Origin',
|
||||
description: 'What writes it. A run input is a parameter a run passes in, a panel input is written by a dashboard or panel control. A hint only: run forms leave panel inputs out, the engine treats both the same.',
|
||||
default: 'run'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
@@ -1376,6 +1383,13 @@ export const FlowInput_OutputSchema = {
|
||||
}
|
||||
],
|
||||
title: 'Initial'
|
||||
},
|
||||
origin: {
|
||||
type: 'string',
|
||||
enum: ['run', 'panel'],
|
||||
title: 'Origin',
|
||||
description: 'What writes it. A run input is a parameter a run passes in, a panel input is written by a dashboard or panel control. A hint only: run forms leave panel inputs out, the engine treats both the same.',
|
||||
default: 'run'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
|
||||
@@ -391,14 +391,27 @@ export type FlowDetail = {
|
||||
export type FlowInput_Input = {
|
||||
spec: MessageSpec;
|
||||
initial?: (unknown | null);
|
||||
/**
|
||||
* What writes it. A run input is a parameter a run passes in, a panel input is written by a dashboard or panel control. A hint only: run forms leave panel inputs out, the engine treats both the same.
|
||||
*/
|
||||
origin?: 'run' | 'panel';
|
||||
};
|
||||
|
||||
/**
|
||||
* What writes it. A run input is a parameter a run passes in, a panel input is written by a dashboard or panel control. A hint only: run forms leave panel inputs out, the engine treats both the same.
|
||||
*/
|
||||
export type origin = 'run' | 'panel';
|
||||
|
||||
/**
|
||||
* A message the flow starts with rather than computes.
|
||||
*/
|
||||
export type FlowInput_Output = {
|
||||
spec: MessageSpec;
|
||||
initial?: (unknown | null);
|
||||
/**
|
||||
* What writes it. A run input is a parameter a run passes in, a panel input is written by a dashboard or panel control. A hint only: run forms leave panel inputs out, the engine treats both the same.
|
||||
*/
|
||||
origin?: 'run' | 'panel';
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@ import { MessagesService } from "@/client"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Segmented } from "@/components/ui/segmented"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -160,6 +161,12 @@ function usePublishInput() {
|
||||
})
|
||||
}
|
||||
|
||||
/** What writes an input: a run passing it in, or a control on a panel. */
|
||||
const ORIGINS = [
|
||||
["run", "run"],
|
||||
["panel", "panel"],
|
||||
] as const
|
||||
|
||||
function InputRow({
|
||||
flow,
|
||||
declared,
|
||||
@@ -231,34 +238,44 @@ function InputRow({
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{live && name ? (
|
||||
<div className="flex items-center gap-2 pl-1 text-xs text-muted-foreground">
|
||||
<span className="shrink-0">now</span>
|
||||
{current === undefined ? (
|
||||
<span className="flex-1">—</span>
|
||||
) : (
|
||||
<span className="min-w-0 flex-1">
|
||||
<ValuePreview value={current.value} dtype={spec.dtype} />
|
||||
</span>
|
||||
)}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
aria-label="Publish this value now"
|
||||
disabled={publish.isPending || declared.initial === null}
|
||||
onClick={() =>
|
||||
publish.mutate({ message, value: declared.initial })
|
||||
}
|
||||
>
|
||||
<ArrowUpFromLine />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Put this value into the flow now</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex items-center gap-2 pl-1 text-xs text-muted-foreground">
|
||||
<Segmented
|
||||
label="Written by"
|
||||
value={declared.origin ?? "run"}
|
||||
options={ORIGINS}
|
||||
onChange={(origin) =>
|
||||
onChange({ ...declared, origin: origin as typeof declared.origin })
|
||||
}
|
||||
/>
|
||||
{live && name ? (
|
||||
<>
|
||||
<span className="shrink-0">now</span>
|
||||
{current === undefined ? (
|
||||
<span className="flex-1">—</span>
|
||||
) : (
|
||||
<span className="min-w-0 flex-1">
|
||||
<ValuePreview value={current.value} dtype={spec.dtype} />
|
||||
</span>
|
||||
)}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
aria-label="Publish this value now"
|
||||
disabled={publish.isPending || declared.initial === null}
|
||||
onClick={() =>
|
||||
publish.mutate({ message, value: declared.initial })
|
||||
}
|
||||
>
|
||||
<ArrowUpFromLine />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Put this value into the flow now</TooltipContent>
|
||||
</Tooltip>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -338,8 +355,9 @@ export function BoundarySections({
|
||||
<div className="flex items-center justify-between">
|
||||
<span className={PANEL_SECTION}>Inputs</span>
|
||||
<InfoTip label="Inputs">
|
||||
Messages that arrive from outside, such as a dashboard control, a
|
||||
run or the API, and the value the flow starts from.
|
||||
Messages that arrive from outside, and the value the flow starts
|
||||
from. A run passes a run input in, a dashboard or panel control
|
||||
writes a panel input.
|
||||
</InfoTip>
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
||||
@@ -94,9 +94,16 @@ export function initialText(inputs: FlowInput_Input[]): Record<string, string> {
|
||||
)
|
||||
}
|
||||
|
||||
/** The declared inputs worth drawing a field for. */
|
||||
/**
|
||||
* The declared inputs worth drawing a field for.
|
||||
*
|
||||
* A panel input is a control's to write, so a run keeps its starting value
|
||||
* and the form does not offer it as a parameter.
|
||||
*/
|
||||
export function declaredInputs(definition: FlowDef_Input): FlowInput_Input[] {
|
||||
return (definition.inputs ?? []).filter((one) => one.spec?.name)
|
||||
return (definition.inputs ?? []).filter(
|
||||
(one) => one.spec?.name && one.origin !== "panel",
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -91,11 +91,14 @@ export function deriveEdges(nodes: NodeDef_Input[], flow: string): Edge[] {
|
||||
*/
|
||||
export function boundaryKey(doc: {
|
||||
mode?: string | null
|
||||
inputs?: { spec?: MessageSpec | null }[] | null
|
||||
inputs?: { spec?: MessageSpec | null; origin?: string }[] | null
|
||||
outputs?: string[] | null
|
||||
}): string {
|
||||
const inputs = (doc.inputs ?? [])
|
||||
.map((one) => `${one.spec?.name ?? ""}:${one.spec?.dtype ?? ""}`)
|
||||
.map(
|
||||
(one) =>
|
||||
`${one.spec?.name ?? ""}:${one.spec?.dtype ?? ""}:${one.origin ?? ""}`,
|
||||
)
|
||||
.join(",")
|
||||
return `${doc.mode ?? "live"}|${inputs}|${(doc.outputs ?? []).join(",")}`
|
||||
}
|
||||
|
||||
@@ -62,7 +62,8 @@ function boundaryOf(
|
||||
kind: "input",
|
||||
id: `input:${message}`,
|
||||
label: displayName(flow, message),
|
||||
detail: declared.spec?.dtype ?? "float",
|
||||
// What writes it, so a control's input does not read as a run's.
|
||||
detail: `${declared.origin ?? "run"} · ${declared.spec?.dtype ?? "float"}`,
|
||||
provides: [message],
|
||||
requires: [],
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user