File size: 7,220 Bytes
fd211b3 d9606f2 fd211b3 f1357b6 fd211b3 f1357b6 fd211b3 d9606f2 fd211b3 99f8658 fd211b3 d9606f2 fd211b3 | 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 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | /**
* server.js - API ไปฃ็ๆๅกไธปๅ
ฅๅฃ
*
* ๅฏนๅคๆด้ฒ OpenAI / Anthropic ๅ
ผๅฎน็ API ็ซฏ็น
* ๅฏนๅ
้่ฟ่ดฆๅทๆฑ ่ฝฎๆขไฝฟ็จ chataibot.pro ่ฏ็จ้ขๅบฆ
*
* ๅฏๅจ: node server.js
* ็ซฏๅฃ: config.server.port (้ป่ฎค 9090)
*/
import http from 'http';
import config from './config.js';
import { AccountPool } from './pool.js';
import { handleChatCompletions } from './openai-adapter.js';
import { handleMessages } from './anthropic-adapter.js';
import { getModelList } from './message-convert.js';
import { getDashboardHTML } from './ui.js';
// ==================== ๅๅงๅ ====================
const pool = new AccountPool();
await pool.init();
// ==================== HTTP ๆๅกๅจ ====================
function parseBody(req, maxSize = 2 * 1024 * 1024) { // ้ป่ฎค 2MB
return new Promise((resolve, reject) => {
let data = '';
let size = 0;
req.on('data', chunk => {
size += chunk.length;
if (size > maxSize) {
req.destroy();
reject(new Error('Request body too large'));
return;
}
data += chunk;
});
req.on('end', () => {
try { resolve(JSON.parse(data)); }
catch { reject(new Error('Invalid JSON body')); }
});
req.on('error', reject);
});
}
function sendJson(res, status, data) {
res.writeHead(status, {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
});
res.end(JSON.stringify(data));
}
function sendError(res, status, message) {
sendJson(res, status, {
error: { message, type: 'invalid_request_error', code: status },
});
}
function sendHtml(res, html) {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache' });
res.end(html);
}
function checkAuth(req) {
if (!config.server.apiKey) return true;
// Bearer token
const auth = req.headers['authorization'] || '';
if (auth.startsWith('Bearer ') && auth.slice(7) === config.server.apiKey) return true;
// x-api-key header (Anthropic ้ฃๆ ผ)
if (req.headers['x-api-key'] === config.server.apiKey) return true;
return false;
}
const server = http.createServer(async (req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const pathname = url.pathname;
const method = req.method;
// CORS ้ขๆฃ
if (method === 'OPTIONS') {
res.writeHead(204, {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-api-key, anthropic-version',
'Access-Control-Max-Age': '86400',
});
res.end();
return;
}
// ๅฅๅบทๆฃๆฅ (ไธ้่ฆ่ฎค่ฏ)
if (method === 'GET' && pathname === '/health') {
sendJson(res, 200, { status: 'ok', uptime: process.uptime() });
return;
}
// Dashboard ๆงๅถๅฐ
if (method === 'GET' && (pathname === '/' || pathname === '/ui')) {
sendHtml(res, getDashboardHTML());
return;
}
// ๆจกๅๅ่กจ (Dashboard ้่ฆๅ
่ฎค่ฏ่ฎฟ้ฎ)
if (method === 'GET' && pathname === '/v1/models') {
sendJson(res, 200, { object: 'list', data: getModelList() });
return;
}
// ่ดฆๅทๆฑ ็ถๆ (Dashboard ้่ฆๅ
่ฎค่ฏ่ฎฟ้ฎ)
if (method === 'GET' && pathname === '/pool/status') {
sendJson(res, 200, pool.getStatus());
return;
}
// ๆณจๅๆฅๅฟ (Dashboard ้่ฆๅ
่ฎค่ฏ่ฎฟ้ฎ)
if (method === 'GET' && pathname === '/pool/logs') {
const since = parseInt(url.searchParams.get('since')) || 0;
sendJson(res, 200, { logs: pool.getLogs(since) });
return;
}
// ๆๅจๆณจๅ่ดฆๅท (Dashboard ่ฐ็จ)
if (method === 'POST' && pathname === '/pool/register') {
try {
const body = await parseBody(req);
const count = Math.min(Math.max(1, body.count || 5), 50);
const provider = body.provider || undefined;
const concurrency = body.concurrency ? Math.min(Math.max(1, body.concurrency), 20) : undefined;
const result = pool.manualRegister(count, provider, concurrency);
sendJson(res, 200, { ok: true, message: `ๅทฒๆไบค ${result.queued} ไธชๆณจๅไปปๅก`, ...result });
} catch (e) {
sendJson(res, 200, { ok: false, message: e.message });
}
return;
}
// ่ฎค่ฏๆ ก้ช
if (!checkAuth(req)) {
sendError(res, 401, 'Invalid API key');
return;
}
try {
// ==================== ่ทฏ็ฑ ====================
// OpenAI: POST /v1/chat/completions
if (method === 'POST' && pathname === '/v1/chat/completions') {
const body = await parseBody(req);
await handleChatCompletions(body, res, pool);
return;
}
// Anthropic: POST /v1/messages
if (method === 'POST' && pathname === '/v1/messages') {
const body = await parseBody(req);
await handleMessages(body, res, pool);
return;
}
// 404
sendError(res, 404, `Not found: ${method} ${pathname}`);
} catch (e) {
console.error(`[Server] ่ฏทๆฑ้่ฏฏ: ${e.message}`);
if (!res.headersSent) {
sendError(res, 500, 'Internal server error');
}
}
});
// ==================== ๅฏๅจ ====================
const { port, host } = config.server;
server.listen(port, host, () => {
console.log(`\n โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ`);
console.log(` โ ChatAIBot API Proxy โ`);
console.log(` โ http://${host}:${port} โ`);
console.log(` โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค`);
console.log(` โ Dashboard: http://${host}:${port}/ โ`);
console.log(` โ OpenAI: POST /v1/chat/completions โ`);
console.log(` โ Anthropic: POST /v1/messages โ`);
console.log(` โ Models: GET /v1/models โ`);
console.log(` โ Pool: GET /pool/status โ`);
console.log(` โ Health: GET /health โ`);
console.log(` โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค`);
console.log(` โ Auth: ${config.server.apiKey ? 'Bearer ' + config.server.apiKey.substring(0, 8) + '...' : 'ๆ (ๅผๆพ่ฎฟ้ฎ)'}${' '.repeat(Math.max(0, 24 - (config.server.apiKey ? 16 : 13)))}โ`);
console.log(` โ Pool: ${pool.getStatus().active} active / ${pool.getStatus().total} total${' '.repeat(Math.max(0, 22 - String(pool.getStatus().active).length - String(pool.getStatus().total).length))}โ`);
console.log(` โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n`);
});
// ไผ้
้ๅบ
process.on('SIGINT', () => {
console.log('\n[Server] ๆญฃๅจๅ
ณ้ญ...');
pool.destroy();
server.close(() => process.exit(0));
});
process.on('SIGTERM', () => {
pool.destroy();
server.close(() => process.exit(0));
});
|