feat: ant path rendering on cahnges

This commit is contained in:
2026-03-11 22:08:08 +01:00
parent ea6bdeb05f
commit 1232d148e6
11 changed files with 322 additions and 34 deletions

View File

@@ -55,6 +55,8 @@ function serializeNodeForContext(nodes: { id: string; type?: string; data?: unkn
function AgentNodeComponent({ id, data, width, height, selected }: Props) {
const flowContext = useContext(FlowContext)
const setFullscreenNodeId = flowContext?.setFullscreenNodeId
const startConnectionPathUpdate = flowContext?.startConnectionPathUpdate
const endConnectionPathUpdate = flowContext?.endConnectionPathUpdate
const supportsFullscreen = getNodeType('agent')?.supportsFullscreen
const { nodes, sourceIds, updateData } = useAbstractNode<AgentNodeData>(id, data ?? {})
const { aiConnection } = usePlatform()
@@ -88,6 +90,7 @@ function AgentNodeComponent({ id, data, width, height, selected }: Props) {
const contextNodes = sourceIds.map((sid) => ({ id: sid, content: serializeNodeForContext(nodes, sid) }))
updateData({ error: undefined, loading: true })
startConnectionPathUpdate?.(id)
setRunning(true)
try {
const res = await fetch('/api/agent', {
@@ -107,16 +110,19 @@ function AgentNodeComponent({ id, data, width, height, selected }: Props) {
error: (json as { error?: string }).error ?? `Request failed: ${res.status}`,
outputMarkdown: undefined,
})
endConnectionPathUpdate?.(id)
return
}
const markdown = (json as { markdown?: string }).markdown ?? ''
updateData({ loading: false, error: undefined, outputMarkdown: markdown })
endConnectionPathUpdate?.(id)
} catch (err: unknown) {
updateData({
loading: false,
error: err instanceof Error ? err.message : 'Agent request failed',
outputMarkdown: undefined,
})
endConnectionPathUpdate?.(id)
} finally {
setRunning(false)
}
@@ -131,6 +137,7 @@ function AgentNodeComponent({ id, data, width, height, selected }: Props) {
<BaseNode
className="min-w-[360px] min-h-[320px]"
dimensions={dimensions}
nodeId={id}
selected={selected}
handles={
<>

View File

@@ -50,6 +50,8 @@ type ViewMode = 'preview' | 'raw'
function RenderingNodeComponent({ id, data, width, height, selected }: Props) {
const flowContext = useContext(FlowContext)
const setFullscreenNodeId = flowContext?.setFullscreenNodeId
const startConnectionPathUpdate = flowContext?.startConnectionPathUpdate
const endConnectionPathUpdate = flowContext?.endConnectionPathUpdate
const supportsFullscreen = getNodeType('render')?.supportsFullscreen
const [renderedContent, setRenderedContent] = useState<string | null>(null)
const [resolvedContent, setResolvedContent] = useState<string | null>(null)
@@ -61,6 +63,7 @@ function RenderingNodeComponent({ id, data, width, height, selected }: Props) {
const runIdRef = useRef(0)
const loadingStartedAtRef = useRef<number | null>(null)
const minLoadingTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const isInitialRunRef = useRef(true)
const { nodes, edges, setNodes, setEdges, sourceIds, updateData } = useAbstractNode<RenderingNodeData>(id, data ?? {})
const viewportWidth = data?.viewportWidth ?? DEFAULT_VIEWPORT_WIDTH
const viewportHeight = data?.viewportHeight ?? DEFAULT_VIEWPORT_HEIGHT
@@ -195,6 +198,8 @@ function RenderingNodeComponent({ id, data, width, height, selected }: Props) {
message: isAgentSource ? 'Run the Agent node to generate output.' : 'No content on connected configuration node',
})
setLoading(false)
if (!isInitialRunRef.current) endConnectionPathUpdate?.(id)
isInitialRunRef.current = false
return
}
if (incomingIds.length === 0) {
@@ -202,6 +207,8 @@ function RenderingNodeComponent({ id, data, width, height, selected }: Props) {
setResolvedContent(null)
setError(null)
setLoading(false)
if (!isInitialRunRef.current) endConnectionPathUpdate?.(id)
isInitialRunRef.current = false
return
}
@@ -212,6 +219,7 @@ function RenderingNodeComponent({ id, data, width, height, selected }: Props) {
const run = async () => {
loadingStartedAtRef.current = Date.now()
setLoading(true)
if (!isInitialRunRef.current) startConnectionPathUpdate?.(id)
setError(null)
try {
if (srcNode?.type === 'agent') {
@@ -223,6 +231,8 @@ function RenderingNodeComponent({ id, data, width, height, selected }: Props) {
setRenderedContent(html)
setError(null)
setLoading(false)
if (!isInitialRunRef.current) endConnectionPathUpdate?.(id)
isInitialRunRef.current = false
return
}
const configIdsUsed = new Set<string>()
@@ -497,6 +507,8 @@ function RenderingNodeComponent({ id, data, width, height, selected }: Props) {
setResolvedContent(null)
setError({ kind: 'render', message: `Nunjucks: ${nunjucksErr.message}` })
setLoading(false)
if (!isInitialRunRef.current) endConnectionPathUpdate?.(id)
isInitialRunRef.current = false
return
}
@@ -524,10 +536,16 @@ function RenderingNodeComponent({ id, data, width, height, selected }: Props) {
if (remaining > 0) {
minLoadingTimeoutRef.current = setTimeout(() => {
minLoadingTimeoutRef.current = null
if (!cancelled && thisRunId === runIdRef.current) setLoading(false)
if (!cancelled && thisRunId === runIdRef.current) {
setLoading(false)
if (!isInitialRunRef.current) endConnectionPathUpdate?.(id)
isInitialRunRef.current = false
}
}, remaining)
} else {
setLoading(false)
if (!isInitialRunRef.current) endConnectionPathUpdate?.(id)
isInitialRunRef.current = false
}
}
}
@@ -537,6 +555,8 @@ function RenderingNodeComponent({ id, data, width, height, selected }: Props) {
setRenderedContent(null)
setError({ kind: 'render', message: err?.message ?? 'Render error' })
setLoading(false)
if (!isInitialRunRef.current) endConnectionPathUpdate?.(id)
isInitialRunRef.current = false
}
}
}
@@ -549,6 +569,8 @@ function RenderingNodeComponent({ id, data, width, height, selected }: Props) {
clearTimeout(minLoadingTimeoutRef.current)
minLoadingTimeoutRef.current = null
}
if (!isInitialRunRef.current) endConnectionPathUpdate?.(id)
isInitialRunRef.current = false
}
// Only re-run when inputs that affect the resolved output change (signatures + source). Debounced to avoid excessive re-renders while typing. retryCount triggers re-run on Retry.
}, [id, sourceContent, configTypeId, configSignature, edgesSignature, variablesSignature, functionsSignature, dataSignature, viewportWidth, viewportHeight, retryCount, isAgentSource])

View File

@@ -84,7 +84,7 @@ function VariableNodeComponent({ id, data, width, height, selected }: Props) {
)
return (
<BaseNode className="min-w-56 min-h-[180px]" dimensions={dimensions} selected={selected} handles={<OutputHandle id="out" />}>
<BaseNode className="min-w-56 min-h-[180px]" dimensions={dimensions} nodeId={id} selected={selected} handles={<OutputHandle id="out" />}>
<BaseNodeHeaderRow icon={<Variable className="size-4" />} title={<NodeHeaderTitle nodeId={id} displayTitle={id} />} />
<BaseNodeContent>