Browse Source

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 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 4 months ago
parent
commit
16c444e3d0
  1. 12
      src/app/api/models/route.ts
  2. 33
      src/components/edges/EditableEdge.tsx
  3. 5
      src/components/edges/SharedEdgeGradients.tsx
  4. 28
      src/utils/nodeDimensions.ts

12
src/app/api/models/route.ts

@ -972,7 +972,17 @@ export async function GET(
includeGemini = true; includeGemini = true;
} else if (providerFilter === "kie") { } else if (providerFilter === "kie") {
// Only Kie requested - no external API calls needed (hardcoded models) // Only Kie requested - no external API calls needed (hardcoded models)
includeKie = true; if (kieKey) {
includeKie = true;
} else {
return NextResponse.json<ModelsErrorResponse>(
{
success: false,
error: "Kie API key required. Add KIE_API_KEY to .env.local or configure in Settings.",
},
{ status: 400 }
);
}
} else if (providerFilter === "wavespeed") { } else if (providerFilter === "wavespeed") {
if (wavespeedKey) { if (wavespeedKey) {
// WaveSpeed requested with key - fetch from API // WaveSpeed requested with key - fetch from API

33
src/components/edges/EditableEdge.tsx

@ -18,11 +18,17 @@ interface EdgeData extends WorkflowEdgeData {
} }
// Colors for different connection types (dimmed for softer appearance) // Colors for different connection types (dimmed for softer appearance)
const EDGE_COLORS = { const EDGE_COLORS: Record<string, string> = {
image: "#0d9668", // Dimmed green for image connections image: "#0d9668", // Green for image connections
prompt: "#2563eb", // Dimmed blue for prompt connections prompt: "#2563eb", // Blue for prompt connections
default: "#64748b", // Dimmed gray for unknown default: "#64748b", // Gray for unknown
pause: "#ea580c", // Dimmed orange for paused edges 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({ export function EditableEdge({
@ -69,15 +75,22 @@ export function EditableEdge({
const edgeColor = useMemo(() => { const edgeColor = useMemo(() => {
if (hasPause) return EDGE_COLORS.pause; if (hasPause) return EDGE_COLORS.pause;
// Use source handle to determine color (or target if source is not available) // Use source handle to determine color (or target if source is not available)
const handleType = sourceHandleId || targetHandleId; // Strip numeric suffixes (e.g., "image-0" -> "image") for lookup
if (handleType === "image") return EDGE_COLORS.image; const handleType = sourceHandleId || targetHandleId || "";
if (handleType === "prompt") return EDGE_COLORS.prompt; const normalizedType = handleType.replace(/-\d+$/, "");
return EDGE_COLORS.default; return EDGE_COLORS[normalizedType] || EDGE_COLORS.default;
}, [hasPause, sourceHandleId, targetHandleId]); }, [hasPause, sourceHandleId, targetHandleId]);
// Reference shared gradient by color key + selection state // Reference shared gradient by color key + selection state
const gradientId = useMemo(() => { 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"; const selectionKey = isConnectedToSelection ? "active" : "dimmed";
return getSharedGradientId(colorKey, selectionKey); return getSharedGradientId(colorKey, selectionKey);
}, [hasPause, sourceHandleId, targetHandleId, isConnectedToSelection]); }, [hasPause, sourceHandleId, targetHandleId, isConnectedToSelection]);

5
src/components/edges/SharedEdgeGradients.tsx

@ -10,6 +10,11 @@ const EDGE_COLORS: Record<string, string> = {
default: "#64748b", default: "#64748b",
pause: "#ea580c", pause: "#ea580c",
reference: "#52525b", reference: "#52525b",
video: "#a855f7",
audio: "#f97316",
text: "#2563eb",
"3d": "#06b6d4",
easeCurve: "#f59e0b",
}; };
const SELECTION_STATES = ["active", "dimmed"] as const; const SELECTION_STATES = ["active", "dimmed"] as const;

28
src/utils/nodeDimensions.ts

@ -11,7 +11,7 @@ export function getImageDimensions(
base64DataUrl: string base64DataUrl: string
): Promise<{ width: number; height: number } | null> { ): Promise<{ width: number; height: number } | null> {
return new Promise((resolve) => { return new Promise((resolve) => {
if (!base64DataUrl || !base64DataUrl.startsWith("data:image")) { if (!base64DataUrl || (!base64DataUrl.startsWith("data:image") && !base64DataUrl.startsWith("http"))) {
resolve(null); resolve(null);
return; return;
} }
@ -103,12 +103,26 @@ export function getMediaDimensions(
return getImageDimensions(url); return getImageDimensions(url);
} }
// data:video/*, blob:*, or http(s) URLs → treat as video // data:video/* → always video
if ( if (url.startsWith("data:video")) {
url.startsWith("data:video") || return getVideoDimensions(url);
url.startsWith("blob:") || }
url.startsWith("http")
) { // 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); return getVideoDimensions(url);
} }

Loading…
Cancel
Save