FrontPilot / server.js
rocky1410's picture
fixed server file
0b4d89b
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');
}
});