feat: runnersok

This commit is contained in:
2026-03-29 01:22:33 +01:00
parent 26b395272d
commit 9607ff3478
24 changed files with 1613 additions and 221 deletions

View File

@@ -22,17 +22,9 @@ import {
MenubarSubContent,
MenubarSubTrigger,
} from '@/components/ui/menubar'
import { Sparkles, Play, ChevronDown, Loader2, RotateCw } from 'lucide-react'
import { Sparkles, Loader2, RotateCw } from 'lucide-react'
import { InputHandle, OutputHandle } from '@/components/graph/NodeHandles'
import { Button } from '@/components/ui/button'
import { ButtonGroup } from '@/components/ui/button-group'
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { useTheme } from '@/lib/themeContext'
import {
useRenderingNodeState,
@@ -165,95 +157,8 @@ function RenderingNodeComponent({ id, data, width, height, selected }: Props) {
: undefined
}
right={
state.incomingIds.length > 0 ? (
<div className="flex items-center gap-2 shrink-0">
<ButtonGroup className="nodrag nopan">
{state.effectiveUpdateMode === 'manual' ? (
<Button
type="button"
size="sm"
variant="outline"
disabled={state.loading || !state.hasPendingInputs}
className="h-7 gap-1.5 rounded-r-none border-r-0 px-2.5 text-xs"
onClick={(e) => {
e.stopPropagation()
state.incrementRunTrigger()
}}
title={
state.hasPendingInputs
? 'Inputs changed — click to render'
: !state.hasPendingInputs
? 'No new data to render'
: undefined
}
>
{state.loading ? (
<Loader2 className="size-3.5 animate-spin shrink-0" aria-hidden />
) : (
<Play className="size-3.5 shrink-0" />
)}
Run
</Button>
) : state.loading ? (
<Button
type="button"
size="sm"
variant="outline"
disabled
className="h-7 gap-1.5 rounded-r-none border-r-0 px-2.5 text-xs"
aria-label="Updating"
>
<Loader2 className="size-3.5 animate-spin shrink-0" aria-hidden />
</Button>
) : null}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
type="button"
size="sm"
variant="outline"
className={`h-7 min-w-[4.5rem] gap-1 pl-2 pr-1.5 text-xs font-normal ${state.effectiveUpdateMode === 'manual' || state.loading ? 'rounded-l-none' : 'rounded-l-md'}`}
aria-label="Update mode"
onClick={(e) => e.stopPropagation()}
>
<span className="text-muted-foreground">
{state.effectiveUpdateMode === 'manual' ? 'Manual' : 'Auto'}
</span>
<ChevronDown className="size-3.5 shrink-0 opacity-70" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-64" onClick={(e) => e.stopPropagation()}>
<DropdownMenuLabel className="text-xs font-normal text-muted-foreground">
When to re-render
</DropdownMenuLabel>
<DropdownMenuCheckboxItem
checked={state.effectiveUpdateMode === 'auto'}
onCheckedChange={(checked) =>
checked && state.setUpdateMode('auto')
}
className="flex flex-col items-start gap-0.5 py-2"
>
<span className="font-medium">Auto</span>
<span className="text-muted-foreground text-xs font-normal">
Re-renders when upstream content changes
</span>
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
checked={state.effectiveUpdateMode === 'manual'}
onCheckedChange={(checked) =>
checked && state.setUpdateMode('manual')
}
className="flex flex-col items-start gap-0.5 py-2"
>
<span className="font-medium">Manual</span>
<span className="text-muted-foreground text-xs font-normal">
Re-renders only when you click Run
</span>
</DropdownMenuCheckboxItem>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
</div>
state.incomingIds.length > 0 && state.loading ? (
<Loader2 className="size-3.5 animate-spin shrink-0 text-muted-foreground" aria-label="Rendering" />
) : undefined
}
/>

View File

@@ -76,6 +76,7 @@ import {
import { buildSourceSignatures, type NodeLike, type EdgeLike } from '@/lib/graph/renderingSignatures'
import { getNodeDisplayStatus, type NodeDisplayStatus } from '@/lib/graph/state'
import type { ConfigTypeId, SourceRenderingLogicContext } from '@/lib/graph/rendering'
import { useRunStore } from '@/lib/graph/runStore'
export type OutputMode = 'image' | 'string'
@@ -207,8 +208,11 @@ export function useRenderingNodeState(
() => (srcNode?.type ? getSourceRenderingLogic(srcNode.type as string) : null),
[srcNode?.type]
)
const effectiveUpdateMode = (data?.updateMode ?? sourceLogic?.defaultUpdateMode ?? 'auto') as 'auto' | 'manual'
const runTrigger = data?.runTrigger ?? 0
// All rendering is now triggered by the global Run button — no auto/manual distinction.
const effectiveUpdateMode: 'auto' | 'manual' = 'manual'
// Use global run trigger from runStore instead of per-node trigger
const globalRunTrigger = useRunStore((s) => s.globalRunTrigger)
const runTrigger = globalRunTrigger
const outputMode: OutputMode = (data?.outputMode ?? 'image') as OutputMode
const setOutputMode = useCallback(
(mode: OutputMode) => updateData({ outputMode: mode }),
@@ -330,7 +334,7 @@ export function useRenderingNodeState(
if (!hasCachedOutput) {
setRenderedContent(null)
setResolvedContent(null)
setError({ kind: 'no-content', message: 'Click Run to render.' })
setError({ kind: 'no-content', message: 'Press Run (⌘↵) to render.' })
setLoading(false)
}
return