Files
FunnyGirl/IdentityPromptLab/server.mjs
T
2026-05-19 13:42:41 +07:00

120 lines
3.5 KiB
JavaScript

import { createServer } from "node:http";
import { readFile } from "node:fs/promises";
import { extname, join, normalize } from "node:path";
import { fileURLToPath } from "node:url";
const root = fileURLToPath(new URL(".", import.meta.url));
const port = Number(process.env.PORT || 4173);
const mime = {
".html": "text/html; charset=utf-8",
".css": "text/css; charset=utf-8",
".js": "text/javascript; charset=utf-8",
".json": "application/json; charset=utf-8",
};
function readBody(request) {
return new Promise((resolve, reject) => {
let body = "";
request.on("data", (chunk) => {
body += chunk;
if (body.length > 2_000_000) {
reject(new Error("Request body is too large"));
request.destroy();
}
});
request.on("end", () => resolve(body));
request.on("error", reject);
});
}
function sendJson(response, status, payload) {
response.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
response.end(JSON.stringify(payload));
}
async function handleChat(request, response) {
try {
const payload = JSON.parse(await readBody(request));
const apiBase = String(payload.apiBase || "").replace(/\/+$/, "");
const apiKey = String(payload.apiKey || "");
const model = String(payload.model || "");
if (!apiBase || !apiKey || !model) {
sendJson(response, 400, { error: "API base, API key, and model are required." });
return;
}
const upstreamBody = {
model,
messages: payload.messages || [],
temperature: payload.temperature,
max_tokens: payload.max_tokens,
};
if (payload.response_format) {
upstreamBody.response_format = payload.response_format;
}
const upstream = await fetch(`${apiBase}/chat/completions`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify(upstreamBody),
});
const data = await upstream.json().catch(() => ({}));
if (!upstream.ok) {
sendJson(response, upstream.status, { error: data?.error?.message || "Model request failed." });
return;
}
sendJson(response, 200, { content: data?.choices?.[0]?.message?.content || "" });
} catch (error) {
const cause = error.cause?.message ? ` (${error.cause.message})` : "";
sendJson(response, 500, { error: `${error.message}${cause}` });
}
}
async function handleStatic(request, response) {
const url = new URL(request.url || "/", `http://${request.headers.host}`);
const cleanPath = normalize(url.pathname === "/" ? "/index.html" : url.pathname).replace(/^(\.\.[/\\])+/, "");
const filePath = join(root, cleanPath);
if (!filePath.startsWith(root)) {
response.writeHead(403);
response.end("Forbidden");
return;
}
try {
const content = await readFile(filePath);
response.writeHead(200, { "Content-Type": mime[extname(filePath)] || "application/octet-stream" });
response.end(content);
} catch {
response.writeHead(404);
response.end("Not found");
}
}
const server = createServer((request, response) => {
if (request.method === "POST" && request.url === "/api/chat") {
handleChat(request, response);
return;
}
if (request.method === "GET" || request.method === "HEAD") {
handleStatic(request, response);
return;
}
response.writeHead(405);
response.end("Method not allowed");
});
server.listen(port, () => {
console.log(`Identity Prompt Lab: http://localhost:${port}`);
});