feat: implement SparkPlug backend API integration for Gemini and Meshy
Build and Deploy / build-and-push (push) Successful in 1m20s
Build and Deploy / build-and-push (push) Successful in 1m20s
This commit is contained in:
@@ -7,6 +7,9 @@ const { v4: uuidv4 } = require('uuid');
|
||||
const multer = require('multer');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const pdfParse = require('pdf-parse');
|
||||
const { GoogleGenerativeAI } = require('@google/generative-ai');
|
||||
const axios = require('axios');
|
||||
|
||||
const { db, run, get, all } = require('./database');
|
||||
const { sendMail } = require('./mail');
|
||||
@@ -305,6 +308,176 @@ app.post('/api/upload/complete', authenticate, (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// SPARK PLUG WORKFLOW APIS
|
||||
// ==========================================
|
||||
|
||||
const getKeysFromProfile = async (userId) => {
|
||||
const profile = await get('SELECT api_keys FROM profiles WHERE id = ?', [userId]);
|
||||
if (profile && profile.api_keys) {
|
||||
try {
|
||||
return JSON.parse(profile.api_keys);
|
||||
} catch(e) {}
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
// 1. Extract PDF (Gemini)
|
||||
app.post('/api/sparkplug/extract', authenticate, upload.single('file'), async (req, res) => {
|
||||
if (!req.file) return res.status(400).json({ error: 'No file uploaded' });
|
||||
|
||||
try {
|
||||
const keys = await getKeysFromProfile(req.user.id);
|
||||
const geminiKey = keys.GEMINI_API_KEY;
|
||||
if (!geminiKey) return res.status(400).json({ error: 'Gemini API key missing' });
|
||||
|
||||
// Parse PDF
|
||||
const dataBuffer = fs.readFileSync(req.file.path);
|
||||
const pdfData = await pdfParse(dataBuffer);
|
||||
const textContent = pdfData.text;
|
||||
|
||||
// Call Gemini to extract prompt
|
||||
const genAI = new GoogleGenerativeAI(geminiKey);
|
||||
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
|
||||
|
||||
const prompt = `
|
||||
You are an expert product designer. Read the following text from a product spec PDF and create a highly detailed, concise visual design prompt for an image generation AI.
|
||||
Focus on the object's shape, color, materials, packaging details, and label content. Do not include background details.
|
||||
|
||||
TEXT:
|
||||
${textContent.substring(0, 30000)} // Limit to avoid context length issues if massive
|
||||
`;
|
||||
|
||||
const result = await model.generateContent(prompt);
|
||||
const response = await result.response;
|
||||
const extractedPrompt = response.text();
|
||||
|
||||
res.json({ prompt: extractedPrompt });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
} finally {
|
||||
if (req.file) {
|
||||
fs.unlinkSync(req.file.path); // cleanup uploaded PDF
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 2. Generate Images (Nano Banana / Gemini Imagen)
|
||||
app.post('/api/sparkplug/generate-images', authenticate, async (req, res) => {
|
||||
const { prompt } = req.body;
|
||||
if (!prompt) return res.status(400).json({ error: 'No prompt provided' });
|
||||
|
||||
try {
|
||||
const keys = await getKeysFromProfile(req.user.id);
|
||||
const apiKey = keys.NANO_BANANA_API_KEY || keys.GEMINI_API_KEY;
|
||||
if (!apiKey) return res.status(400).json({ error: 'Nano Banana (Gemini) API key missing' });
|
||||
|
||||
// We will use axios to call the Gemini Imagen REST API
|
||||
const baseUrl = 'https://generativelanguage.googleapis.com/v1beta/models/imagen-3.0-generate-001:predict';
|
||||
|
||||
const views = ['Front view', 'Back view', 'Left side view', 'Right side view'];
|
||||
const imageUrls = [];
|
||||
|
||||
// Run parallel generation for all 4 views
|
||||
const promises = views.map(async (view, index) => {
|
||||
const fullPrompt = `${prompt}. ${view}, isolated on a pure white background, studio lighting.`;
|
||||
|
||||
const payload = {
|
||||
instances: [
|
||||
{
|
||||
prompt: fullPrompt
|
||||
}
|
||||
],
|
||||
parameters: {
|
||||
sampleCount: 1,
|
||||
outputOptions: { mimeType: 'image/png' }
|
||||
}
|
||||
};
|
||||
|
||||
const response = await axios.post(`${baseUrl}?key=${apiKey}`, payload, {
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
|
||||
const base64Image = response.data.predictions[0].bytesBase64Encoded;
|
||||
const buffer = Buffer.from(base64Image, 'base64');
|
||||
|
||||
const filename = `sparkplug-${req.user.id}-${Date.now()}-${index}.png`;
|
||||
const filepath = path.join(uploadsDir, filename);
|
||||
fs.writeFileSync(filepath, buffer);
|
||||
|
||||
return `/uploads/${filename}`;
|
||||
});
|
||||
|
||||
const generatedUrls = await Promise.all(promises);
|
||||
res.json({ images: generatedUrls });
|
||||
} catch (err) {
|
||||
// Gemini HTTP errors usually have response.data.error
|
||||
const errorMsg = err.response?.data?.error?.message || err.message;
|
||||
res.status(500).json({ error: errorMsg });
|
||||
}
|
||||
});
|
||||
|
||||
// 3. Generate 3D (Meshy)
|
||||
app.post('/api/sparkplug/generate-3d', authenticate, async (req, res) => {
|
||||
const { imageUrls } = req.body;
|
||||
if (!imageUrls || imageUrls.length === 0) return res.status(400).json({ error: 'No images provided' });
|
||||
|
||||
try {
|
||||
const keys = await getKeysFromProfile(req.user.id);
|
||||
const meshyKey = keys.MESHY_API_KEY;
|
||||
if (!meshyKey) return res.status(400).json({ error: 'Meshy API key missing' });
|
||||
|
||||
// Assuming imageUrls are local paths like /uploads/...
|
||||
// Meshy requires base64 Data URIs if the images are not publicly accessible URLs.
|
||||
// Our local URLs are not publicly accessible to Meshy's servers! We MUST convert to base64.
|
||||
|
||||
const base64Images = imageUrls.map(url => {
|
||||
// Extract filename from URL
|
||||
const filename = url.replace('/uploads/', '');
|
||||
const filepath = path.join(uploadsDir, filename);
|
||||
const buffer = fs.readFileSync(filepath);
|
||||
return `data:image/png;base64,${buffer.toString('base64')}`;
|
||||
});
|
||||
|
||||
const response = await axios.post('https://api.meshy.ai/openapi/v1/multi-image-to-3d', {
|
||||
image_urls: base64Images,
|
||||
enable_pbr: true
|
||||
}, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${meshyKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
res.json({ taskId: response.data.result });
|
||||
} catch (err) {
|
||||
const errorMsg = err.response?.data?.message || err.message;
|
||||
res.status(500).json({ error: errorMsg });
|
||||
}
|
||||
});
|
||||
|
||||
// 4. Check 3D Status (Meshy)
|
||||
app.get('/api/sparkplug/status-3d/:taskId', authenticate, async (req, res) => {
|
||||
const { taskId } = req.params;
|
||||
|
||||
try {
|
||||
const keys = await getKeysFromProfile(req.user.id);
|
||||
const meshyKey = keys.MESHY_API_KEY;
|
||||
if (!meshyKey) return res.status(400).json({ error: 'Meshy API key missing' });
|
||||
|
||||
const response = await axios.get(`https://api.meshy.ai/openapi/v1/multi-image-to-3d/${taskId}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${meshyKey}`
|
||||
}
|
||||
});
|
||||
|
||||
res.json(response.data);
|
||||
} catch (err) {
|
||||
const errorMsg = err.response?.data?.message || err.message;
|
||||
res.status(500).json({ error: errorMsg });
|
||||
}
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 3005;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Backend server running on port ${PORT}`);
|
||||
|
||||
Reference in New Issue
Block a user