diff --git a/app.tsx b/app.tsx index 35574d0..6a6035d 100644 --- a/app.tsx +++ b/app.tsx @@ -931,8 +931,42 @@ export default function App() { className="w-full bg-slate-950 border border-slate-700 rounded-lg px-3 py-2 text-sm focus:border-cyan-500 outline-none resize-none" />
diff --git a/server.js b/server.js index 31efb54..a147c13 100644 --- a/server.js +++ b/server.js @@ -354,10 +354,48 @@ fs.ensureDirSync('uploads'); // ───────────────────────────────────────────────────────────────────────────── // Gemini SEO proxy // ───────────────────────────────────────────────────────────────────────────── +function asCleanText(v, max = 2000) { + if (typeof v !== 'string') return ''; + return v.trim().slice(0, max); +} + +function buildSeoPrompt(payload = {}) { + const directPrompt = asCleanText(payload.promptText, 4000); + if (directPrompt) return directPrompt; + + const fields = { + title: asCleanText(payload.title, 255), + artist: asCleanText(payload.artist, 255), + genre: asCleanText(payload.genre, 120), + platform: asCleanText(payload.platform, 120), + description: asCleanText(payload.description, 1000), + tags: asCleanText(payload.tags, 1000), + lyrics: asCleanText(payload.lyrics, 1200), + vibe: asCleanText(payload.vibe, 200), + }; + + const hasUsefulStructuredFields = Object.values(fields).some(Boolean); + if (!hasUsefulStructuredFields) return ''; + + return [ + 'You are an expert music marketing assistant.', + 'Generate SEO metadata for a song as strict JSON with keys: title, description, tags.', + `Platform: ${fields.platform || 'General'}`, + `Current title: ${fields.title || 'Untitled'}`, + `Artist: ${fields.artist || 'Unknown artist'}`, + `Genre: ${fields.genre || 'Unknown genre'}`, + fields.vibe ? `Vibe: ${fields.vibe}` : null, + fields.description ? `Context description: ${fields.description}` : null, + fields.tags ? `Existing tags/context: ${fields.tags}` : null, + fields.lyrics ? `Lyrics excerpt/context: ${fields.lyrics}` : null, + 'Keep title concise, description platform-friendly, and tags comma-separated.', + ].filter(Boolean).join('\n'); +} + app.post('/api/generate-seo', requireAuth, async (req, res) => { - const { promptText } = req.body; - if (!promptText || typeof promptText !== 'string' || promptText.length > 4000) { - return res.status(400).json({ error: 'Invalid prompt' }); + const promptText = buildSeoPrompt(req.body); + if (!promptText) { + return res.status(400).json({ error: 'Invalid prompt payload. Provide promptText or useful SEO fields.' }); } const apiKey = process.env.GEMINI_API_KEY; if (!apiKey) return res.status(500).json({ error: 'GEMINI_API_KEY not configured' }); @@ -387,7 +425,19 @@ app.post('/api/generate-seo', requireAuth, async (req, res) => { ); if (!response.ok) throw new Error(`Gemini error ${response.status}: ${await response.text()}`); const data = await response.json(); - res.json(JSON.parse(data?.candidates?.[0]?.content?.parts?.[0]?.text || '{}')); + const rawText = data?.candidates?.[0]?.content?.parts?.[0]?.text || '{}'; + let parsed; + try { + parsed = JSON.parse(rawText); + } catch { + return res.status(502).json({ error: 'Malformed JSON returned by Gemini' }); + } + + res.json({ + title: typeof parsed?.title === 'string' ? parsed.title : '', + description: typeof parsed?.description === 'string' ? parsed.description : '', + tags: typeof parsed?.tags === 'string' ? parsed.tags : '', + }); } catch (err) { console.error('Gemini proxy error:', err); res.status(500).json({ error: err.message });