You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

230 lines
7.0 KiB

import { NextRequest, NextResponse } from "next/server";
import * as fs from "fs/promises";
import * as path from "path";
import { logger } from "@/utils/logger";
export const maxDuration = 300; // 5 minute timeout for large image operations
const IMAGES_FOLDER = "inputs";
const LEGACY_IMAGES_FOLDER = ".images"; // For backward compatibility
// POST: Save an image to the workflow's inputs or generations folder
export async function POST(request: NextRequest) {
let workflowPath: string | undefined;
let imageId: string | undefined;
let folder: string | undefined;
try {
const body = await request.json();
workflowPath = body.workflowPath;
imageId = body.imageId;
folder = body.folder || IMAGES_FOLDER; // Default to "inputs"
const imageData = body.imageData; // Base64 data URL
// Validate folder is one of the allowed values
if (folder !== IMAGES_FOLDER && folder !== "generations") {
folder = IMAGES_FOLDER;
}
logger.info('file.save', 'Workflow image save request received', {
workflowPath,
imageId,
folder,
hasImageData: !!imageData,
});
if (!workflowPath || !imageId || !imageData) {
logger.warn('file.save', 'Workflow image save validation failed: missing fields', {
hasWorkflowPath: !!workflowPath,
hasImageId: !!imageId,
hasImageData: !!imageData,
});
return NextResponse.json(
{ success: false, error: "Missing required fields (workflowPath, imageId, imageData)" },
{ status: 400 }
);
}
// Validate workflow directory exists
try {
const stats = await fs.stat(workflowPath);
if (!stats.isDirectory()) {
logger.warn('file.error', 'Workflow image save failed: path is not a directory', {
workflowPath,
});
return NextResponse.json(
{ success: false, error: "Workflow path is not a directory" },
{ status: 400 }
);
}
} catch (dirError) {
logger.warn('file.error', 'Workflow image save failed: directory does not exist', {
workflowPath,
});
return NextResponse.json(
{ success: false, error: "Workflow directory does not exist" },
{ status: 400 }
);
}
// Create target folder if it doesn't exist
const targetFolder = path.join(workflowPath, folder);
try {
await fs.mkdir(targetFolder, { recursive: true });
} catch (mkdirError) {
logger.error('file.error', 'Failed to create target folder', {
targetFolder,
}, mkdirError instanceof Error ? mkdirError : undefined);
return NextResponse.json(
{ success: false, error: "Failed to create target folder" },
{ status: 500 }
);
}
// Construct file path
const filename = `${imageId}.png`;
const filePath = path.join(targetFolder, filename);
// Extract base64 data and convert to buffer
const base64Data = imageData.replace(/^data:image\/\w+;base64,/, "");
const buffer = Buffer.from(base64Data, "base64");
// Write the image file
await fs.writeFile(filePath, buffer);
logger.info('file.save', 'Workflow image saved successfully', {
filePath,
imageId,
fileSize: buffer.length,
});
return NextResponse.json({
success: true,
imageId,
filePath,
});
} catch (error) {
logger.error('file.error', 'Failed to save workflow image', {
workflowPath,
imageId,
}, error instanceof Error ? error : undefined);
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : "Save failed",
},
{ status: 500 }
);
}
}
// GET: Load an image from the workflow's folders (inputs, generations, or legacy .images)
export async function GET(request: NextRequest) {
const workflowPath = request.nextUrl.searchParams.get("workflowPath");
const imageId = request.nextUrl.searchParams.get("imageId");
const folder = request.nextUrl.searchParams.get("folder"); // Optional hint for which folder to check first
logger.info('file.load', 'Workflow image load request received', {
workflowPath,
imageId,
folder,
});
if (!workflowPath || !imageId) {
logger.warn('file.load', 'Workflow image load validation failed: missing parameters', {
hasWorkflowPath: !!workflowPath,
hasImageId: !!imageId,
});
return NextResponse.json(
{ success: false, error: "Missing required parameters (workflowPath, imageId)" },
{ status: 400 }
);
}
try {
// Validate workflow directory exists
try {
const stats = await fs.stat(workflowPath);
if (!stats.isDirectory()) {
return NextResponse.json(
{ success: false, error: "Workflow path is not a directory" },
{ status: 400 }
);
}
} catch {
return NextResponse.json(
{ success: false, error: "Workflow directory does not exist" },
{ status: 400 }
);
}
// Construct file path - check folders in order based on hint
const filename = `${imageId}.png`;
const inputsFolder = path.join(workflowPath, IMAGES_FOLDER);
const generationsFolder = path.join(workflowPath, "generations");
const legacyFolder = path.join(workflowPath, LEGACY_IMAGES_FOLDER);
// Build search order based on folder hint
const searchOrder = folder === "generations"
? [generationsFolder, inputsFolder, legacyFolder]
: [inputsFolder, generationsFolder, legacyFolder];
let filePath: string | null = null;
// Check each folder in order
for (const searchFolder of searchOrder) {
const candidatePath = path.join(searchFolder, filename);
try {
await fs.access(candidatePath);
filePath = candidatePath;
if (searchFolder === legacyFolder) {
logger.info('file.load', 'Found image in legacy .images folder', { filePath });
}
break;
} catch {
// File not found in this folder, try next
}
}
if (!filePath) {
logger.warn('file.error', 'Workflow image load failed: file not found', {
imageId,
searchedFolders: searchOrder,
});
return NextResponse.json(
{ success: false, error: "Image file not found" },
{ status: 404 }
);
}
// Read the image file
const buffer = await fs.readFile(filePath);
// Convert to base64 data URL
const base64 = buffer.toString("base64");
const dataUrl = `data:image/png;base64,${base64}`;
logger.info('file.load', 'Workflow image loaded successfully', {
filePath,
imageId,
fileSize: buffer.length,
});
return NextResponse.json({
success: true,
imageId,
image: dataUrl,
});
} catch (error) {
logger.error('file.error', 'Failed to load workflow image', {
workflowPath,
imageId,
}, error instanceof Error ? error : undefined);
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : "Load failed",
},
{ status: 500 }
);
}
}