const express = require('express'); const app = express(); const cors = require('cors'); require('dotenv').config(); const { createClient } = require('@supabase/supabase-js'); const axios = require('axios'); app.use(express.json({ limit: '50mb' })); // โ† Add limit here app.use(express.urlencoded({ limit: '50mb', extended: true })); // โ† Add this too const port = process.env.PORT || 5235; // Middleware app.use(cors()); app.use(express.json()); // ============================================ // SUPABASE SETUP // ============================================ const supabase = createClient( process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY ); console.log('โœ… Supabase client initialized'); const FASHN_API_KEY = process.env.FASHN_API_KEY; const FASHN_API_URL = 'https://api.fashn.ai/v1'; // ============================================ // SIMPLE TEST ENDPOINT (1 GARMENT) // ============================================ app.post('/api/test-fashn', async (req, res) => { try { const { modelImage, garmentImage } = req.body; console.log('\n๐Ÿงช TESTING FASHN.AI'); console.log('Model:', modelImage?.substring(0, 50)); console.log('Garment:', garmentImage?.substring(0, 50)); if (!FASHN_API_KEY) { return res.status(500).json({ success: false, error: 'FASHN_API_KEY not set in .env' }); } // Step 1: Submit job console.log('\n๐Ÿ“ค Submitting to FASHN.ai...'); const submitResponse = await axios.post( `${FASHN_API_URL}/run`, { model_name: 'tryon-v1.6', inputs: { model_image: modelImage, garment_image: garmentImage, category: 'auto' } }, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}`, 'Content-Type': 'application/json' }, timeout: 10000 } ); console.log('โœ… Response:', JSON.stringify(submitResponse.data, null, 2)); if (!submitResponse.data.id) { return res.json({ success: false, error: 'No job ID received', response: submitResponse.data }); } const jobId = submitResponse.data.id; console.log('โœ… Job created:', jobId); // Step 2: Poll for result console.log('\nโณ Polling for result...'); const maxAttempts = 30; // 1 minute max const pollInterval = 2000; // 2 seconds for (let i = 0; i < maxAttempts; i++) { await new Promise(resolve => setTimeout(resolve, pollInterval)); console.log(`๐Ÿ”„ Attempt ${i + 1}/${maxAttempts}`); try { const statusResponse = await axios.get( `${FASHN_API_URL}/status/${jobId}`, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}` }, timeout: 10000 } ); console.log('๐Ÿ“Š Status:', JSON.stringify(statusResponse.data, null, 2)); if (statusResponse.data.status === 'completed') { let imageUrl = statusResponse.data.output?.[0] || statusResponse.data.result || statusResponse.data.image; console.log('โœ… COMPLETED!'); console.log('๐Ÿ–ผ๏ธ Image:', imageUrl); return res.json({ success: true, message: 'FASHN.ai works!', jobId: jobId, imageUrl: imageUrl, attempts: i + 1 }); } if (statusResponse.data.status === 'failed') { console.log('โŒ FAILED:', statusResponse.data.error); return res.json({ success: false, error: 'FASHN.ai job failed', details: statusResponse.data.error }); } console.log('โณ Still processing...'); } catch (pollError) { if (pollError.response?.status === 404) { console.log('โณ Not ready yet (404)'); continue; } console.log('โš ๏ธ Poll error:', pollError.response?.status, pollError.message); continue; } } // Timeout return res.json({ success: false, error: 'Timeout - FASHN.ai took too long', jobId: jobId }); } catch (error) { console.error('โŒ ERROR:', error.response?.data || error.message); res.status(500).json({ success: false, error: error.message, details: error.response?.data }); } }); // ============================================ // TEST WITH 2 GARMENTS (SHIRT + PANT) // ============================================ app.post('/api/test-two-garments', async (req, res) => { try { const { modelImage, shirtImage, pantImage } = req.body; console.log('\n๐Ÿงช TESTING 2 GARMENTS (SHIRT + PANT)'); console.log('Model:', modelImage?.substring(0, 50)); console.log('Shirt:', shirtImage?.substring(0, 50)); console.log('Pant:', pantImage?.substring(0, 50)); if (!FASHN_API_KEY) { return res.status(500).json({ success: false, error: 'FASHN_API_KEY not set in .env' }); } // STEP 1: Apply SHIRT first console.log('\n๐Ÿ‘• STEP 1: Applying SHIRT...'); const shirtResponse = await axios.post( `${FASHN_API_URL}/run`, { model_name: 'tryon-v1.6', inputs: { model_image: modelImage, garment_image: shirtImage, category: 'tops' } }, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}`, 'Content-Type': 'application/json' }, timeout: 10000 } ); const shirtJobId = shirtResponse.data.id; console.log('โœ… Shirt job created:', shirtJobId); // Poll for shirt result console.log('โณ Waiting for shirt...'); let shirtResult = null; for (let i = 0; i < 30; i++) { await new Promise(resolve => setTimeout(resolve, 2000)); try { const statusResponse = await axios.get( `${FASHN_API_URL}/status/${shirtJobId}`, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}` }, timeout: 10000 } ); console.log(`๐Ÿ”„ Shirt attempt ${i + 1}/30 - Status: ${statusResponse.data.status}`); if (statusResponse.data.status === 'completed') { shirtResult = statusResponse.data.output?.[0]; console.log('โœ… Shirt applied!'); console.log('๐Ÿ–ผ๏ธ Intermediate image:', shirtResult); break; } if (statusResponse.data.status === 'failed') { return res.json({ success: false, step: 'shirt', error: 'Shirt application failed', details: statusResponse.data.error }); } } catch (pollError) { if (pollError.response?.status !== 404) { console.log('โš ๏ธ Shirt poll error:', pollError.message); } } } if (!shirtResult) { return res.json({ success: false, step: 'shirt', error: 'Shirt application timeout' }); } // STEP 2: Apply PANT on top of shirt result console.log('\n๐Ÿ‘– STEP 2: Applying PANT...'); const pantResponse = await axios.post( `${FASHN_API_URL}/run`, { model_name: 'tryon-v1.6', inputs: { model_image: shirtResult, garment_image: pantImage, category: 'bottoms' } }, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}`, 'Content-Type': 'application/json' }, timeout: 10000 } ); const pantJobId = pantResponse.data.id; console.log('โœ… Pant job created:', pantJobId); // Poll for pant result console.log('โณ Waiting for pant...'); let pantResult = null; for (let i = 0; i < 30; i++) { await new Promise(resolve => setTimeout(resolve, 2000)); try { const statusResponse = await axios.get( `${FASHN_API_URL}/status/${pantJobId}`, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}` }, timeout: 10000 } ); console.log(`๐Ÿ”„ Pant attempt ${i + 1}/30 - Status: ${statusResponse.data.status}`); if (statusResponse.data.status === 'completed') { pantResult = statusResponse.data.output?.[0]; console.log('โœ… Pant applied!'); console.log('๐Ÿ–ผ๏ธ Final image:', pantResult); break; } if (statusResponse.data.status === 'failed') { return res.json({ success: false, step: 'pant', error: 'Pant application failed', details: statusResponse.data.error, shirtResult: shirtResult }); } } catch (pollError) { if (pollError.response?.status !== 404) { console.log('โš ๏ธ Pant poll error:', pollError.message); } } } if (!pantResult) { return res.json({ success: false, step: 'pant', error: 'Pant application timeout', shirtResult: shirtResult }); } // Return final result console.log('\nโœ… PROCESS COMPLETED!'); return res.json({ success: true, message: 'Both garments applied successfully!', steps: { shirt: { jobId: shirtJobId, result: shirtResult }, pant: { jobId: pantJobId, result: pantResult } }, finalImage: pantResult }); } catch (error) { console.error('โŒ ERROR:', error.response?.data || error.message); res.status(500).json({ success: false, error: error.message, details: error.response?.data }); } }); // ============================================ // VIRTUAL TRY-ON (FRONTEND INTEGRATION) // ============================================ app.post('/api/virtual-tryon', async (req, res) => { try { const { userId, userImage, wardrobeItems, selectedPose, selectedBackground, selectedEffect } = req.body; console.log('\n๐ŸŽจ VIRTUAL TRY-ON REQUEST'); console.log('User ID:', userId); console.log('Wardrobe Items:', wardrobeItems?.length, 'items'); console.log('Pose:', selectedPose); console.log('Background:', selectedBackground); console.log('Effect:', selectedEffect); if (!FASHN_API_KEY) { return res.status(500).json({ success: false, error: 'FASHN_API_KEY not set in .env' }); } if (!userImage || !wardrobeItems || wardrobeItems.length === 0) { return res.status(400).json({ success: false, error: 'userImage and wardrobeItems are required' }); } // Separate tops and bottoms const tops = wardrobeItems.filter(item => item.typecategory === 'tops'); const bottoms = wardrobeItems.filter(item => item.typecategory === 'bottoms'); console.log('๐Ÿ‘• Tops:', tops.length); console.log('๐Ÿ‘– Bottoms:', bottoms.length); // Build the prompt for face-to-model let modelPrompt = ''; if (selectedEffect) { // If selectedEffect is an object, get the description const effectText = typeof selectedEffect === 'string' ? selectedEffect : selectedEffect?.description || ''; modelPrompt += effectText; } if (selectedPose) { // If selectedPose is an object, get the description const poseText = typeof selectedPose === 'string' ? selectedPose : selectedPose?.description || ''; modelPrompt += modelPrompt ? `, ${poseText}` : poseText; } console.log('๐ŸŽญ Model Prompt:', modelPrompt || 'auto-inferred'); // STEP 1: Create avatar from face console.log('\n๐Ÿ‘ค STEP 1: Creating avatar from face...'); const faceToModelResponse = await axios.post( `${FASHN_API_URL}/run`, { model_name: 'face-to-model', inputs: { face_image: userImage, ...(modelPrompt && { prompt: modelPrompt }) } }, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}`, 'Content-Type': 'application/json' }, timeout: 10000 } ); const faceJobId = faceToModelResponse.data.id; console.log('โœ… Face-to-model job created:', faceJobId); // Poll for avatar creation console.log('โณ Waiting for avatar generation...'); let currentImage = null; for (let i = 0; i < 30; i++) { await new Promise(resolve => setTimeout(resolve, 2000)); try { const statusResponse = await axios.get( `${FASHN_API_URL}/status/${faceJobId}`, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}` }, timeout: 10000 } ); console.log(`๐Ÿ”„ Avatar attempt ${i + 1}/30 - Status: ${statusResponse.data.status}`); if (statusResponse.data.status === 'completed') { currentImage = statusResponse.data.output?.[0]; console.log('โœ… Avatar created from face!'); break; } if (statusResponse.data.status === 'failed') { return res.json({ success: false, step: 'face-to-model', error: 'Avatar creation failed', details: statusResponse.data.error }); } } catch (pollError) { if (pollError.response?.status !== 404) { console.log('โš ๏ธ Avatar poll error:', pollError.message); } } } if (!currentImage) { return res.json({ success: false, step: 'face-to-model', error: 'Avatar creation timeout' }); } const results = { avatar: currentImage }; // STEP 2: Apply tops if available if (tops.length > 0) { console.log('\n๐Ÿ‘• STEP 2: Applying tops...'); for (const top of tops) { console.log(` Applying: ${top.name}`); const topResponse = await axios.post( `${FASHN_API_URL}/run`, { model_name: 'tryon-v1.6', inputs: { model_image: currentImage, garment_image: top.image_url, category: 'tops' } }, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}`, 'Content-Type': 'application/json' }, timeout: 10000 } ); const topJobId = topResponse.data.id; // Poll for result for (let i = 0; i < 30; i++) { await new Promise(resolve => setTimeout(resolve, 2000)); try { const statusResponse = await axios.get( `${FASHN_API_URL}/status/${topJobId}`, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}` } } ); if (statusResponse.data.status === 'completed') { currentImage = statusResponse.data.output?.[0]; console.log(` โœ… ${top.name} applied!`); break; } if (statusResponse.data.status === 'failed') { console.log(` โŒ ${top.name} failed, skipping`); break; } } catch (pollError) { // Continue polling } } } results.withTops = currentImage; } // STEP 3: Apply bottoms if available if (bottoms.length > 0) { console.log('\n๐Ÿ‘– STEP 3: Applying bottoms...'); for (const bottom of bottoms) { console.log(` Applying: ${bottom.name}`); const bottomResponse = await axios.post( `${FASHN_API_URL}/run`, { model_name: 'tryon-v1.6', inputs: { model_image: currentImage, garment_image: bottom.image_url, category: 'bottoms' } }, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}`, 'Content-Type': 'application/json' }, timeout: 10000 } ); const bottomJobId = bottomResponse.data.id; // Poll for result for (let i = 0; i < 30; i++) { await new Promise(resolve => setTimeout(resolve, 2000)); try { const statusResponse = await axios.get( `${FASHN_API_URL}/status/${bottomJobId}`, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}` } } ); if (statusResponse.data.status === 'completed') { currentImage = statusResponse.data.output?.[0]; console.log(` โœ… ${bottom.name} applied!`); break; } if (statusResponse.data.status === 'failed') { console.log(` โŒ ${bottom.name} failed, skipping`); break; } } catch (pollError) { // Continue polling } } } results.withBottoms = currentImage; } // STEP 4: Change background if provided if (selectedBackground) { console.log('\n๐ŸŒ† STEP 4: Changing background...'); // If selectedBackground is an object, get the description const backgroundText = typeof selectedBackground === 'string' ? selectedBackground : selectedBackground?.description || ''; if (!backgroundText) { console.log(' โš ๏ธ No background description provided, skipping'); } else { const backgroundResponse = await axios.post( `${FASHN_API_URL}/run`, { model_name: 'background-change', inputs: { image: currentImage, prompt: backgroundText } }, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}`, 'Content-Type': 'application/json' }, timeout: 10000 } ); const bgJobId = backgroundResponse.data.id; // Poll for result for (let i = 0; i < 30; i++) { await new Promise(resolve => setTimeout(resolve, 2000)); try { const statusResponse = await axios.get( `${FASHN_API_URL}/status/${bgJobId}`, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}` } } ); if (statusResponse.data.status === 'completed') { currentImage = statusResponse.data.output?.[0]; console.log(' โœ… Background changed!'); break; } if (statusResponse.data.status === 'failed') { console.log(' โš ๏ธ Background change failed, using previous result'); break; } } catch (pollError) { // Continue polling } } results.withBackground = currentImage; } } // Return final result console.log('\nโœ… VIRTUAL TRY-ON COMPLETE!'); console.log('๐Ÿ–ผ๏ธ Final Image:', currentImage); return res.json({ success: true, message: 'Virtual try-on completed successfully!', userId: userId, generatedImageUrl: currentImage, results: results, appliedItems: { tops: tops.map(t => t.name), bottoms: bottoms.map(b => b.name) } }); } catch (error) { console.error('โŒ ERROR:', error.response?.data || error.message); res.status(500).json({ success: false, error: error.message, details: error.response?.data }); } }); // ============================================ // BACKGROUND CHANGE // ============================================ app.post('/api/change-background', async (req, res) => { try { const { image, prompt } = req.body; console.log('\n๐ŸŒ† CHANGING BACKGROUND'); console.log('Image:', image?.substring(0, 50)); console.log('Background Prompt:', prompt); if (!FASHN_API_KEY) { return res.status(500).json({ success: false, error: 'FASHN_API_KEY not set in .env' }); } if (!image || !prompt) { return res.status(400).json({ success: false, error: 'image and prompt are required', example: { image: 'https://cdn.fashn.ai/xyz/output_0.png', prompt: 'city street with buildings, urban background, busy downtown' } }); } // Submit background change job console.log('\n๐Ÿ“ค Submitting background change request...'); const changeResponse = await axios.post( `${FASHN_API_URL}/run`, { model_name: 'background-change', inputs: { image: image, prompt: prompt } }, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}`, 'Content-Type': 'application/json' }, timeout: 10000 } ); console.log('โœ… Response:', JSON.stringify(changeResponse.data, null, 2)); if (!changeResponse.data.id) { return res.json({ success: false, error: 'No job ID received', response: changeResponse.data }); } const jobId = changeResponse.data.id; console.log('โœ… Background change job created:', jobId); // Poll for result console.log('\nโณ Polling for background change...'); const maxAttempts = 30; const pollInterval = 2000; for (let i = 0; i < maxAttempts; i++) { await new Promise(resolve => setTimeout(resolve, pollInterval)); console.log(`๐Ÿ”„ Attempt ${i + 1}/${maxAttempts}`); try { const statusResponse = await axios.get( `${FASHN_API_URL}/status/${jobId}`, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}` }, timeout: 10000 } ); console.log('๐Ÿ“Š Status:', JSON.stringify(statusResponse.data, null, 2)); if (statusResponse.data.status === 'completed') { let imageUrl = statusResponse.data.output?.[0] || statusResponse.data.result || statusResponse.data.image; console.log('โœ… BACKGROUND CHANGED!'); console.log('๐Ÿ–ผ๏ธ New Image:', imageUrl); return res.json({ success: true, message: 'Background changed successfully!', jobId: jobId, imageUrl: imageUrl, prompt: prompt, attempts: i + 1 }); } if (statusResponse.data.status === 'failed') { console.log('โŒ FAILED:', statusResponse.data.error); return res.json({ success: false, error: 'Background change failed', details: statusResponse.data.error }); } console.log('โณ Still processing...'); } catch (pollError) { if (pollError.response?.status === 404) { console.log('โณ Not ready yet (404)'); continue; } console.log('โš ๏ธ Poll error:', pollError.response?.status, pollError.message); continue; } } // Timeout return res.json({ success: false, error: 'Timeout - Background change took too long', jobId: jobId }); } catch (error) { console.error('โŒ ERROR:', error.response?.data || error.message); res.status(500).json({ success: false, error: error.message, details: error.response?.data }); } }); // ============================================ // FACE TO MODEL (CREATE AVATAR FROM FACE) // ============================================ app.post('/api/face-to-model', async (req, res) => { try { const { faceImage, prompt, outputFormat } = req.body; console.log('\n๐Ÿ‘ค CREATING MODEL FROM FACE IMAGE'); console.log('Face Image:', faceImage?.substring(0, 50)); console.log('Prompt:', prompt || 'None (auto-inferred)'); console.log('Output Format:', outputFormat || 'png'); if (!FASHN_API_KEY) { return res.status(500).json({ success: false, error: 'FASHN_API_KEY not set in .env' }); } if (!faceImage) { return res.status(400).json({ success: false, error: 'faceImage is required', example: { faceImage: 'https://i.ibb.co/C3DCLgSD/fashn-try-on-1761233346758.png', prompt: 'athletic build, confident', outputFormat: 'png' } }); } // Submit face-to-model job console.log('\n๐Ÿ“ค Submitting face-to-model request...'); const requestBody = { model_name: 'face-to-model', inputs: { face_image: faceImage } }; // Add optional parameters if (prompt) { requestBody.inputs.prompt = prompt; } if (outputFormat) { requestBody.inputs.output_format = outputFormat; } const createResponse = await axios.post( `${FASHN_API_URL}/run`, requestBody, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}`, 'Content-Type': 'application/json' }, timeout: 10000 } ); console.log('โœ… Response:', JSON.stringify(createResponse.data, null, 2)); if (!createResponse.data.id) { return res.json({ success: false, error: 'No job ID received', response: createResponse.data }); } const jobId = createResponse.data.id; console.log('โœ… Face-to-model job created:', jobId); // Poll for result console.log('\nโณ Polling for avatar generation...'); const maxAttempts = 30; // 1 minute max const pollInterval = 2000; // 2 seconds for (let i = 0; i < maxAttempts; i++) { await new Promise(resolve => setTimeout(resolve, pollInterval)); console.log(`๐Ÿ”„ Attempt ${i + 1}/${maxAttempts}`); try { const statusResponse = await axios.get( `${FASHN_API_URL}/status/${jobId}`, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}` }, timeout: 10000 } ); console.log('๐Ÿ“Š Status:', JSON.stringify(statusResponse.data, null, 2)); if (statusResponse.data.status === 'completed') { let avatarUrl = statusResponse.data.output?.[0] || statusResponse.data.result || statusResponse.data.image; console.log('โœ… AVATAR CREATED FROM FACE!'); console.log('๐Ÿ–ผ๏ธ Avatar Image:', avatarUrl); return res.json({ success: true, message: 'Avatar created from face successfully!', jobId: jobId, avatarImage: avatarUrl, faceImage: faceImage, prompt: prompt || 'auto-inferred', attempts: i + 1, note: 'You can now use this avatarImage for try-on' }); } if (statusResponse.data.status === 'failed') { console.log('โŒ FAILED:', statusResponse.data.error); return res.json({ success: false, error: 'Face-to-model conversion failed', details: statusResponse.data.error }); } console.log('โณ Still processing...'); } catch (pollError) { if (pollError.response?.status === 404) { console.log('โณ Not ready yet (404)'); continue; } console.log('โš ๏ธ Poll error:', pollError.response?.status, pollError.message); continue; } } // Timeout return res.json({ success: false, error: 'Timeout - Face-to-model took too long', jobId: jobId }); } catch (error) { console.error('โŒ ERROR:', error.response?.data || error.message); res.status(500).json({ success: false, error: error.message, details: error.response?.data }); } }); // ============================================ // FACE TO MODEL + APPLY GARMENTS (COMPLETE WORKFLOW) // ============================================ app.post('/api/face-to-model-and-tryon', async (req, res) => { try { const { faceImage, prompt, shirtImage, pantImage, backgroundPrompt } = req.body; console.log('\n๐ŸŽฏ FACE-TO-MODEL + GARMENT TRY-ON WORKFLOW'); console.log('Face Image:', faceImage?.substring(0, 50)); console.log('Prompt:', prompt || 'auto-inferred'); console.log('Shirt:', shirtImage?.substring(0, 50)); console.log('Pant:', pantImage?.substring(0, 50)); console.log('Background:', backgroundPrompt || 'None'); if (!FASHN_API_KEY) { return res.status(500).json({ success: false, error: 'FASHN_API_KEY not set in .env' }); } if (!faceImage || !shirtImage || !pantImage) { return res.status(400).json({ success: false, error: 'faceImage, shirtImage, and pantImage are required' }); } // STEP 1: Create avatar from face console.log('\n๐Ÿ‘ค STEP 1: Creating avatar from face...'); const faceToModelResponse = await axios.post( `${FASHN_API_URL}/run`, { model_name: 'face-to-model', inputs: { face_image: faceImage, ...(prompt && { prompt }) } }, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}`, 'Content-Type': 'application/json' }, timeout: 10000 } ); const faceJobId = faceToModelResponse.data.id; console.log('โœ… Face-to-model job created:', faceJobId); // Poll for avatar creation console.log('โณ Waiting for avatar generation...'); let avatarImage = null; for (let i = 0; i < 30; i++) { await new Promise(resolve => setTimeout(resolve, 2000)); try { const statusResponse = await axios.get( `${FASHN_API_URL}/status/${faceJobId}`, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}` }, timeout: 10000 } ); console.log(`๐Ÿ”„ Avatar attempt ${i + 1}/30 - Status: ${statusResponse.data.status}`); if (statusResponse.data.status === 'completed') { avatarImage = statusResponse.data.output?.[0]; console.log('โœ… Avatar created from face!'); console.log('๐Ÿ–ผ๏ธ Avatar:', avatarImage); break; } if (statusResponse.data.status === 'failed') { return res.json({ success: false, step: 'face-to-model', error: 'Avatar creation failed', details: statusResponse.data.error }); } } catch (pollError) { if (pollError.response?.status !== 404) { console.log('โš ๏ธ Avatar poll error:', pollError.message); } } } if (!avatarImage) { return res.json({ success: false, step: 'face-to-model', error: 'Avatar creation timeout' }); } // STEP 2: Apply SHIRT to avatar console.log('\n๐Ÿ‘• STEP 2: Applying SHIRT to avatar...'); const shirtResponse = await axios.post( `${FASHN_API_URL}/run`, { model_name: 'tryon-v1.6', inputs: { model_image: avatarImage, garment_image: shirtImage, category: 'tops' } }, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}`, 'Content-Type': 'application/json' }, timeout: 10000 } ); const shirtJobId = shirtResponse.data.id; console.log('โœ… Shirt job created:', shirtJobId); // Poll for shirt result console.log('โณ Waiting for shirt...'); let shirtResult = null; for (let i = 0; i < 30; i++) { await new Promise(resolve => setTimeout(resolve, 2000)); try { const statusResponse = await axios.get( `${FASHN_API_URL}/status/${shirtJobId}`, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}` }, timeout: 10000 } ); console.log(`๐Ÿ”„ Shirt attempt ${i + 1}/30 - Status: ${statusResponse.data.status}`); if (statusResponse.data.status === 'completed') { shirtResult = statusResponse.data.output?.[0]; console.log('โœ… Shirt applied!'); console.log('๐Ÿ–ผ๏ธ With shirt:', shirtResult); break; } if (statusResponse.data.status === 'failed') { return res.json({ success: false, step: 'shirt', error: 'Shirt application failed', details: statusResponse.data.error, avatarImage: avatarImage }); } } catch (pollError) { if (pollError.response?.status !== 404) { console.log('โš ๏ธ Shirt poll error:', pollError.message); } } } if (!shirtResult) { return res.json({ success: false, step: 'shirt', error: 'Shirt application timeout', avatarImage: avatarImage }); } // STEP 3: Apply PANT to shirt result console.log('\n๐Ÿ‘– STEP 3: Applying PANT...'); const pantResponse = await axios.post( `${FASHN_API_URL}/run`, { model_name: 'tryon-v1.6', inputs: { model_image: shirtResult, garment_image: pantImage, category: 'bottoms' } }, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}`, 'Content-Type': 'application/json' }, timeout: 10000 } ); const pantJobId = pantResponse.data.id; console.log('โœ… Pant job created:', pantJobId); // Poll for pant result console.log('โณ Waiting for pant...'); let pantResult = null; for (let i = 0; i < 30; i++) { await new Promise(resolve => setTimeout(resolve, 2000)); try { const statusResponse = await axios.get( `${FASHN_API_URL}/status/${pantJobId}`, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}` }, timeout: 10000 } ); console.log(`๐Ÿ”„ Pant attempt ${i + 1}/30 - Status: ${statusResponse.data.status}`); if (statusResponse.data.status === 'completed') { pantResult = statusResponse.data.output?.[0]; console.log('โœ… Pant applied!'); console.log('๐Ÿ–ผ๏ธ Final result:', pantResult); break; } if (statusResponse.data.status === 'failed') { return res.json({ success: false, step: 'pant', error: 'Pant application failed', details: statusResponse.data.error, avatarImage: avatarImage, shirtResult: shirtResult }); } } catch (pollError) { if (pollError.response?.status !== 404) { console.log('โš ๏ธ Pant poll error:', pollError.message); } } } if (!pantResult) { return res.json({ success: false, step: 'pant', error: 'Pant application timeout', avatarImage: avatarImage, shirtResult: shirtResult }); } // STEP 4: Change background if provided let finalResult = pantResult; let backgroundJobId = null; if (backgroundPrompt) { console.log('\n๐ŸŒ† STEP 4: Changing BACKGROUND...'); const backgroundResponse = await axios.post( `${FASHN_API_URL}/run`, { model_name: 'background-change', inputs: { image: pantResult, prompt: backgroundPrompt } }, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}`, 'Content-Type': 'application/json' }, timeout: 10000 } ); backgroundJobId = backgroundResponse.data.id; console.log('โœ… Background job created:', backgroundJobId); // Poll for background result console.log('โณ Waiting for background change...'); for (let i = 0; i < 30; i++) { await new Promise(resolve => setTimeout(resolve, 2000)); try { const statusResponse = await axios.get( `${FASHN_API_URL}/status/${backgroundJobId}`, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}` }, timeout: 10000 } ); console.log(`๐Ÿ”„ Background attempt ${i + 1}/30 - Status: ${statusResponse.data.status}`); if (statusResponse.data.status === 'completed') { finalResult = statusResponse.data.output?.[0]; console.log('โœ… Background changed!'); console.log('๐Ÿ–ผ๏ธ Final image:', finalResult); break; } if (statusResponse.data.status === 'failed') { console.log('โš ๏ธ Background change failed, using garments-only result'); console.log('Error:', statusResponse.data.error); // Don't fail the whole request, just skip background change break; } } catch (pollError) { if (pollError.response?.status !== 404) { console.log('โš ๏ธ Background poll error:', pollError.message); } } } } // Return final result console.log('\nโœ… COMPLETE FACE-TO-MODEL + TRY-ON FINISHED!'); const response = { success: true, message: backgroundPrompt ? 'Avatar created, garments applied, and background changed!' : 'Avatar created from face and garments applied!', steps: { faceToModel: { jobId: faceJobId, result: avatarImage, prompt: prompt || 'auto-inferred' }, shirt: { jobId: shirtJobId, result: shirtResult }, pant: { jobId: pantJobId, result: pantResult } }, finalImage: finalResult }; if (backgroundPrompt && backgroundJobId) { response.steps.background = { jobId: backgroundJobId, result: finalResult, prompt: backgroundPrompt }; } return res.json(response); } catch (error) { console.error('โŒ ERROR:', error.response?.data || error.message); res.status(500).json({ success: false, error: error.message, details: error.response?.data }); } }); // ============================================ // MODEL CREATION ONLY (TEST POSES) // ============================================ app.post('/api/create-model', async (req, res) => { try { const { prompt } = req.body; console.log('\n๐ŸŽญ CREATING MODEL WITH FASHN.AI'); console.log('Prompt:', prompt); if (!FASHN_API_KEY) { return res.status(500).json({ success: false, error: 'FASHN_API_KEY not set in .env' }); } if (!prompt) { return res.status(400).json({ success: false, error: 'Prompt is required', examples: [ 'male fashion model, athletic build, power pose with hands on hips, confident stance, full body, white background', 'female fashion model, elegant, sitting on chair with legs crossed, professional, studio lighting', 'male model, tall and slim, walking pose, one leg forward, dynamic movement, urban background' ] }); } // Submit model creation job console.log('\n๐Ÿ“ค Submitting model creation request...'); const createResponse = await axios.post( `${FASHN_API_URL}/run`, { model_name: 'model-create', inputs: { prompt: prompt } }, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}`, 'Content-Type': 'application/json' }, timeout: 10000 } ); console.log('โœ… Response:', JSON.stringify(createResponse.data, null, 2)); if (!createResponse.data.id) { return res.json({ success: false, error: 'No job ID received', response: createResponse.data }); } const jobId = createResponse.data.id; console.log('โœ… Model creation job created:', jobId); // Poll for result console.log('\nโณ Polling for model generation...'); const maxAttempts = 30; // 1 minute max const pollInterval = 2000; // 2 seconds for (let i = 0; i < maxAttempts; i++) { await new Promise(resolve => setTimeout(resolve, pollInterval)); console.log(`๐Ÿ”„ Attempt ${i + 1}/${maxAttempts}`); try { const statusResponse = await axios.get( `${FASHN_API_URL}/status/${jobId}`, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}` }, timeout: 10000 } ); console.log('๐Ÿ“Š Status:', JSON.stringify(statusResponse.data, null, 2)); if (statusResponse.data.status === 'completed') { let imageUrl = statusResponse.data.output?.[0] || statusResponse.data.result || statusResponse.data.image; console.log('โœ… MODEL CREATED!'); console.log('๐Ÿ–ผ๏ธ Model Image:', imageUrl); return res.json({ success: true, message: 'Model created successfully!', jobId: jobId, modelImage: imageUrl, prompt: prompt, attempts: i + 1, note: 'You can now use this modelImage URL for try-on' }); } if (statusResponse.data.status === 'failed') { console.log('โŒ FAILED:', statusResponse.data.error); return res.json({ success: false, error: 'Model creation failed', details: statusResponse.data.error }); } console.log('โณ Still processing...'); } catch (pollError) { if (pollError.response?.status === 404) { console.log('โณ Not ready yet (404)'); continue; } console.log('โš ๏ธ Poll error:', pollError.response?.status, pollError.message); continue; } } // Timeout return res.json({ success: false, error: 'Timeout - Model creation took too long', jobId: jobId }); } catch (error) { console.error('โŒ ERROR:', error.response?.data || error.message); res.status(500).json({ success: false, error: error.message, details: error.response?.data }); } }); // ============================================ // CREATE MODEL WITH POSE + APPLY GARMENTS // ============================================ app.post('/api/create-model-and-tryon', async (req, res) => { try { const { posePrompt, shirtImage, pantImage, modelDescription } = req.body; console.log('\n๐Ÿงช CREATE MODEL WITH POSE + APPLY GARMENTS'); console.log('Pose Prompt:', posePrompt); console.log('Model Description:', modelDescription || 'Default model'); console.log('Shirt:', shirtImage?.substring(0, 50)); console.log('Pant:', pantImage?.substring(0, 50)); if (!FASHN_API_KEY) { return res.status(500).json({ success: false, error: 'FASHN_API_KEY not set in .env' }); } // STEP 1: Create model with specific pose console.log('\n๐ŸŽญ STEP 1: Creating model with pose...'); const fullPrompt = modelDescription ? `${modelDescription}, ${posePrompt}` : posePrompt; const modelCreateResponse = await axios.post( `${FASHN_API_URL}/run`, { model_name: 'model-create', inputs: { prompt: fullPrompt } }, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}`, 'Content-Type': 'application/json' }, timeout: 10000 } ); const modelJobId = modelCreateResponse.data.id; console.log('โœ… Model creation job created:', modelJobId); // Poll for model creation result console.log('โณ Waiting for model generation...'); let generatedModel = null; for (let i = 0; i < 30; i++) { await new Promise(resolve => setTimeout(resolve, 2000)); try { const statusResponse = await axios.get( `${FASHN_API_URL}/status/${modelJobId}`, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}` }, timeout: 10000 } ); console.log(`๐Ÿ”„ Model attempt ${i + 1}/30 - Status: ${statusResponse.data.status}`); if (statusResponse.data.status === 'completed') { generatedModel = statusResponse.data.output?.[0]; console.log('โœ… Model created with pose!'); console.log('๐Ÿ–ผ๏ธ Generated model:', generatedModel); break; } if (statusResponse.data.status === 'failed') { return res.json({ success: false, step: 'model-create', error: 'Model creation failed', details: statusResponse.data.error }); } } catch (pollError) { if (pollError.response?.status !== 404) { console.log('โš ๏ธ Model creation poll error:', pollError.message); } } } if (!generatedModel) { return res.json({ success: false, step: 'model-create', error: 'Model creation timeout' }); } // STEP 2: Apply SHIRT to the generated model console.log('\n๐Ÿ‘• STEP 2: Applying SHIRT to generated model...'); const shirtResponse = await axios.post( `${FASHN_API_URL}/run`, { model_name: 'tryon-v1.6', inputs: { model_image: generatedModel, garment_image: shirtImage, category: 'tops' } }, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}`, 'Content-Type': 'application/json' }, timeout: 10000 } ); const shirtJobId = shirtResponse.data.id; console.log('โœ… Shirt job created:', shirtJobId); // Poll for shirt result console.log('โณ Waiting for shirt...'); let shirtResult = null; for (let i = 0; i < 30; i++) { await new Promise(resolve => setTimeout(resolve, 2000)); try { const statusResponse = await axios.get( `${FASHN_API_URL}/status/${shirtJobId}`, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}` }, timeout: 10000 } ); console.log(`๐Ÿ”„ Shirt attempt ${i + 1}/30 - Status: ${statusResponse.data.status}`); if (statusResponse.data.status === 'completed') { shirtResult = statusResponse.data.output?.[0]; console.log('โœ… Shirt applied!'); console.log('๐Ÿ–ผ๏ธ With shirt:', shirtResult); break; } if (statusResponse.data.status === 'failed') { return res.json({ success: false, step: 'shirt', error: 'Shirt application failed', details: statusResponse.data.error, generatedModel: generatedModel }); } } catch (pollError) { if (pollError.response?.status !== 404) { console.log('โš ๏ธ Shirt poll error:', pollError.message); } } } if (!shirtResult) { return res.json({ success: false, step: 'shirt', error: 'Shirt application timeout', generatedModel: generatedModel }); } // STEP 3: Apply PANT to the shirt result console.log('\n๐Ÿ‘– STEP 3: Applying PANT...'); const pantResponse = await axios.post( `${FASHN_API_URL}/run`, { model_name: 'tryon-v1.6', inputs: { model_image: shirtResult, garment_image: pantImage, category: 'bottoms' } }, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}`, 'Content-Type': 'application/json' }, timeout: 10000 } ); const pantJobId = pantResponse.data.id; console.log('โœ… Pant job created:', pantJobId); // Poll for pant result console.log('โณ Waiting for pant...'); let pantResult = null; for (let i = 0; i < 30; i++) { await new Promise(resolve => setTimeout(resolve, 2000)); try { const statusResponse = await axios.get( `${FASHN_API_URL}/status/${pantJobId}`, { headers: { 'Authorization': `Bearer ${FASHN_API_KEY}` }, timeout: 10000 } ); console.log(`๐Ÿ”„ Pant attempt ${i + 1}/30 - Status: ${statusResponse.data.status}`); if (statusResponse.data.status === 'completed') { pantResult = statusResponse.data.output?.[0]; console.log('โœ… Pant applied!'); console.log('๐Ÿ–ผ๏ธ Final result:', pantResult); break; } if (statusResponse.data.status === 'failed') { return res.json({ success: false, step: 'pant', error: 'Pant application failed', details: statusResponse.data.error, generatedModel: generatedModel, shirtResult: shirtResult }); } } catch (pollError) { if (pollError.response?.status !== 404) { console.log('โš ๏ธ Pant poll error:', pollError.message); } } } if (!pantResult) { return res.json({ success: false, step: 'pant', error: 'Pant application timeout', generatedModel: generatedModel, shirtResult: shirtResult }); } // Return final result console.log('\nโœ… COMPLETE WORKFLOW FINISHED!'); return res.json({ success: true, message: 'Model created with pose and garments applied!', steps: { modelCreate: { jobId: modelJobId, result: generatedModel, prompt: fullPrompt }, shirt: { jobId: shirtJobId, result: shirtResult }, pant: { jobId: pantJobId, result: pantResult } }, finalImage: pantResult }); } catch (error) { console.error('โŒ ERROR:', error.response?.data || error.message); res.status(500).json({ success: false, error: error.message, details: error.response?.data }); } }); app.get('/api/products', async (req, res) => { try { const { data: products, error } = await supabase .from('products') .select('*'); if (error) { throw error; } res.json({ success: true, products: products || [] }); } catch (error) { console.error('โŒ Products error:', error); res.status(500).json({ success: false, error: error.message }); } }); app.post('/api/addproducts', async (req, res) => { try { const { name, price, description, image_url, category } = req.body; // Basic validation if (!name || !price || !description || !image_url || !category) { return res.status(400).json({ success: false, error: 'All fields (name, price, description, image_url, category) are required' }); } // Insert into Supabase const { data, error } = await supabase .from('products') .insert([ { name, price, description, image_url, category, created_at: new Date().toISOString(), updated_at: new Date().toISOString() } ]) .select('*') .single(); if (error) throw error; res.status(201).json({ success: true, message: 'โœ… Product created successfully', product: data }); } catch (error) { console.error('โŒ Product insert error:', error); res.status(500).json({ success: false, error: error.message }); } }); app.get('/api/backgrounds', async (req, res) => { try { const { data, error } = await supabase .from('backgrounds') .select('*') .order('id', { ascending: true }); if (error) throw error; res.json({ success: true, backgrounds: data || [] }); } catch (err) { console.error('โŒ Backgrounds fetch error:', err); res.status(500).json({ success: false, error: err.message }); } }); app.get('/api/poses', async (req, res) => { try { const { data, error } = await supabase .from('poses') .select('*') .order('id', { ascending: true }); if (error) throw error; res.json({ success: true, poses: data || [] }); } catch (err) { console.error('โŒ Poses fetch error:', err); res.status(500).json({ success: false, error: err.message }); } }); app.get('/api/effects', async (req, res) => { try { const { data, error } = await supabase .from('effects') .select('*') .order('id', { ascending: true }); if (error) throw error; res.json({ success: true, effects: data || [] }); } catch (err) { console.error('โŒ Effects fetch error:', err); res.status(500).json({ success: false, error: err.message }); } }); // ============================================ // ROOT // ============================================ app.get('/', (req, res) => { res.json({ message: 'FASHN.ai Test Server', endpoints: { virtualTryon: 'POST /api/virtual-tryon', changeBackground: 'POST /api/change-background', faceToModel: 'POST /api/face-to-model', faceToModelAndTryon: 'POST /api/face-to-model-and-tryon', createModel: 'POST /api/create-model', singleGarment: 'POST /api/test-fashn', twoGarments: 'POST /api/test-two-garments', createModelAndTryon: 'POST /api/create-model-and-tryon' }, fashnApiKey: FASHN_API_KEY ? 'โœ… Configured' : 'โŒ Missing' }); }); // ============================================ // START // ============================================ const PORT = 5235; app.listen(PORT, () => { console.log('\n========================================'); console.log('๐Ÿงช FASHN.ai Test Server'); console.log('========================================'); console.log(`Server: http://localhost:${PORT}`); console.log(`FASHN API Key: ${FASHN_API_KEY ? 'โœ… Set' : 'โŒ Not set'}`); console.log('========================================\n'); }); // ============================================ // START SERVER (Local Development) // ============================================ if (process.env.NODE_ENV !== 'production') { const PORT = process.env.PORT || 5235; app.listen(PORT, () => { console.log('\n========================================'); console.log('๐Ÿงช FASHN.ai Test Server'); console.log('========================================'); console.log(`Server: http://localhost:${PORT}`); console.log(`FASHN API Key: ${FASHN_API_KEY ? 'โœ… Set' : 'โŒ Not set'}`); console.log('========================================\n'); }); } // ============================================ // EXPORT FOR VERCEL (Serverless) // ============================================ module.exports = app;