From 16c444e3d00b71faeab6e5594c8e1781fb759cba Mon Sep 17 00:00:00 2001 From: shrimbly Date: Wed, 11 Mar 2026 14:03:02 +1300 Subject: [PATCH] fix(24-01): fix edge colors, Kie API gating, and media dimension detection - Add video, audio, text, 3d, easeCurve entries to EDGE_COLORS in SharedEdgeGradients - Normalize handle type suffixes (e.g. image-0 -> image) in EditableEdge color lookup - Gate Kie provider filter behind API key check (return 400 if missing) - Fix getMediaDimensions to check URL pathname for image extensions before defaulting to video Co-Authored-By: Claude Opus 4.6 --- src/app/api/models/route.ts | 12 ++++++- src/components/edges/EditableEdge.tsx | 33 ++++++++++++++------ src/components/edges/SharedEdgeGradients.tsx | 5 +++ src/utils/nodeDimensions.ts | 28 ++++++++++++----- 4 files changed, 60 insertions(+), 18 deletions(-) diff --git a/src/app/api/models/route.ts b/src/app/api/models/route.ts index 7b943a64..e5670103 100644 --- a/src/app/api/models/route.ts +++ b/src/app/api/models/route.ts @@ -972,7 +972,17 @@ export async function GET( includeGemini = true; } else if (providerFilter === "kie") { // Only Kie requested - no external API calls needed (hardcoded models) - includeKie = true; + if (kieKey) { + includeKie = true; + } else { + return NextResponse.json( + { + success: false, + error: "Kie API key required. Add KIE_API_KEY to .env.local or configure in Settings.", + }, + { status: 400 } + ); + } } else if (providerFilter === "wavespeed") { if (wavespeedKey) { // WaveSpeed requested with key - fetch from API diff --git a/src/components/edges/EditableEdge.tsx b/src/components/edges/EditableEdge.tsx index 32e57291..eee67525 100644 --- a/src/components/edges/EditableEdge.tsx +++ b/src/components/edges/EditableEdge.tsx @@ -18,11 +18,17 @@ interface EdgeData extends WorkflowEdgeData { } // Colors for different connection types (dimmed for softer appearance) -const EDGE_COLORS = { - image: "#0d9668", // Dimmed green for image connections - prompt: "#2563eb", // Dimmed blue for prompt connections - default: "#64748b", // Dimmed gray for unknown - pause: "#ea580c", // Dimmed orange for paused edges +const EDGE_COLORS: Record = { + image: "#0d9668", // Green for image connections + prompt: "#2563eb", // Blue for prompt connections + default: "#64748b", // Gray for unknown + pause: "#ea580c", // Orange for paused edges + reference: "#52525b", // Gray for reference connections + video: "#a855f7", // Purple for video connections + audio: "#f97316", // Orange for audio connections + text: "#2563eb", // Blue for text connections + "3d": "#06b6d4", // Cyan for 3D connections + easeCurve: "#f59e0b", // Amber for ease curve connections }; export function EditableEdge({ @@ -69,15 +75,22 @@ export function EditableEdge({ const edgeColor = useMemo(() => { if (hasPause) return EDGE_COLORS.pause; // Use source handle to determine color (or target if source is not available) - const handleType = sourceHandleId || targetHandleId; - if (handleType === "image") return EDGE_COLORS.image; - if (handleType === "prompt") return EDGE_COLORS.prompt; - return EDGE_COLORS.default; + // Strip numeric suffixes (e.g., "image-0" -> "image") for lookup + const handleType = sourceHandleId || targetHandleId || ""; + const normalizedType = handleType.replace(/-\d+$/, ""); + return EDGE_COLORS[normalizedType] || EDGE_COLORS.default; }, [hasPause, sourceHandleId, targetHandleId]); // Reference shared gradient by color key + selection state const gradientId = useMemo(() => { - const colorKey = hasPause ? "pause" : (sourceHandleId || targetHandleId || "default"); + if (hasPause) { + const selectionKey = isConnectedToSelection ? "active" : "dimmed"; + return getSharedGradientId("pause", selectionKey); + } + const handleType = sourceHandleId || targetHandleId || "default"; + const normalizedType = handleType.replace(/-\d+$/, ""); + // Use the normalized type if it exists in EDGE_COLORS, otherwise fall back to "default" + const colorKey = normalizedType in EDGE_COLORS ? normalizedType : "default"; const selectionKey = isConnectedToSelection ? "active" : "dimmed"; return getSharedGradientId(colorKey, selectionKey); }, [hasPause, sourceHandleId, targetHandleId, isConnectedToSelection]); diff --git a/src/components/edges/SharedEdgeGradients.tsx b/src/components/edges/SharedEdgeGradients.tsx index 174ff1e7..d41f09dd 100644 --- a/src/components/edges/SharedEdgeGradients.tsx +++ b/src/components/edges/SharedEdgeGradients.tsx @@ -10,6 +10,11 @@ const EDGE_COLORS: Record = { default: "#64748b", pause: "#ea580c", reference: "#52525b", + video: "#a855f7", + audio: "#f97316", + text: "#2563eb", + "3d": "#06b6d4", + easeCurve: "#f59e0b", }; const SELECTION_STATES = ["active", "dimmed"] as const; diff --git a/src/utils/nodeDimensions.ts b/src/utils/nodeDimensions.ts index 37ded0a0..65936e9f 100644 --- a/src/utils/nodeDimensions.ts +++ b/src/utils/nodeDimensions.ts @@ -11,7 +11,7 @@ export function getImageDimensions( base64DataUrl: string ): Promise<{ width: number; height: number } | null> { return new Promise((resolve) => { - if (!base64DataUrl || !base64DataUrl.startsWith("data:image")) { + if (!base64DataUrl || (!base64DataUrl.startsWith("data:image") && !base64DataUrl.startsWith("http"))) { resolve(null); return; } @@ -103,12 +103,26 @@ export function getMediaDimensions( return getImageDimensions(url); } - // data:video/*, blob:*, or http(s) URLs → treat as video - if ( - url.startsWith("data:video") || - url.startsWith("blob:") || - url.startsWith("http") - ) { + // data:video/* → always video + if (url.startsWith("data:video")) { + return getVideoDimensions(url); + } + + // blob:* → treat as video (most common use case) + if (url.startsWith("blob:")) { + return getVideoDimensions(url); + } + + // http(s) URLs → check pathname for image extensions before defaulting to video + if (url.startsWith("http")) { + try { + const pathname = new URL(url).pathname.toLowerCase(); + if (/\.(jpe?g|png|gif|webp|bmp|svg|avif|ico)(\?|$)/.test(pathname)) { + return getImageDimensions(url); + } + } catch { + // Invalid URL, fall through to video + } return getVideoDimensions(url); }