Add flow settings, pulse emitting nodes, and simplify node state
- One dot per node now carries the whole story: primary while running, sage after a good run, red when anything is wrong, with the explanation on hover. The corner badge is gone, along with the second way of saying the same thing. - A node that publishes something flashes a ring, so a running flow is legible without reading the edge values. Nodes that consume but publish nothing stay quiet, which is why the event carries an output count. - Flow settings open in the same panel its nodes use, from a pencil in the dock: the title, the name, and deleting the flow. NodePanel and FlowPanel share the panel chrome rather than each drawing their own. - Renaming is a server operation, because a flow's name is the namespace of its messages: the directory moves and every other flow reading `old.message` is repointed, instead of being left pointing at a flow that no longer exists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WzrvW7rjQbynnhF6pxh6i
This commit is contained in:
co-authored by
Claude Fable 5
parent
c254d487ba
commit
fd666743d2
@@ -1,13 +1,5 @@
|
||||
import { Handle, type NodeProps, Position } from "@xyflow/react"
|
||||
import {
|
||||
AlertCircle,
|
||||
Braces,
|
||||
Clock,
|
||||
Code2,
|
||||
Database,
|
||||
Globe,
|
||||
Radio,
|
||||
} from "lucide-react"
|
||||
import { Braces, Clock, Code2, Database, Globe, Radio } from "lucide-react"
|
||||
import { memo } from "react"
|
||||
|
||||
import type { MessageSpec, NodeDef_Input } from "@/client"
|
||||
@@ -18,7 +10,7 @@ import {
|
||||
} from "@/components/ui/tooltip"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { portOf } from "./deriveEdges"
|
||||
import { useNodeStatus } from "./liveStore"
|
||||
import { useNodeEmits, useNodeStatus } from "./liveStore"
|
||||
|
||||
const NODE_ICONS = {
|
||||
python: Code2,
|
||||
@@ -29,11 +21,12 @@ const NODE_ICONS = {
|
||||
mlp: Braces,
|
||||
} as const
|
||||
|
||||
// Only the states worth a quiet marker. Anything wrong goes to the badge
|
||||
// instead, so a problem is never reported twice on the same node.
|
||||
// One dot says everything about a node's state. Idle nodes carry no dot at all,
|
||||
// so the canvas stays quiet until something happens.
|
||||
const STATUS_STYLES = {
|
||||
running: { dot: "bg-primary animate-pulse", label: "Running" },
|
||||
success: { dot: "bg-status-success", label: "Last run succeeded" },
|
||||
error: { dot: "bg-destructive", label: "Something went wrong" },
|
||||
} as const
|
||||
|
||||
export type FlowNodeData = {
|
||||
@@ -85,16 +78,16 @@ function PortHandles({
|
||||
function FlowNodeComponent({ data, selected }: NodeProps) {
|
||||
const { definition, flow, typeLabel, issueText } = data as FlowNodeData
|
||||
const live = useNodeStatus(`${flow}.${definition.id}`)
|
||||
const emits = useNodeEmits(`${flow}.${definition.id}`)
|
||||
const Icon = NODE_ICONS[definition.type as keyof typeof NODE_ICONS] ?? Code2
|
||||
|
||||
// Whatever is wrong — it failed to load, it failed to run, or the graph
|
||||
// around it does not add up — is one badge with one explanation.
|
||||
// around it does not add up — is the same red dot with the same explanation.
|
||||
const problem = [live?.status === "error" ? live.error : null, issueText]
|
||||
.filter(Boolean)
|
||||
.join("\n")
|
||||
const style = problem
|
||||
? undefined
|
||||
: STATUS_STYLES[live?.status as keyof typeof STATUS_STYLES]
|
||||
const status = problem ? "error" : live?.status
|
||||
const style = STATUS_STYLES[status as keyof typeof STATUS_STYLES]
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -103,6 +96,9 @@ function FlowNodeComponent({ data, selected }: NodeProps) {
|
||||
selected && "border-primary shadow-e2",
|
||||
)}
|
||||
>
|
||||
{/* Remounting on each emit is what restarts the animation. */}
|
||||
{emits > 0 ? <span key={emits} className="node-pulse" /> : null}
|
||||
|
||||
<PortHandles
|
||||
specs={definition.requires ?? []}
|
||||
type="target"
|
||||
@@ -130,28 +126,13 @@ function FlowNodeComponent({ data, selected }: NodeProps) {
|
||||
aria-label={style.label}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{live?.error ?? style.label}</TooltipContent>
|
||||
<TooltipContent className="max-w-xs whitespace-pre-line">
|
||||
{problem || style.label}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{problem ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span
|
||||
role="img"
|
||||
aria-label="This node has a problem"
|
||||
className="absolute -right-1.5 -top-1.5 flex size-4 items-center justify-center rounded-full bg-destructive text-primary-foreground"
|
||||
>
|
||||
<AlertCircle className="size-3" />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs whitespace-pre-line">
|
||||
{problem}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
|
||||
<PortHandles
|
||||
specs={definition.provides ?? []}
|
||||
type="source"
|
||||
|
||||
Reference in New Issue
Block a user