Spaces:
Running
Running
File size: 5,663 Bytes
538d81e f7333d7 538d81e f7333d7 538d81e f7333d7 538d81e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | import http from 'http';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { createAiCore, UpstreamError } from './server/aiCore.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const PORT = process.env.PORT || 7860;
const HOST = process.env.HOST || '0.0.0.0';
const distDir = path.join(__dirname, 'dist');
const ai = createAiCore(process.env);
function sendJson(res, statusCode, payload) {
res.writeHead(statusCode, {
'Content-Type': 'application/json; charset=utf-8',
'Cache-Control': 'no-store',
'Access-Control-Allow-Origin': '*',
});
res.end(JSON.stringify(payload));
}
function readBody(req) {
return new Promise((resolve, reject) => {
let data = '';
req.on('data', (chunk) => (data += chunk));
req.on('end', () => resolve(data));
req.on('error', reject);
});
}
// MIME types mapping
const mimeTypes = {
'.html': 'text/html; charset=utf-8',
'.js': 'application/javascript',
'.mjs': 'application/javascript',
'.css': 'text/css',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.ttf': 'font/ttf',
'.eot': 'application/vnd.ms-fontobject',
};
function getMimeType(filePath) {
const ext = path.extname(filePath).toLowerCase();
return mimeTypes[ext] || 'application/octet-stream';
}
function serveFile(res, filePath, statusCode = 200) {
try {
const content = fs.readFileSync(filePath);
const mimeType = getMimeType(filePath);
res.writeHead(statusCode, {
'Content-Type': mimeType,
'Cache-Control': 'public, max-age=3600',
'Access-Control-Allow-Origin': '*',
});
res.end(content);
} catch (err) {
console.error(`Error serving file ${filePath}:`, err);
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('Internal Server Error');
}
}
const server = http.createServer((req, res) => {
// Set CORS headers
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
// Remove query string from URL
const urlPath = req.url.split('?')[0];
// API routes (production)
if (urlPath === '/api/ai/ping' && req.method === 'GET') {
return sendJson(res, 200, ai.ping());
}
if (urlPath === '/api/ai/mix' && req.method === 'POST') {
return (async () => {
try {
const raw = await readBody(req);
const parsed = raw ? JSON.parse(raw) : {};
const result = await ai.mix({
substances: parsed.substances || [],
temperatureC: parsed.temperatureC,
});
return sendJson(res, 200, result);
} catch (err) {
const triedModels = err?.triedModels || [];
const lastModel = err?.lastModel || null;
if (err instanceof UpstreamError) {
if (err.status === 401 || err.status === 403) {
return sendJson(res, 401, { error: 'API key Groq invalide ou non autorisee', triedModels, lastModel });
}
if (err.status === 429) {
return sendJson(res, 429, { error: 'Quota/limite Groq atteinte (429). Reessaye plus tard.', triedModels, lastModel });
}
if (err.status >= 400 && err.status < 500) {
return sendJson(res, err.status, { error: err.upstreamMessage || err.message, triedModels, lastModel });
}
return sendJson(res, 502, { error: 'Erreur upstream Groq lors de la generation IA', details: err.message, triedModels, lastModel });
}
return sendJson(res, 500, { error: 'Erreur serveur /api/ai/mix', details: err?.message || String(err), triedModels, lastModel });
}
})();
}
let filePath = path.join(distDir, urlPath === '/' ? 'index.html' : urlPath);
// Normalize path to prevent directory traversal
filePath = path.normalize(filePath);
if (!filePath.startsWith(distDir)) {
res.writeHead(403, { 'Content-Type': 'text/plain' });
res.end('Forbidden');
return;
}
// Check if file exists
try {
const stats = fs.statSync(filePath);
if (stats.isFile()) {
serveFile(res, filePath);
} else if (stats.isDirectory()) {
// Try to serve index.html from the directory
const indexPath = path.join(filePath, 'index.html');
if (fs.existsSync(indexPath)) {
serveFile(res, indexPath);
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
}
} catch (err) {
// File not found, serve index.html for SPA routing
const indexPath = path.join(distDir, 'index.html');
if (fs.existsSync(indexPath)) {
serveFile(res, indexPath, 200);
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
}
});
server.listen(PORT, HOST, () => {
console.log(`\n✓ Server is running!`);
console.log(`✓ Listening on http://${HOST}:${PORT}`);
console.log(`✓ Environment: ${process.env.NODE_ENV || 'development'}`);
});
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down gracefully...');
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});
process.on('SIGINT', () => {
console.log('SIGINT received, shutting down gracefully...');
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});
|