Spaces:
Running
Running
File size: 5,070 Bytes
cbcec77 214d697 cbcec77 214d697 cbcec77 214d697 cbcec77 214d697 cbcec77 | 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 | const express = require('express');
const { createProxyMiddleware, fixRequestBody } = require('http-proxy-middleware');
const fs = require('fs');
const path = require('path');
const app = express();
const proxyCache = {};
const stats = {};
// Load Endpoints & Initialize Stats
let endpoints = {};
try {
endpoints = JSON.parse(fs.readFileSync(path.join(__dirname, 'endpoints.json'), 'utf8'));
console.log("✔ Loaded Endpoints:", Object.keys(endpoints).join(', '));
for (const key in endpoints) {
stats[key] = { count: 0, totalTime: 0, avgTime: 0 };
}
} catch (e) {
console.error("✘ Error: endpoints.json missing or invalid.");
}
// 1. GLOBAL CORS INTERCEPTOR
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') return res.sendStatus(200);
next();
});
app.use(express.json({ limit: '50mb' }));
// 2. OPENAI OMNI MODERATION MIDDLEWARE
async function checkModeration(req, res, next) {
if (req.method !== 'POST' || !req.body?.messages) return next();
try {
const textToCheck = req.body.messages.map(m => m.content).join('\n');
if (!textToCheck.trim()) return next();
if (!process.env.OPENAI_API_KEY) {
console.warn("[Moderation] Skipped: OPENAI_API_KEY is not defined in Space Env Settings.");
return next();
}
const modRes = await fetch('https://api.openai.com/v1/moderations', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`
},
body: JSON.stringify({
model: "omni-moderation-latest",
input: textToCheck
})
});
const modData = await modRes.json();
if (modData?.results?.[0]) {
const results = modData.results[0];
if (results.categories['sexual/minors']) {
console.warn("[Moderation] Blocked CSAM content.");
return res.status(403).json({ error: "Content policy violation." });
}
}
} catch (err) {
console.error("[Moderation Error]", err.message);
}
next();
}
// Serve UI assets out of the explicit public folder
app.use(express.static(path.join(__dirname, 'public')));
app.get('/api/endpoints', (req, res) => res.json(endpoints));
app.get('/api/stats', (req, res) => res.json(stats));
// 3. THE SAFE PROXY SETUP
app.use('/:nick', checkModeration, (req, res, next) => {
const { nick } = req.params;
if (nick === 'api' || !endpoints[nick]) return next();
// In-memory request timing metrics
const startTime = Date.now();
res.on('finish', () => {
try {
const duration = Date.now() - startTime;
if (stats[nick]) {
stats[nick].count++;
stats[nick].totalTime += duration;
stats[nick].avgTime = Math.round(stats[nick].totalTime / stats[nick].count);
}
} catch (e) {
console.error("Metrics logging error:", e);
}
});
if (!proxyCache[nick]) {
const target = endpoints[nick].url;
console.log(`[Setup] Routing /${nick} -> ${target}`);
proxyCache[nick] = createProxyMiddleware({
target: target,
changeOrigin: true,
pathRewrite: { [`^/${nick}`]: '' },
onProxyReq: (proxyReq, req, res) => {
if (req.method === 'POST' && req.body) {
if (nick === 'nim') {
req.body.chat_template_kwargs = { enable_thinking: true, clear_thinking: false };
if (req.body.messages && Array.isArray(req.body.messages)) {
req.body.messages.forEach(msg => {
if (msg.role === 'developer') msg.role = 'system';
});
}
}
// Re-serializes the modified body
fixRequestBody(proxyReq, req);
// 🔥 THE FIX: Force the proxy request to finish!
// Since the async middleware drained the original stream,
// we have to manually tell the proxy we are done writing.
proxyReq.end();
}
},
onError: (err, req, res) => {
console.error(`[Error: ${nick}]`, err.message);
if (!res.headersSent) res.status(502).json({ error: "Provider Offline or Timeout." });
}
});
}
return proxyCache[nick](req, res, next);
});
const PORT = 7860;
app.listen(PORT, '0.0.0.0', () => console.log(`🚀 Hub active on port ${PORT}`)); |