Spaces:
Running
Running
| 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 \`<script>\` tags for Alpine.js) must be self-contained within each HTML file. | |
| **TECHNOLOGY CONSTRAINTS:** | |
| - Use Tailwind CSS via CDN: \`<script src="https://cdn.tailwindcss.com"></script>\` in the <head>. | |
| - Use Alpine.js for interactivity via CDN: \`<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>\` in the <head>. | |
| - Ensure links between pages use relative paths (e.g., \`<a href="./about.html">\`). | |
| **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'); | |
| } | |
| }); |