Implement agent node functionality in canvas, including connection policies and UI updates. Add support for agent node type in node catalog, templates, and connection validation. Update documentation to reflect new agent capabilities and ensure proper handling of input sources. Enhance adjustment preview to include crop node. Add tests for agent connection policies.

This commit is contained in:
2026-04-09 10:06:53 +02:00
parent b7f24223f2
commit 6d0c7b1ff6
18 changed files with 749 additions and 5 deletions

View File

@@ -12,7 +12,7 @@ import {
import type { PipelineStep } from "@/lib/image-pipeline/contracts";
import { buildHistogramPlot } from "@/lib/image-pipeline/histogram-plot";
const PREVIEW_PIPELINE_TYPES = new Set(["curves", "color-adjust", "light-adjust", "detail-adjust"]);
const PREVIEW_PIPELINE_TYPES = new Set(["crop", "curves", "color-adjust", "light-adjust", "detail-adjust"]);
export default function AdjustmentPreview({
nodeId,

View File

@@ -0,0 +1,90 @@
"use client";
import { Bot } from "lucide-react";
import { Handle, Position, type Node, type NodeProps } from "@xyflow/react";
import { getAgentTemplate } from "@/lib/agent-templates";
import BaseNodeWrapper from "./base-node-wrapper";
type AgentNodeData = {
templateId?: string;
canvasId?: string;
_status?: string;
_statusMessage?: string;
};
type AgentNodeType = Node<AgentNodeData, "agent">;
const DEFAULT_AGENT_TEMPLATE_ID = "campaign-distributor";
function CompactList({ items }: { items: readonly string[] }) {
return (
<ul className="space-y-1">
{items.slice(0, 4).map((item) => (
<li key={item} className="truncate text-[11px] text-foreground/90" title={item}>
- {item}
</li>
))}
</ul>
);
}
export default function AgentNode({ data, selected }: NodeProps<AgentNodeType>) {
const nodeData = data as AgentNodeData;
const template =
getAgentTemplate(nodeData.templateId ?? DEFAULT_AGENT_TEMPLATE_ID) ??
getAgentTemplate(DEFAULT_AGENT_TEMPLATE_ID);
if (!template) {
return null;
}
return (
<BaseNodeWrapper
nodeType="agent"
selected={selected}
status={nodeData._status}
statusMessage={nodeData._statusMessage}
className="min-w-[300px] border-amber-500/30"
>
<Handle
type="target"
position={Position.Left}
id="agent-in"
className="!h-3 !w-3 !bg-amber-500 !border-2 !border-background"
/>
<div className="flex h-full flex-col gap-3 p-3">
<header className="space-y-1">
<div className="flex items-center gap-1.5 text-xs font-medium text-amber-700 dark:text-amber-300">
<Bot className="h-3.5 w-3.5" />
<span>{template.emoji}</span>
<span>{template.name}</span>
</div>
<p className="line-clamp-2 text-xs text-muted-foreground">{template.description}</p>
</header>
<section className="space-y-1">
<p className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">
Channels
</p>
<CompactList items={template.channels} />
</section>
<section className="space-y-1">
<p className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">
Expected Inputs
</p>
<CompactList items={template.expectedInputs} />
</section>
<section className="space-y-1">
<p className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">
Expected Outputs
</p>
<CompactList items={template.expectedOutputs} />
</section>
</div>
</BaseNodeWrapper>
);
}

View File

@@ -45,6 +45,7 @@ const RESIZE_CONFIGS: Record<string, ResizeConfig> = {
"detail-adjust": { minWidth: 300, minHeight: 820 },
crop: { minWidth: 320, minHeight: 520 },
render: { minWidth: 260, minHeight: 300, keepAspectRatio: true },
agent: { minWidth: 300, minHeight: 280 },
text: { minWidth: 220, minHeight: 90 },
note: { minWidth: 200, minHeight: 90 },
};