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:
@@ -13,6 +13,7 @@ Geteilte Hilfsfunktionen, Typ-Definitionen und Konfiguration. Keine React-Kompon
|
||||
| `canvas-node-types.ts` | TypeScript-Typen und Union-Typen für Canvas-Nodes |
|
||||
| `canvas-node-templates.ts` | Default-Daten für neue Nodes (beim Einfügen aus Palette) |
|
||||
| `canvas-connection-policy.ts` | Validierungsregeln für Edge-Verbindungen zwischen Nodes |
|
||||
| `agent-templates.ts` | Typsichere Agent-Registry für statische Agent-Node-Metadaten |
|
||||
| `ai-models.ts` | Client-seitige Bild-Modell-Definitionen (muss mit `convex/openrouter.ts` in sync bleiben) |
|
||||
| `ai-video-models.ts` | Video-Modell-Registry: 5 MVP-Modelle mit Endpunkten, Credit-Kosten, Tier-Zugang |
|
||||
| `video-poll-logging.ts` | Log-Volumen-Steuerung für Video-Polling (vermeidet excessive Konsolenausgabe) |
|
||||
@@ -48,6 +49,7 @@ Alle Adapter-Funktionen zwischen Convex-Datenmodell und React Flow. Details in `
|
||||
- `NODE_DEFAULTS` — Default-Größen und Daten per Node-Typ (inkl. `video-prompt` und `ai-video`)
|
||||
- `NODE_HANDLE_MAP` — Handle-IDs pro Node-Typ (inkl. `video-prompt-out/in` und `video-out/in`)
|
||||
- `SOURCE_NODE_GLOW_RGB` — Edge-Glow-Farben pro Source-Node-Typ (inkl. `video-prompt` und `ai-video`)
|
||||
- `agent` ist als input-only Node enthalten (`NODE_HANDLE_MAP.agent = { target: "agent-in" }`)
|
||||
- `computeBridgeCreatesForDeletedNodes` — Kanten-Reconnect nach Node-Löschung
|
||||
- `computeMediaNodeSize` — Dynamische Node-Größe basierend auf Bild-Dimensionen
|
||||
|
||||
@@ -112,6 +114,7 @@ Default-Initial-Daten für neue Nodes beim Einfügen aus Palette.
|
||||
- Erstellt durch die Node-Katalog-Einträge
|
||||
- Enthält default-Werte für `data`-Felder
|
||||
- `video-prompt` hat Default-Daten: `{ modelId: "wan-2-2-720p", durationSeconds: 5 }`
|
||||
- `agent` hat aktuell ein statisches Template-Default: `{ templateId: "campaign-distributor" }`
|
||||
- Wird von `canvas.tsx` verwendet beim Node-Create
|
||||
|
||||
---
|
||||
@@ -134,6 +137,7 @@ Regeln für erlaubte Verbindungen zwischen Node-Typen.
|
||||
- `ai-video` als Target akzeptiert nur `video-prompt` als Source (`ai-video-source-invalid`)
|
||||
- `video-prompt` als Source akzeptiert nur `ai-video` als Target (`video-prompt-target-invalid`)
|
||||
- `text → video-prompt` ✅ (Prompt-Quelle, über default-Handles)
|
||||
- **Agent-MVP:** `agent` akzeptiert nur Content-/Kontext-Quellen (`agent-source-invalid` bei Prompt/Steuerknoten), ohne eingehendes Kantenlimit
|
||||
- Curves- und Adjustment-Node-Presets: Nur Presets nutzen, keine direkten Edges
|
||||
|
||||
---
|
||||
|
||||
69
lib/agent-templates.ts
Normal file
69
lib/agent-templates.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
export type AgentTemplateId = "campaign-distributor";
|
||||
|
||||
export type AgentTemplate = {
|
||||
id: AgentTemplateId;
|
||||
name: string;
|
||||
description: string;
|
||||
emoji: string;
|
||||
color: string;
|
||||
vibe: string;
|
||||
tools: readonly string[];
|
||||
channels: readonly string[];
|
||||
expectedInputs: readonly string[];
|
||||
expectedOutputs: readonly string[];
|
||||
notes: readonly string[];
|
||||
};
|
||||
|
||||
export const AGENT_TEMPLATES: readonly AgentTemplate[] = [
|
||||
{
|
||||
id: "campaign-distributor",
|
||||
name: "Campaign Distributor",
|
||||
description:
|
||||
"Develops and distributes LemonSpace campaign content across social media and messenger channels.",
|
||||
emoji: "lemon",
|
||||
color: "yellow",
|
||||
vibe: "Transforms canvas outputs into campaign-ready channel content.",
|
||||
tools: ["WebFetch", "WebSearch", "Read", "Write", "Edit"],
|
||||
channels: [
|
||||
"Instagram Feed",
|
||||
"Instagram Stories",
|
||||
"Instagram Reels",
|
||||
"LinkedIn",
|
||||
"Twitter / X",
|
||||
"TikTok",
|
||||
"Pinterest",
|
||||
"WhatsApp Business",
|
||||
"Telegram",
|
||||
"E-Mail Newsletter",
|
||||
"Discord",
|
||||
],
|
||||
expectedInputs: [
|
||||
"Render-Node-Export",
|
||||
"Compare-Varianten",
|
||||
"KI-Bild-Output",
|
||||
"Frame-Dimensionen",
|
||||
],
|
||||
expectedOutputs: [
|
||||
"Caption-Pakete",
|
||||
"Kanal-Matrix",
|
||||
"Posting-Plan",
|
||||
"Hashtag-Sets",
|
||||
"Messenger-Texte",
|
||||
],
|
||||
notes: [
|
||||
"MVP: static input-only node, no execution flow.",
|
||||
"agent-output remains pending until runtime orchestration exists.",
|
||||
],
|
||||
},
|
||||
] as const;
|
||||
|
||||
const AGENT_TEMPLATE_BY_ID = new Map<AgentTemplateId, AgentTemplate>(
|
||||
AGENT_TEMPLATES.map((template) => [template.id, template]),
|
||||
);
|
||||
|
||||
export function getAgentTemplate(id: string): AgentTemplate | undefined {
|
||||
if (id === "campaign-distributor") {
|
||||
return AGENT_TEMPLATE_BY_ID.get(id);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -37,6 +37,19 @@ const RENDER_ALLOWED_SOURCE_TYPES = new Set<string>([
|
||||
"detail-adjust",
|
||||
]);
|
||||
|
||||
const AGENT_ALLOWED_SOURCE_TYPES = new Set<string>([
|
||||
"image",
|
||||
"asset",
|
||||
"video",
|
||||
"text",
|
||||
"note",
|
||||
"frame",
|
||||
"compare",
|
||||
"render",
|
||||
"ai-image",
|
||||
"ai-video",
|
||||
]);
|
||||
|
||||
const ADJUSTMENT_DISALLOWED_TARGET_TYPES = new Set<string>(["prompt", "ai-image"]);
|
||||
|
||||
export type CanvasConnectionValidationReason =
|
||||
@@ -51,7 +64,8 @@ export type CanvasConnectionValidationReason =
|
||||
| "crop-incoming-limit"
|
||||
| "compare-incoming-limit"
|
||||
| "adjustment-target-forbidden"
|
||||
| "render-source-invalid";
|
||||
| "render-source-invalid"
|
||||
| "agent-source-invalid";
|
||||
|
||||
export function validateCanvasConnectionPolicy(args: {
|
||||
sourceType: string;
|
||||
@@ -81,6 +95,10 @@ export function validateCanvasConnectionPolicy(args: {
|
||||
}
|
||||
}
|
||||
|
||||
if (targetType === "agent" && !AGENT_ALLOWED_SOURCE_TYPES.has(sourceType)) {
|
||||
return "agent-source-invalid";
|
||||
}
|
||||
|
||||
if (isAdjustmentNodeType(targetType) && targetType !== "render") {
|
||||
if (!ADJUSTMENT_ALLOWED_SOURCE_TYPES.has(sourceType)) {
|
||||
return "adjustment-source-invalid";
|
||||
@@ -132,6 +150,8 @@ export function getCanvasConnectionValidationMessage(
|
||||
return "Adjustment-Ausgaben koennen nicht an Prompt- oder KI-Bild-Nodes angeschlossen werden.";
|
||||
case "render-source-invalid":
|
||||
return "Render akzeptiert nur Bild-, Asset-, KI-Bild-, Crop- oder Adjustment-Input.";
|
||||
case "agent-source-invalid":
|
||||
return "Agent-Nodes akzeptieren nur Content- und Kontext-Inputs, keine Generierungs-Steuerknoten wie Prompt.";
|
||||
default:
|
||||
return "Verbindung ist fuer diese Node-Typen nicht erlaubt.";
|
||||
}
|
||||
|
||||
@@ -220,8 +220,6 @@ export const NODE_CATALOG: readonly NodeCatalogEntry[] = [
|
||||
label: "Agent",
|
||||
category: "control",
|
||||
phase: 2,
|
||||
implemented: false,
|
||||
disabledHint: "Folgt in Phase 2",
|
||||
}),
|
||||
entry({
|
||||
type: "mixer",
|
||||
|
||||
@@ -34,6 +34,15 @@ export const CANVAS_NODE_TEMPLATES = [
|
||||
hasAudio: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "agent",
|
||||
label: "Campaign Distributor",
|
||||
width: 360,
|
||||
height: 320,
|
||||
defaultData: {
|
||||
templateId: "campaign-distributor",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "note",
|
||||
label: "Notiz",
|
||||
|
||||
@@ -120,6 +120,7 @@ const SOURCE_NODE_GLOW_RGB: Record<string, readonly [number, number, number]> =
|
||||
"detail-adjust": [99, 102, 241],
|
||||
crop: [139, 92, 246],
|
||||
render: [14, 165, 233],
|
||||
agent: [245, 158, 11],
|
||||
};
|
||||
|
||||
/** Compare: Ziel-Handles blau/smaragd, Quelle compare-out grau (wie in compare-node.tsx). */
|
||||
@@ -227,6 +228,7 @@ export const NODE_HANDLE_MAP: Record<
|
||||
"detail-adjust": { source: undefined, target: undefined },
|
||||
crop: { source: undefined, target: undefined },
|
||||
render: { source: undefined, target: undefined },
|
||||
agent: { target: "agent-in" },
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -276,6 +278,11 @@ export const NODE_DEFAULTS: Record<
|
||||
height: 420,
|
||||
data: { outputResolution: "original", format: "png", jpegQuality: 90 },
|
||||
},
|
||||
agent: {
|
||||
width: 360,
|
||||
height: 320,
|
||||
data: { templateId: "campaign-distributor" },
|
||||
},
|
||||
};
|
||||
|
||||
type MediaNodeKind = "asset" | "image";
|
||||
|
||||
Reference in New Issue
Block a user