const express = require('express'); const { GoogleGenerativeAI, SchemaType } = require('@google/generative-ai'); const path = require('path'); const app = express(); const port = process.env.PORT || 7860; // Middleware app.use(express.json({ limit: '50mb' })); app.use(express.static(path.join(__dirname, 'dist'))); // Initialize Gemini with server-side env var (no VITE_ prefix) const GEMINI_API_KEY = process.env.GEMINI_API_KEY; const genAI = GEMINI_API_KEY ? new GoogleGenerativeAI(GEMINI_API_KEY) : null; // Define the same schema as your client code const fileSchema = { type: SchemaType.OBJECT, properties: { files: { type: SchemaType.ARRAY, items: { type: SchemaType.OBJECT, properties: { fileName: { type: SchemaType.STRING }, code: { type: SchemaType.STRING } }, required: ["fileName", "code"] } } }, required: ["files"] }; // Helper function to format HTML (same as client) const { html: formatHtml } = require('js-beautify'); const formatCode = (code) => { try { return formatHtml(code, { indent_size: 2, indent_char: ' ', max_preserve_newlines: 2, preserve_newlines: true, indent_inner_html: true, brace_style: 'collapse', indent_scripts: 'normal', wrap_line_length: 120, unformatted: ['code', 'pre', 'em', 'strong', 'span'], content_unformatted: ['pre', 'code'], }); } catch (error) { console.error('Error formatting code:', error); return code; } }; const transformApiResponse = (response) => { const filesRecord = {}; if (response.files && Array.isArray(response.files)) { for (const file of response.files) { if (file.fileName && file.code) { filesRecord[file.fileName] = formatCode(file.code); } } } return filesRecord; }; // API endpoint for generating code app.post('/api/generate-code', async (req, res) => { if (!GEMINI_API_KEY) { return res.status(500).json({ error: "Gemini API key is not configured. Please set GEMINI_API_KEY in your environment variables." }); } try { const { messages } = req.body; const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash", generationConfig: { responseMimeType: "application/json", responseSchema: fileSchema } }); const result = await model.generateContent({ contents: [{ role: "user", parts: [{ text: `You are an expert frontend developer. Your task is to generate complete HTML pages based on user requests. **YOUR PRIMARY JOB:** - Understand the user's intent to create a website with one or more pages. - Generate complete, valid HTML files that fulfill this intent. - Your output will be structured as an array of objects, where each object has a "fileName" and a "code" property. **CRITICAL RULES ON FILE CREATION:** - **ONLY create full HTML page files** (e.g., \`index.html\`, \`about.html\`). - **DO NOT create separate files for components** like navbars or footers. These must be part of their respective HTML page files. - **DO NOT create separate .css or .js files.** All styling (via Tailwind classes) and scripts (via \`\` in the . - Use Alpine.js for interactivity via CDN: \`\` in the . - Ensure links between pages use relative paths (e.g., \`\`). **RULES:** - Each HTML file must be a complete document (DOCTYPE, html, head, body). - Ensure all pages are responsive and visually consistent. User request: ${messages[messages.length - 1].content}` }] }], safetySettings: [ { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" }, { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" }, { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" }, { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" } ] }); const response = await result.response; const parsedResponse = JSON.parse(response.text()); res.json(transformApiResponse(parsedResponse)); } catch (error) { console.error("Error in generateCode:", error); res.status(500).json({ error: error.message }); } }); // API endpoint for modifying code app.post('/api/modify-code', async (req, res) => { if (!GEMINI_API_KEY) { return res.status(500).json({ error: "Gemini API key is not configured. Please set GEMINI_API_KEY in your environment variables." }); } try { const { currentFiles, modificationRequest } = req.body; const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash", generationConfig: { responseMimeType: "application/json", responseSchema: fileSchema } }); const result = await model.generateContent({ contents: [{ role: "user", parts: [{ text: `You are an expert frontend developer working on a website modification task. **YOUR PRIMARY JOB:** - Understand the user's intent to modify their existing website. - Make precise changes to fulfill that intent, which may involve editing multiple files and/or creating new files. - Your output will be structured as an array of objects, where each object has a "fileName" and a "code" property for each file that was changed or created. **CRITICAL SAFETY RULE:** Your primary goal is to prevent data loss. When a user asks to move content to a new file or create a new file, you MUST provide the content for all new files in your response. It is unacceptable to only show the file where content was removed. Your response MUST be a complete fulfillment of the user's request, including both the modified original files and the newly created files. **CRITICAL RULES ON FILE CREATION & MODIFICATION:** - **ONLY create or modify full HTML page files** (e.g., \`index.html\`, \`about.html\`). - **DO NOT create separate files for components** like navbars or footers. Integrate them into the main HTML pages. - **DO NOT create separate .css or .js files.** All styling and scripts must be self-contained within each HTML file. - If you create a new page, you MUST also modify existing pages to add navigation links to it. **RULES:** - If a request involves changes that logically affect multiple files (e.g., updating a navigation menu), you MUST include ALL of those files in your response. **Current Project Files:** ${JSON.stringify(currentFiles, null, 2)} **Modification Request:** ${modificationRequest}` }] }], safetySettings: [ { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" }, { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" }, { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" }, { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" } ] }); const response = await result.response; const parsedResponse = JSON.parse(response.text()); res.json(transformApiResponse(parsedResponse)); } catch (error) { console.error("Error in modifyCode:", error); res.status(500).json({ error: error.message }); } }); // Serve React app for all other routes (use RegExp to avoid path-to-regexp wildcard parsing issues) app.get(/.*/, (req, res) => { res.sendFile(path.join(__dirname, 'dist', 'index.html')); }); app.listen(port, '0.0.0.0', () => { console.log(`Server running on port ${port}`); if (!GEMINI_API_KEY) { console.warn('Warning: GEMINI_API_KEY environment variable is not set'); } });