Merge branch 'main' of https://github.com/Yidadaa/ChatGPT-Next-Web
commit
418d270d74
@ -0,0 +1,70 @@
|
|||||||
|
import { NextRequest } from "next/server";
|
||||||
|
import { getServerSideConfig } from "../config/server";
|
||||||
|
import md5 from "spark-md5";
|
||||||
|
|
||||||
|
const serverConfig = getServerSideConfig();
|
||||||
|
|
||||||
|
function getIP(req: NextRequest) {
|
||||||
|
let ip = req.ip ?? req.headers.get("x-real-ip");
|
||||||
|
const forwardedFor = req.headers.get("x-forwarded-for");
|
||||||
|
|
||||||
|
if (!ip && forwardedFor) {
|
||||||
|
ip = forwardedFor.split(",").at(0) ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseApiKey(bearToken: string) {
|
||||||
|
const token = bearToken.trim().replaceAll("Bearer ", "").trim();
|
||||||
|
const isOpenAiKey = token.startsWith("sk-");
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessCode: isOpenAiKey ? "" : token,
|
||||||
|
apiKey: isOpenAiKey ? token : "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function auth(req: NextRequest) {
|
||||||
|
const authToken = req.headers.get("Authorization") ?? "";
|
||||||
|
|
||||||
|
// check if it is openai api key or user token
|
||||||
|
const { accessCode, apiKey: token } = parseApiKey(authToken);
|
||||||
|
|
||||||
|
const hashedCode = md5.hash(accessCode ?? "").trim();
|
||||||
|
|
||||||
|
console.log("[Auth] allowed hashed codes: ", [...serverConfig.codes]);
|
||||||
|
console.log("[Auth] got access code:", accessCode);
|
||||||
|
console.log("[Auth] hashed access code:", hashedCode);
|
||||||
|
console.log("[User IP] ", getIP(req));
|
||||||
|
console.log("[Time] ", new Date().toLocaleString());
|
||||||
|
|
||||||
|
if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !token) {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
needAccessCode: true,
|
||||||
|
msg: "Please go settings page and fill your access code.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// if user does not provide an api key, inject system api key
|
||||||
|
if (!token) {
|
||||||
|
const apiKey = serverConfig.apiKey;
|
||||||
|
if (apiKey) {
|
||||||
|
console.log("[Auth] use system api key");
|
||||||
|
req.headers.set("Authorization", `Bearer ${apiKey}`);
|
||||||
|
} else {
|
||||||
|
console.log("[Auth] admin did not provide an api key");
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
msg: "Empty Api Key",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("[Auth] use user api key");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: false,
|
||||||
|
};
|
||||||
|
}
|
@ -1,62 +0,0 @@
|
|||||||
import { createParser } from "eventsource-parser";
|
|
||||||
import { NextRequest } from "next/server";
|
|
||||||
import { requestOpenai } from "../common";
|
|
||||||
|
|
||||||
async function createStream(req: NextRequest) {
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
|
|
||||||
const res = await requestOpenai(req);
|
|
||||||
|
|
||||||
const contentType = res.headers.get("Content-Type") ?? "";
|
|
||||||
if (!contentType.includes("stream")) {
|
|
||||||
const content = await (
|
|
||||||
await res.text()
|
|
||||||
).replace(/provided:.*. You/, "provided: ***. You");
|
|
||||||
console.log("[Stream] error ", content);
|
|
||||||
return "```json\n" + content + "```";
|
|
||||||
}
|
|
||||||
|
|
||||||
const stream = new ReadableStream({
|
|
||||||
async start(controller) {
|
|
||||||
function onParse(event: any) {
|
|
||||||
if (event.type === "event") {
|
|
||||||
const data = event.data;
|
|
||||||
// https://beta.openai.com/docs/api-reference/completions/create#completions/create-stream
|
|
||||||
if (data === "[DONE]") {
|
|
||||||
controller.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const json = JSON.parse(data);
|
|
||||||
const text = json.choices[0].delta.content;
|
|
||||||
const queue = encoder.encode(text);
|
|
||||||
controller.enqueue(queue);
|
|
||||||
} catch (e) {
|
|
||||||
controller.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const parser = createParser(onParse);
|
|
||||||
for await (const chunk of res.body as any) {
|
|
||||||
parser.feed(decoder.decode(chunk, { stream: true }));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
|
||||||
try {
|
|
||||||
const stream = await createStream(req);
|
|
||||||
return new Response(stream);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[Chat Stream]", error);
|
|
||||||
return new Response(
|
|
||||||
["```json\n", JSON.stringify(error, null, " "), "\n```"].join(""),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const runtime = "edge";
|
|
@ -0,0 +1,101 @@
|
|||||||
|
import { createParser } from "eventsource-parser";
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { auth } from "../../auth";
|
||||||
|
import { requestOpenai } from "../../common";
|
||||||
|
|
||||||
|
async function createStream(res: Response) {
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
|
||||||
|
const stream = new ReadableStream({
|
||||||
|
async start(controller) {
|
||||||
|
function onParse(event: any) {
|
||||||
|
if (event.type === "event") {
|
||||||
|
const data = event.data;
|
||||||
|
// https://beta.openai.com/docs/api-reference/completions/create#completions/create-stream
|
||||||
|
if (data === "[DONE]") {
|
||||||
|
controller.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(data);
|
||||||
|
const text = json.choices[0].delta.content;
|
||||||
|
const queue = encoder.encode(text);
|
||||||
|
controller.enqueue(queue);
|
||||||
|
} catch (e) {
|
||||||
|
controller.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parser = createParser(onParse);
|
||||||
|
for await (const chunk of res.body as any) {
|
||||||
|
parser.feed(decoder.decode(chunk, { stream: true }));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatResponse(msg: any) {
|
||||||
|
const jsonMsg = ["```json\n", JSON.stringify(msg, null, " "), "\n```"].join(
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
return new Response(jsonMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handle(
|
||||||
|
req: NextRequest,
|
||||||
|
{ params }: { params: { path: string[] } },
|
||||||
|
) {
|
||||||
|
console.log("[OpenAI Route] params ", params);
|
||||||
|
|
||||||
|
const authResult = auth(req);
|
||||||
|
if (authResult.error) {
|
||||||
|
return NextResponse.json(authResult, {
|
||||||
|
status: 401,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const api = await requestOpenai(req);
|
||||||
|
|
||||||
|
const contentType = api.headers.get("Content-Type") ?? "";
|
||||||
|
|
||||||
|
// streaming response
|
||||||
|
if (contentType.includes("stream")) {
|
||||||
|
const stream = await createStream(api);
|
||||||
|
const res = new Response(stream);
|
||||||
|
res.headers.set("Content-Type", contentType);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to parse error msg
|
||||||
|
try {
|
||||||
|
const mayBeErrorBody = await api.json();
|
||||||
|
if (mayBeErrorBody.error) {
|
||||||
|
console.error("[OpenAI Response] ", mayBeErrorBody);
|
||||||
|
return formatResponse(mayBeErrorBody);
|
||||||
|
} else {
|
||||||
|
const res = new Response(JSON.stringify(mayBeErrorBody));
|
||||||
|
res.headers.set("Content-Type", "application/json");
|
||||||
|
res.headers.set("Cache-Control", "no-cache");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[OpenAI Parse] ", e);
|
||||||
|
return formatResponse({
|
||||||
|
msg: "invalid response from openai server",
|
||||||
|
error: e,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[OpenAI] ", e);
|
||||||
|
return formatResponse(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GET = handle;
|
||||||
|
export const POST = handle;
|
||||||
|
|
||||||
|
export const runtime = "edge";
|
@ -1,33 +0,0 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
import { requestOpenai } from "../common";
|
|
||||||
|
|
||||||
async function makeRequest(req: NextRequest) {
|
|
||||||
try {
|
|
||||||
const api = await requestOpenai(req);
|
|
||||||
const res = new NextResponse(api.body);
|
|
||||||
res.headers.set("Content-Type", "application/json");
|
|
||||||
res.headers.set("Cache-Control", "no-cache");
|
|
||||||
return res;
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[OpenAI] ", req.body, e);
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: true,
|
|
||||||
msg: JSON.stringify(e),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 500,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
|
||||||
return makeRequest(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function GET(req: NextRequest) {
|
|
||||||
return makeRequest(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const runtime = "edge";
|
|
@ -1,72 +0,0 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
import { getServerSideConfig } from "./app/config/server";
|
|
||||||
import md5 from "spark-md5";
|
|
||||||
|
|
||||||
export const config = {
|
|
||||||
matcher: ["/api/openai", "/api/chat-stream"],
|
|
||||||
};
|
|
||||||
|
|
||||||
const serverConfig = getServerSideConfig();
|
|
||||||
|
|
||||||
function getIP(req: NextRequest) {
|
|
||||||
let ip = req.ip ?? req.headers.get("x-real-ip");
|
|
||||||
const forwardedFor = req.headers.get("x-forwarded-for");
|
|
||||||
|
|
||||||
if (!ip && forwardedFor) {
|
|
||||||
ip = forwardedFor.split(",").at(0) ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return ip;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function middleware(req: NextRequest) {
|
|
||||||
const accessCode = req.headers.get("access-code");
|
|
||||||
const token = req.headers.get("token");
|
|
||||||
const hashedCode = md5.hash(accessCode ?? "").trim();
|
|
||||||
|
|
||||||
console.log("[Auth] allowed hashed codes: ", [...serverConfig.codes]);
|
|
||||||
console.log("[Auth] got access code:", accessCode);
|
|
||||||
console.log("[Auth] hashed access code:", hashedCode);
|
|
||||||
console.log("[User IP] ", getIP(req));
|
|
||||||
console.log("[Time] ", new Date().toLocaleString());
|
|
||||||
|
|
||||||
if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !token) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: true,
|
|
||||||
needAccessCode: true,
|
|
||||||
msg: "Please go settings page and fill your access code.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 401,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// inject api key
|
|
||||||
if (!token) {
|
|
||||||
const apiKey = serverConfig.apiKey;
|
|
||||||
if (apiKey) {
|
|
||||||
console.log("[Auth] set system token");
|
|
||||||
req.headers.set("token", apiKey);
|
|
||||||
} else {
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: true,
|
|
||||||
msg: "Empty Api Key",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 401,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log("[Auth] set user token");
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.next({
|
|
||||||
request: {
|
|
||||||
headers: req.headers,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
|
||||||
|
|
||||||
const nextConfig = {
|
|
||||||
experimental: {
|
|
||||||
appDir: true,
|
|
||||||
},
|
|
||||||
webpack(config) {
|
|
||||||
config.module.rules.push({
|
|
||||||
test: /\.svg$/,
|
|
||||||
use: ["@svgr/webpack"],
|
|
||||||
});
|
|
||||||
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
output: "standalone",
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = nextConfig;
|
|
@ -0,0 +1,32 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
|
||||||
|
const nextConfig = {
|
||||||
|
experimental: {
|
||||||
|
appDir: true,
|
||||||
|
},
|
||||||
|
async rewrites() {
|
||||||
|
const ret = [];
|
||||||
|
|
||||||
|
const apiUrl = process.env.API_URL;
|
||||||
|
if (apiUrl) {
|
||||||
|
console.log("[Next] using api url ", apiUrl);
|
||||||
|
ret.push({
|
||||||
|
source: "/api/:path*",
|
||||||
|
destination: `${apiUrl}/:path*`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
webpack(config) {
|
||||||
|
config.module.rules.push({
|
||||||
|
test: /\.svg$/,
|
||||||
|
use: ["@svgr/webpack"],
|
||||||
|
});
|
||||||
|
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
output: "standalone",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
Loading…
Reference in new issue