Initial commit: NekoAI Cloudflare chat app
This commit is contained in:
132
src/index.js
Normal file
132
src/index.js
Normal file
@@ -0,0 +1,132 @@
|
||||
export default {
|
||||
async fetch(request, env) {
|
||||
const url = new URL(request.url);
|
||||
|
||||
if (request.method === 'OPTIONS') {
|
||||
return new Response(null, { headers: corsHeaders(request, env) });
|
||||
}
|
||||
|
||||
if (url.pathname === '/api/models' && request.method === 'GET') {
|
||||
return handleModels(request, env);
|
||||
}
|
||||
|
||||
if (url.pathname === '/api/chat' && request.method === 'POST') {
|
||||
return handleChat(request, env);
|
||||
}
|
||||
|
||||
return json({ error: 'Not found' }, 404, request, env);
|
||||
},
|
||||
};
|
||||
|
||||
async function handleModels(request, env) {
|
||||
const authError = verifyAccessKey(request, env);
|
||||
if (authError) return authError;
|
||||
|
||||
const upstream = await fetch(joinUrl(env.OPENAI_BASE_URL, '/v1/models'), {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${env.OPENAI_API_KEY}`,
|
||||
},
|
||||
});
|
||||
|
||||
const text = await upstream.text();
|
||||
let data;
|
||||
|
||||
try {
|
||||
data = JSON.parse(text);
|
||||
} catch {
|
||||
return json({ error: 'Invalid upstream /v1/models response', raw: text.slice(0, 500) }, 502, request, env);
|
||||
}
|
||||
|
||||
if (!upstream.ok) {
|
||||
return json(
|
||||
{
|
||||
error: data?.error?.message || data?.error || `Upstream /v1/models failed with HTTP ${upstream.status}`,
|
||||
upstreamStatus: upstream.status,
|
||||
},
|
||||
upstream.status,
|
||||
request,
|
||||
env,
|
||||
);
|
||||
}
|
||||
|
||||
const models = Array.isArray(data?.data)
|
||||
? data.data
|
||||
.filter((item) => item && typeof item.id === 'string' && item.id.trim())
|
||||
.map((item) => ({ id: item.id, label: item.id }))
|
||||
: [];
|
||||
|
||||
return json({ models }, 200, request, env);
|
||||
}
|
||||
|
||||
async function handleChat(request, env) {
|
||||
const authError = verifyAccessKey(request, env);
|
||||
if (authError) return authError;
|
||||
|
||||
let payload;
|
||||
try {
|
||||
payload = await request.json();
|
||||
} catch {
|
||||
return json({ error: 'Invalid JSON body' }, 400, request, env);
|
||||
}
|
||||
|
||||
const upstream = await fetch(joinUrl(env.OPENAI_BASE_URL, '/v1/chat/completions'), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${env.OPENAI_API_KEY}`,
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
const headers = new Headers(corsHeaders(request, env));
|
||||
const contentType = upstream.headers.get('content-type');
|
||||
if (contentType) headers.set('content-type', contentType);
|
||||
|
||||
return new Response(upstream.body, {
|
||||
status: upstream.status,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
function verifyAccessKey(request, env) {
|
||||
const auth = request.headers.get('authorization') || '';
|
||||
const expected = `Bearer ${env.ACCESS_KEY}`;
|
||||
|
||||
if (!env.ACCESS_KEY) {
|
||||
return json({ error: 'Server ACCESS_KEY is not configured' }, 500, request, env);
|
||||
}
|
||||
|
||||
if (auth !== expected) {
|
||||
return json({ error: 'Unauthorized' }, 401, request, env);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function joinUrl(base, path) {
|
||||
return `${String(base || '').replace(/\/$/, '')}${path}`;
|
||||
}
|
||||
|
||||
function corsHeaders(request, env) {
|
||||
const origin = request?.headers?.get('origin');
|
||||
const allowedOrigin = env.ALLOWED_ORIGIN || '*';
|
||||
const finalOrigin = allowedOrigin === '*' ? '*' : origin || allowedOrigin;
|
||||
|
||||
return {
|
||||
'Access-Control-Allow-Origin': finalOrigin,
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
||||
'Vary': 'Origin',
|
||||
};
|
||||
}
|
||||
|
||||
function json(data, status = 200, request, env) {
|
||||
return new Response(JSON.stringify(data), {
|
||||
status,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
...corsHeaders(request, env),
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user