Spaces:
Running
Running
making changes
Browse files- app/api/process/route.ts +75 -19
- app/editor/page.tsx +85 -6
- eslint.config.mjs +11 -0
- next.config.ts +9 -0
- package-lock.json +10 -0
- package.json +1 -0
app/api/process/route.ts
CHANGED
|
@@ -3,6 +3,9 @@ import { GoogleGenAI } from "@google/genai";
|
|
| 3 |
|
| 4 |
export const runtime = "nodejs";
|
| 5 |
|
|
|
|
|
|
|
|
|
|
| 6 |
function parseDataUrl(dataUrl: string): { mimeType: string; data: string } | null {
|
| 7 |
const match = dataUrl.match(/^data:(.*?);base64,(.*)$/);
|
| 8 |
if (!match) return null;
|
|
@@ -11,13 +14,26 @@ function parseDataUrl(dataUrl: string): { mimeType: string; data: string } | nul
|
|
| 11 |
|
| 12 |
export async function POST(req: NextRequest) {
|
| 13 |
try {
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
const apiKey = process.env.GOOGLE_API_KEY;
|
| 23 |
if (!apiKey || apiKey === 'your_actual_api_key_here') {
|
|
@@ -105,13 +121,21 @@ The result should look like all subjects were photographed together in the same
|
|
| 105 |
}
|
| 106 |
|
| 107 |
const mergeParts: any[] = [{ text: mergePrompt }];
|
| 108 |
-
for (
|
| 109 |
-
const
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
}
|
| 114 |
-
mergeParts.push({ inlineData: { mimeType: parsed.mimeType, data: parsed.data } });
|
| 115 |
}
|
| 116 |
|
| 117 |
console.log(`[MERGE] Sending ${mergeParts.length - 1} images to model`);
|
|
@@ -177,15 +201,27 @@ The result should look like all subjects were photographed together in the same
|
|
| 177 |
|
| 178 |
// Clothes modifications
|
| 179 |
if (params.clothesImage) {
|
|
|
|
|
|
|
| 180 |
if (params.selectedPreset === "Sukajan") {
|
| 181 |
prompts.push("Replace the person's clothing with a Japanese sukajan jacket (embroidered designs). Use the clothes reference image if provided.");
|
| 182 |
} else if (params.selectedPreset === "Blazer") {
|
| 183 |
prompts.push("Replace the person's clothing with a professional blazer. Use the clothes reference image if provided.");
|
| 184 |
} else {
|
| 185 |
-
prompts.push("Replace the person
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
}
|
| 187 |
-
const clothesRef = await toInlineDataFromAny(params.clothesImage);
|
| 188 |
-
if (clothesRef) referenceParts.push({ inlineData: clothesRef });
|
| 189 |
}
|
| 190 |
|
| 191 |
// Style application
|
|
@@ -307,8 +343,28 @@ The result should look like all subjects were photographed together in the same
|
|
| 307 |
}
|
| 308 |
|
| 309 |
return NextResponse.json({ image: images[0] });
|
| 310 |
-
} catch (err) {
|
| 311 |
-
console.error("/api/process error", err);
|
| 312 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 313 |
}
|
| 314 |
}
|
|
|
|
| 3 |
|
| 4 |
export const runtime = "nodejs";
|
| 5 |
|
| 6 |
+
// Increase the body size limit to 50MB for large images
|
| 7 |
+
export const maxDuration = 60; // 60 seconds timeout
|
| 8 |
+
|
| 9 |
function parseDataUrl(dataUrl: string): { mimeType: string; data: string } | null {
|
| 10 |
const match = dataUrl.match(/^data:(.*?);base64,(.*)$/);
|
| 11 |
if (!match) return null;
|
|
|
|
| 14 |
|
| 15 |
export async function POST(req: NextRequest) {
|
| 16 |
try {
|
| 17 |
+
// Log request size for debugging
|
| 18 |
+
const contentLength = req.headers.get('content-length');
|
| 19 |
+
console.log(`[API] Request size: ${contentLength} bytes`);
|
| 20 |
+
|
| 21 |
+
let body: any;
|
| 22 |
+
try {
|
| 23 |
+
body = await req.json() as {
|
| 24 |
+
type: string;
|
| 25 |
+
image?: string;
|
| 26 |
+
images?: string[];
|
| 27 |
+
prompt?: string;
|
| 28 |
+
params?: any;
|
| 29 |
+
};
|
| 30 |
+
} catch (jsonError) {
|
| 31 |
+
console.error('[API] Failed to parse JSON:', jsonError);
|
| 32 |
+
return NextResponse.json(
|
| 33 |
+
{ error: "Invalid JSON in request body. This might be due to large image data or special characters." },
|
| 34 |
+
{ status: 400 }
|
| 35 |
+
);
|
| 36 |
+
}
|
| 37 |
|
| 38 |
const apiKey = process.env.GOOGLE_API_KEY;
|
| 39 |
if (!apiKey || apiKey === 'your_actual_api_key_here') {
|
|
|
|
| 121 |
}
|
| 122 |
|
| 123 |
const mergeParts: any[] = [{ text: mergePrompt }];
|
| 124 |
+
for (let i = 0; i < imgs.length; i++) {
|
| 125 |
+
const url = imgs[i];
|
| 126 |
+
console.log(`[MERGE] Processing image ${i + 1}/${imgs.length}, type: ${typeof url}, length: ${url?.length || 0}`);
|
| 127 |
+
|
| 128 |
+
try {
|
| 129 |
+
const parsed = await toInlineDataFromAny(url);
|
| 130 |
+
if (!parsed) {
|
| 131 |
+
console.error(`[MERGE] Failed to parse image ${i + 1}:`, url.substring(0, 100));
|
| 132 |
+
continue;
|
| 133 |
+
}
|
| 134 |
+
mergeParts.push({ inlineData: { mimeType: parsed.mimeType, data: parsed.data } });
|
| 135 |
+
console.log(`[MERGE] Successfully processed image ${i + 1}`);
|
| 136 |
+
} catch (error) {
|
| 137 |
+
console.error(`[MERGE] Error processing image ${i + 1}:`, error);
|
| 138 |
}
|
|
|
|
| 139 |
}
|
| 140 |
|
| 141 |
console.log(`[MERGE] Sending ${mergeParts.length - 1} images to model`);
|
|
|
|
| 201 |
|
| 202 |
// Clothes modifications
|
| 203 |
if (params.clothesImage) {
|
| 204 |
+
console.log(`[API] Processing clothes image, type: ${typeof params.clothesImage}, length: ${params.clothesImage?.length || 0}`);
|
| 205 |
+
|
| 206 |
if (params.selectedPreset === "Sukajan") {
|
| 207 |
prompts.push("Replace the person's clothing with a Japanese sukajan jacket (embroidered designs). Use the clothes reference image if provided.");
|
| 208 |
} else if (params.selectedPreset === "Blazer") {
|
| 209 |
prompts.push("Replace the person's clothing with a professional blazer. Use the clothes reference image if provided.");
|
| 210 |
} else {
|
| 211 |
+
prompts.push("Replace the clothes of person from Image 1 to match the provided clothes reference image (attached below). Preserve body pose and identity.");
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
try {
|
| 215 |
+
const clothesRef = await toInlineDataFromAny(params.clothesImage);
|
| 216 |
+
if (clothesRef) {
|
| 217 |
+
console.log(`[API] Successfully processed clothes image`);
|
| 218 |
+
referenceParts.push({ inlineData: clothesRef });
|
| 219 |
+
} else {
|
| 220 |
+
console.error('[API] Failed to process clothes image - toInlineDataFromAny returned null');
|
| 221 |
+
}
|
| 222 |
+
} catch (error) {
|
| 223 |
+
console.error('[API] Error processing clothes image:', error);
|
| 224 |
}
|
|
|
|
|
|
|
| 225 |
}
|
| 226 |
|
| 227 |
// Style application
|
|
|
|
| 343 |
}
|
| 344 |
|
| 345 |
return NextResponse.json({ image: images[0] });
|
| 346 |
+
} catch (err: any) {
|
| 347 |
+
console.error("/api/process error:", err);
|
| 348 |
+
console.error("Error stack:", err?.stack);
|
| 349 |
+
|
| 350 |
+
// Provide more specific error messages
|
| 351 |
+
if (err?.message?.includes('payload size')) {
|
| 352 |
+
return NextResponse.json(
|
| 353 |
+
{ error: "Image data too large. Please use smaller images or reduce image quality." },
|
| 354 |
+
{ status: 413 }
|
| 355 |
+
);
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
if (err?.message?.includes('JSON')) {
|
| 359 |
+
return NextResponse.json(
|
| 360 |
+
{ error: "Invalid data format. Please ensure images are properly encoded." },
|
| 361 |
+
{ status: 400 }
|
| 362 |
+
);
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
return NextResponse.json(
|
| 366 |
+
{ error: `Failed to process image: ${err?.message || 'Unknown error'}` },
|
| 367 |
+
{ status: 500 }
|
| 368 |
+
);
|
| 369 |
}
|
| 370 |
}
|
app/editor/page.tsx
CHANGED
|
@@ -57,7 +57,7 @@ The result should look like all subjects were photographed together in the same
|
|
| 57 |
}
|
| 58 |
|
| 59 |
// Types
|
| 60 |
-
type NodeType = "CHARACTER" | "MERGE" | "BACKGROUND" | "CLOTHES" | "STYLE" | "EDIT" | "CAMERA" | "AGE" | "FACE";
|
| 61 |
|
| 62 |
type NodeBase = {
|
| 63 |
id: string;
|
|
@@ -167,7 +167,16 @@ type FaceNode = NodeBase & {
|
|
| 167 |
error?: string | null;
|
| 168 |
};
|
| 169 |
|
| 170 |
-
type
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
|
| 172 |
// Default placeholder portrait
|
| 173 |
const DEFAULT_PERSON =
|
|
@@ -506,7 +515,8 @@ function MergeNodeView({
|
|
| 506 |
image = (inputNode as any).output;
|
| 507 |
label = `${inputNode.type}`;
|
| 508 |
} else if (inputNode.type === "MERGE" && (inputNode as MergeNode).output) {
|
| 509 |
-
|
|
|
|
| 510 |
label = "Merged";
|
| 511 |
} else {
|
| 512 |
// Node without output yet
|
|
@@ -960,7 +970,7 @@ export default function EditorPage() {
|
|
| 960 |
|
| 961 |
// Update the merge node with output
|
| 962 |
setNodes(prev => prev.map(n =>
|
| 963 |
-
n.id === merge.id ? { ...n, output: mergeOutput, isRunning: false, error: null } : n
|
| 964 |
));
|
| 965 |
|
| 966 |
// Track that we processed this merge as part of the chain
|
|
@@ -1028,6 +1038,36 @@ export default function EditorPage() {
|
|
| 1028 |
}));
|
| 1029 |
|
| 1030 |
try {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1031 |
// Make a SINGLE API call with all accumulated parameters
|
| 1032 |
const res = await fetch("/api/process", {
|
| 1033 |
method: "POST",
|
|
@@ -1039,6 +1079,14 @@ export default function EditorPage() {
|
|
| 1039 |
}),
|
| 1040 |
});
|
| 1041 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1042 |
const data = await res.json();
|
| 1043 |
if (!res.ok) throw new Error(data.error || "Processing failed");
|
| 1044 |
|
|
@@ -1156,11 +1204,17 @@ export default function EditorPage() {
|
|
| 1156 |
label = `${inputNode.type} Output`;
|
| 1157 |
} else if (inputNode.type === "MERGE" && (inputNode as MergeNode).output) {
|
| 1158 |
// Another merge node's output
|
| 1159 |
-
|
|
|
|
| 1160 |
label = "Merged Image";
|
| 1161 |
}
|
| 1162 |
|
| 1163 |
if (image) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1164 |
mergeImages.push(image);
|
| 1165 |
inputData.push({ image, label: label || `Input ${mergeImages.length}` });
|
| 1166 |
}
|
|
@@ -1171,6 +1225,13 @@ export default function EditorPage() {
|
|
| 1171 |
throw new Error("Not enough valid inputs for merge. Need at least 2 images.");
|
| 1172 |
}
|
| 1173 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1174 |
const prompt = generateMergePrompt(inputData);
|
| 1175 |
|
| 1176 |
// Use the process route instead of merge route
|
|
@@ -1184,6 +1245,14 @@ export default function EditorPage() {
|
|
| 1184 |
}),
|
| 1185 |
});
|
| 1186 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1187 |
const data = await res.json();
|
| 1188 |
if (!res.ok) {
|
| 1189 |
throw new Error(data.error || "Merge failed");
|
|
@@ -1217,7 +1286,8 @@ export default function EditorPage() {
|
|
| 1217 |
label = `${inputNode.type} Output ${index + 1}`;
|
| 1218 |
} else if (inputNode.type === "MERGE" && (inputNode as MergeNode).output) {
|
| 1219 |
// Another merge node's output
|
| 1220 |
-
|
|
|
|
| 1221 |
label = `Merged Image ${index + 1}`;
|
| 1222 |
}
|
| 1223 |
|
|
@@ -1247,6 +1317,15 @@ export default function EditorPage() {
|
|
| 1247 |
prompt
|
| 1248 |
}),
|
| 1249 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1250 |
const js = await res.json();
|
| 1251 |
if (!res.ok) {
|
| 1252 |
// Show more helpful error messages
|
|
|
|
| 57 |
}
|
| 58 |
|
| 59 |
// Types
|
| 60 |
+
type NodeType = "CHARACTER" | "MERGE" | "BACKGROUND" | "CLOTHES" | "STYLE" | "EDIT" | "CAMERA" | "AGE" | "FACE" | "BLEND";
|
| 61 |
|
| 62 |
type NodeBase = {
|
| 63 |
id: string;
|
|
|
|
| 167 |
error?: string | null;
|
| 168 |
};
|
| 169 |
|
| 170 |
+
type BlendNode = NodeBase & {
|
| 171 |
+
type: "BLEND";
|
| 172 |
+
input?: string;
|
| 173 |
+
output?: string;
|
| 174 |
+
blendStrength?: number;
|
| 175 |
+
isRunning?: boolean;
|
| 176 |
+
error?: string | null;
|
| 177 |
+
};
|
| 178 |
+
|
| 179 |
+
type AnyNode = CharacterNode | MergeNode | BackgroundNode | ClothesNode | StyleNode | EditNode | CameraNode | AgeNode | FaceNode | BlendNode;
|
| 180 |
|
| 181 |
// Default placeholder portrait
|
| 182 |
const DEFAULT_PERSON =
|
|
|
|
| 515 |
image = (inputNode as any).output;
|
| 516 |
label = `${inputNode.type}`;
|
| 517 |
} else if (inputNode.type === "MERGE" && (inputNode as MergeNode).output) {
|
| 518 |
+
const mergeOutput = (inputNode as MergeNode).output;
|
| 519 |
+
image = mergeOutput !== undefined ? mergeOutput : null;
|
| 520 |
label = "Merged";
|
| 521 |
} else {
|
| 522 |
// Node without output yet
|
|
|
|
| 970 |
|
| 971 |
// Update the merge node with output
|
| 972 |
setNodes(prev => prev.map(n =>
|
| 973 |
+
n.id === merge.id ? { ...n, output: mergeOutput || undefined, isRunning: false, error: null } : n
|
| 974 |
));
|
| 975 |
|
| 976 |
// Track that we processed this merge as part of the chain
|
|
|
|
| 1038 |
}));
|
| 1039 |
|
| 1040 |
try {
|
| 1041 |
+
// Validate image data before sending
|
| 1042 |
+
if (inputImage && inputImage.length > 10 * 1024 * 1024) { // 10MB limit warning
|
| 1043 |
+
console.warn("Large input image detected, size:", (inputImage.length / (1024 * 1024)).toFixed(2) + "MB");
|
| 1044 |
+
}
|
| 1045 |
+
|
| 1046 |
+
// Check if params contains custom images and validate them
|
| 1047 |
+
if (params.clothesImage) {
|
| 1048 |
+
console.log("[Process] Clothes image size:", (params.clothesImage.length / 1024).toFixed(2) + "KB");
|
| 1049 |
+
// Validate it's a proper data URL
|
| 1050 |
+
if (!params.clothesImage.startsWith('data:') && !params.clothesImage.startsWith('http') && !params.clothesImage.startsWith('/')) {
|
| 1051 |
+
throw new Error("Invalid clothes image format. Please upload a valid image.");
|
| 1052 |
+
}
|
| 1053 |
+
}
|
| 1054 |
+
|
| 1055 |
+
if (params.customBackgroundImage) {
|
| 1056 |
+
console.log("[Process] Custom background size:", (params.customBackgroundImage.length / 1024).toFixed(2) + "KB");
|
| 1057 |
+
// Validate it's a proper data URL
|
| 1058 |
+
if (!params.customBackgroundImage.startsWith('data:') && !params.customBackgroundImage.startsWith('http') && !params.customBackgroundImage.startsWith('/')) {
|
| 1059 |
+
throw new Error("Invalid background image format. Please upload a valid image.");
|
| 1060 |
+
}
|
| 1061 |
+
}
|
| 1062 |
+
|
| 1063 |
+
// Log request details for debugging
|
| 1064 |
+
console.log("[Process] Sending request with:", {
|
| 1065 |
+
hasImage: !!inputImage,
|
| 1066 |
+
imageSize: inputImage ? (inputImage.length / 1024).toFixed(2) + "KB" : 0,
|
| 1067 |
+
paramsKeys: Object.keys(params),
|
| 1068 |
+
nodeType: node.type
|
| 1069 |
+
});
|
| 1070 |
+
|
| 1071 |
// Make a SINGLE API call with all accumulated parameters
|
| 1072 |
const res = await fetch("/api/process", {
|
| 1073 |
method: "POST",
|
|
|
|
| 1079 |
}),
|
| 1080 |
});
|
| 1081 |
|
| 1082 |
+
// Check if response is actually JSON before parsing
|
| 1083 |
+
const contentType = res.headers.get("content-type");
|
| 1084 |
+
if (!contentType || !contentType.includes("application/json")) {
|
| 1085 |
+
const textResponse = await res.text();
|
| 1086 |
+
console.error("Non-JSON response received:", textResponse);
|
| 1087 |
+
throw new Error("Server returned an error page instead of JSON. Check your API key configuration.");
|
| 1088 |
+
}
|
| 1089 |
+
|
| 1090 |
const data = await res.json();
|
| 1091 |
if (!res.ok) throw new Error(data.error || "Processing failed");
|
| 1092 |
|
|
|
|
| 1204 |
label = `${inputNode.type} Output`;
|
| 1205 |
} else if (inputNode.type === "MERGE" && (inputNode as MergeNode).output) {
|
| 1206 |
// Another merge node's output
|
| 1207 |
+
const mergeOutput = (inputNode as MergeNode).output;
|
| 1208 |
+
image = mergeOutput !== undefined ? mergeOutput : null;
|
| 1209 |
label = "Merged Image";
|
| 1210 |
}
|
| 1211 |
|
| 1212 |
if (image) {
|
| 1213 |
+
// Validate image format
|
| 1214 |
+
if (!image.startsWith('data:') && !image.startsWith('http') && !image.startsWith('/')) {
|
| 1215 |
+
console.error(`Invalid image format for ${label}:`, image.substring(0, 100));
|
| 1216 |
+
continue; // Skip invalid images
|
| 1217 |
+
}
|
| 1218 |
mergeImages.push(image);
|
| 1219 |
inputData.push({ image, label: label || `Input ${mergeImages.length}` });
|
| 1220 |
}
|
|
|
|
| 1225 |
throw new Error("Not enough valid inputs for merge. Need at least 2 images.");
|
| 1226 |
}
|
| 1227 |
|
| 1228 |
+
// Log merge details for debugging
|
| 1229 |
+
console.log("[Merge] Processing merge with:", {
|
| 1230 |
+
imageCount: mergeImages.length,
|
| 1231 |
+
imageSizes: mergeImages.map(img => (img.length / 1024).toFixed(2) + "KB"),
|
| 1232 |
+
labels: inputData.map(d => d.label)
|
| 1233 |
+
});
|
| 1234 |
+
|
| 1235 |
const prompt = generateMergePrompt(inputData);
|
| 1236 |
|
| 1237 |
// Use the process route instead of merge route
|
|
|
|
| 1245 |
}),
|
| 1246 |
});
|
| 1247 |
|
| 1248 |
+
// Check if response is actually JSON before parsing
|
| 1249 |
+
const contentType = res.headers.get("content-type");
|
| 1250 |
+
if (!contentType || !contentType.includes("application/json")) {
|
| 1251 |
+
const textResponse = await res.text();
|
| 1252 |
+
console.error("Non-JSON response received:", textResponse);
|
| 1253 |
+
throw new Error("Server returned an error page instead of JSON. Check your API key configuration.");
|
| 1254 |
+
}
|
| 1255 |
+
|
| 1256 |
const data = await res.json();
|
| 1257 |
if (!res.ok) {
|
| 1258 |
throw new Error(data.error || "Merge failed");
|
|
|
|
| 1286 |
label = `${inputNode.type} Output ${index + 1}`;
|
| 1287 |
} else if (inputNode.type === "MERGE" && (inputNode as MergeNode).output) {
|
| 1288 |
// Another merge node's output
|
| 1289 |
+
const mergeOutput = (inputNode as MergeNode).output;
|
| 1290 |
+
image = mergeOutput !== undefined ? mergeOutput : null;
|
| 1291 |
label = `Merged Image ${index + 1}`;
|
| 1292 |
}
|
| 1293 |
|
|
|
|
| 1317 |
prompt
|
| 1318 |
}),
|
| 1319 |
});
|
| 1320 |
+
|
| 1321 |
+
// Check if response is actually JSON before parsing
|
| 1322 |
+
const contentType = res.headers.get("content-type");
|
| 1323 |
+
if (!contentType || !contentType.includes("application/json")) {
|
| 1324 |
+
const textResponse = await res.text();
|
| 1325 |
+
console.error("Non-JSON response received:", textResponse);
|
| 1326 |
+
throw new Error("Server returned an error page instead of JSON. Check your API key configuration.");
|
| 1327 |
+
}
|
| 1328 |
+
|
| 1329 |
const js = await res.json();
|
| 1330 |
if (!res.ok) {
|
| 1331 |
// Show more helpful error messages
|
eslint.config.mjs
CHANGED
|
@@ -20,6 +20,17 @@ const eslintConfig = [
|
|
| 20 |
"next-env.d.ts",
|
| 21 |
],
|
| 22 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
];
|
| 24 |
|
| 25 |
export default eslintConfig;
|
|
|
|
| 20 |
"next-env.d.ts",
|
| 21 |
],
|
| 22 |
},
|
| 23 |
+
{
|
| 24 |
+
rules: {
|
| 25 |
+
"@typescript-eslint/no-explicit-any": "warn",
|
| 26 |
+
"@typescript-eslint/no-unused-vars": "warn",
|
| 27 |
+
"@typescript-eslint/no-empty-object-type": "warn",
|
| 28 |
+
"@next/next/no-img-element": "warn",
|
| 29 |
+
"@next/next/no-html-link-for-pages": "warn",
|
| 30 |
+
"react/no-unescaped-entities": "warn",
|
| 31 |
+
"react-hooks/exhaustive-deps": "warn",
|
| 32 |
+
},
|
| 33 |
+
},
|
| 34 |
];
|
| 35 |
|
| 36 |
export default eslintConfig;
|
next.config.ts
CHANGED
|
@@ -2,6 +2,15 @@ import type { NextConfig } from "next";
|
|
| 2 |
|
| 3 |
const nextConfig: NextConfig = {
|
| 4 |
/* config options here */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
};
|
| 6 |
|
| 7 |
export default nextConfig;
|
|
|
|
| 2 |
|
| 3 |
const nextConfig: NextConfig = {
|
| 4 |
/* config options here */
|
| 5 |
+
// Increase body size limit for API routes to handle large images
|
| 6 |
+
serverRuntimeConfig: {
|
| 7 |
+
bodySizeLimit: '50mb',
|
| 8 |
+
},
|
| 9 |
+
api: {
|
| 10 |
+
bodyParser: {
|
| 11 |
+
sizeLimit: '50mb',
|
| 12 |
+
},
|
| 13 |
+
},
|
| 14 |
};
|
| 15 |
|
| 16 |
export default nextConfig;
|
package-lock.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
| 11 |
"@google/genai": "^1.17.0",
|
| 12 |
"class-variance-authority": "^0.7.0",
|
| 13 |
"clsx": "^2.1.1",
|
|
|
|
| 14 |
"next": "15.5.2",
|
| 15 |
"react": "19.1.0",
|
| 16 |
"react-dom": "19.1.0",
|
|
@@ -4703,6 +4704,15 @@
|
|
| 4703 |
"loose-envify": "cli.js"
|
| 4704 |
}
|
| 4705 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4706 |
"node_modules/magic-string": {
|
| 4707 |
"version": "0.30.18",
|
| 4708 |
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz",
|
|
|
|
| 11 |
"@google/genai": "^1.17.0",
|
| 12 |
"class-variance-authority": "^0.7.0",
|
| 13 |
"clsx": "^2.1.1",
|
| 14 |
+
"lucide-react": "^0.542.0",
|
| 15 |
"next": "15.5.2",
|
| 16 |
"react": "19.1.0",
|
| 17 |
"react-dom": "19.1.0",
|
|
|
|
| 4704 |
"loose-envify": "cli.js"
|
| 4705 |
}
|
| 4706 |
},
|
| 4707 |
+
"node_modules/lucide-react": {
|
| 4708 |
+
"version": "0.542.0",
|
| 4709 |
+
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.542.0.tgz",
|
| 4710 |
+
"integrity": "sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==",
|
| 4711 |
+
"license": "ISC",
|
| 4712 |
+
"peerDependencies": {
|
| 4713 |
+
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 4714 |
+
}
|
| 4715 |
+
},
|
| 4716 |
"node_modules/magic-string": {
|
| 4717 |
"version": "0.30.18",
|
| 4718 |
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz",
|
package.json
CHANGED
|
@@ -12,6 +12,7 @@
|
|
| 12 |
"@google/genai": "^1.17.0",
|
| 13 |
"class-variance-authority": "^0.7.0",
|
| 14 |
"clsx": "^2.1.1",
|
|
|
|
| 15 |
"next": "15.5.2",
|
| 16 |
"react": "19.1.0",
|
| 17 |
"react-dom": "19.1.0",
|
|
|
|
| 12 |
"@google/genai": "^1.17.0",
|
| 13 |
"class-variance-authority": "^0.7.0",
|
| 14 |
"clsx": "^2.1.1",
|
| 15 |
+
"lucide-react": "^0.542.0",
|
| 16 |
"next": "15.5.2",
|
| 17 |
"react": "19.1.0",
|
| 18 |
"react-dom": "19.1.0",
|