mirror of
https://github.com/PawanOsman/ChatGPT.git
synced 2026-07-18 08:05:57 +02:00
update
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Pawan Osman
|
||||
Copyright (c) 2023 Pawan Osman
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// Server configuration
|
||||
export const SERVER_PORT = 3000; // Server port
|
||||
export const DEBUG = false; // Debug mode
|
||||
|
||||
// Prompt Moderation before sending to OpenAI
|
||||
export const MODERATION = true; // Moderation mode
|
||||
|
||||
// Rate limit
|
||||
export const PRIOD = 15 * 1000; // 15 seconds
|
||||
export const RATE_LIMIT = 50; // 50 requests per 15 seconds
|
||||
|
||||
// Whitelisted IPs
|
||||
export const WHITELISTED_IPS = [
|
||||
// "127.0.0.1"
|
||||
];
|
||||
|
||||
// OpenAI API Keys
|
||||
export let OPENAI_KEYS = [
|
||||
"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
];
|
||||
@@ -0,0 +1,45 @@
|
||||
import { OPENAI_KEYS } from "./config.js";
|
||||
|
||||
async function* chunksToLines(chunksAsync) {
|
||||
let previous = "";
|
||||
for await (const chunk of chunksAsync) {
|
||||
const bufferChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
||||
previous += bufferChunk;
|
||||
let eolIndex;
|
||||
while ((eolIndex = previous.indexOf("\n")) >= 0) {
|
||||
// line includes the EOL
|
||||
const line = previous.slice(0, eolIndex + 1).trimEnd();
|
||||
if (line === "data: [DONE]") break;
|
||||
if (line.startsWith("data: ")) yield line;
|
||||
previous = previous.slice(eolIndex + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function* linesToMessages(linesAsync) {
|
||||
for await (const line of linesAsync) {
|
||||
const message = line.substring("data :".length);
|
||||
|
||||
yield message;
|
||||
}
|
||||
}
|
||||
|
||||
async function* streamCompletion(data) {
|
||||
yield* linesToMessages(chunksToLines(data));
|
||||
}
|
||||
|
||||
function generateId() {
|
||||
const chars =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
let id = "org-";
|
||||
for (let i = 0; i < 24; i++) {
|
||||
id += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
function getOpenAIKey() {
|
||||
return OPENAI_KEYS[Math.floor(Math.random() * OPENAI_KEYS.length)];
|
||||
}
|
||||
|
||||
export { generateId, getOpenAIKey, streamCompletion }
|
||||
@@ -0,0 +1,33 @@
|
||||
import express, { json, urlencoded } from 'express';
|
||||
import { completions, chatCompletions } from './routes.js';
|
||||
import { corsMiddleware, rateLimitMiddleware } from './middlewares.js';
|
||||
import { SERVER_PORT } from './config.js';
|
||||
|
||||
let app = express();
|
||||
|
||||
process.on("uncaughtException", function (err) {
|
||||
if (DEBUG) console.error(`Caught exception: ${err}`);
|
||||
});
|
||||
|
||||
// Middlewares
|
||||
app.use(corsMiddleware);
|
||||
app.use(rateLimitMiddleware);
|
||||
app.use(json());
|
||||
app.use(urlencoded({ extended: true }));
|
||||
|
||||
// Register routes
|
||||
app.all("/", async function (req, res) {
|
||||
res.set("Content-Type", "application/json");
|
||||
return res.status(200).send({
|
||||
status: true,
|
||||
github: "https://github.com/PawanOsman/ChatGPT",
|
||||
discord: "https://discord.pawan.krd"
|
||||
});
|
||||
});
|
||||
app.post("/api/completions", completions);
|
||||
app.post("/api/chat/completions", chatCompletions);
|
||||
|
||||
// Start server
|
||||
app.listen(SERVER_PORT, () => {
|
||||
console.log(`Listening on ${SERVER_PORT} ...`);
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { RATE_LIMIT, PRIOD, WHITELISTED_IPS } from "./config.js";
|
||||
|
||||
const rateLimit = new Map();
|
||||
|
||||
function corsMiddleware(req, res, next) {
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
res.header("Access-Control-Allow-Headers", "*");
|
||||
res.header("Access-Control-Allow-Methods", "*");
|
||||
next();
|
||||
};
|
||||
|
||||
async function rateLimitMiddleware(req, res, next) {
|
||||
let ip = req.headers["CF-Connecting-IP"] ?? req.headers["cf-connecting-ip"] ?? req.headers["X-Forwarded-For"] ?? req.headers["x-forwarded-for"] ?? req.src.ip;
|
||||
if (WHITELISTED_IPS.includes(ip)) return next();
|
||||
if (!rateLimit.has(ip)) {
|
||||
rateLimit.set(ip, {
|
||||
requests: 1,
|
||||
lastRequestTime: Date.now()
|
||||
});
|
||||
} else {
|
||||
const currentTime = Date.now();
|
||||
const timeSinceLastRequest = currentTime - rateLimit.get(ip).lastRequestTime;
|
||||
if (timeSinceLastRequest > PRIOD) {
|
||||
rateLimit.set(ip, {
|
||||
requests: 1,
|
||||
lastRequestTime: currentTime
|
||||
});
|
||||
} else {
|
||||
let updatedCount = rateLimit.get(ip).requests + 1;
|
||||
if (updatedCount > RATE_LIMIT) {
|
||||
return res.status(429).send({
|
||||
status: false,
|
||||
error: "Too many requests, please try again later"
|
||||
});
|
||||
}
|
||||
rateLimit.set(ip, {
|
||||
requests: updatedCount,
|
||||
lastRequestTime: rateLimit.get(ip).lastRequestTime
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
export { corsMiddleware, rateLimitMiddleware }
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "chatgpt",
|
||||
"version": "1.0.0",
|
||||
"description": "## If you have any questions or need assistance, please join [[Discord](https://discord.pawan.krd)]",
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/PawanOsman/ChatGPT.git"
|
||||
},
|
||||
"author": "PawanOsman <contact@pawan.krd> (https://pawan.krd/)",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/PawanOsman/ChatGPT/issues"
|
||||
},
|
||||
"homepage": "https://github.com/PawanOsman/ChatGPT#readme"
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
import axios from "axios";
|
||||
import { Configuration, OpenAIApi } from "openai";
|
||||
import { streamCompletion, generateId, getOpenAIKey } from "./functions.js"
|
||||
import { DEBUG, MODERATION } from "./config.js";
|
||||
|
||||
async function completions(req, res) {
|
||||
let orgId = generateId();
|
||||
let key = getOpenAIKey();
|
||||
|
||||
if (!req.body.prompt) {
|
||||
res.set("Content-Type", "application/json");
|
||||
return res.status(400).send({
|
||||
status: false,
|
||||
error: "No prompt provided"
|
||||
});
|
||||
}
|
||||
|
||||
if (DEBUG) console.log(`[Text] [${req.user.data.id}] [${req.user.data.name}] [MAX-TOKENS:${req.body.max_tokens ?? "unset"}] ${req.body.prompt}`);
|
||||
|
||||
if (MODERATION) {
|
||||
try {
|
||||
let openAi = new OpenAIApi(new Configuration({ apiKey: key.apikey }));
|
||||
let response = await openAi.createModeration({
|
||||
input: req.body.prompt,
|
||||
});
|
||||
|
||||
if (response.data.results[0].flagged) {
|
||||
res.set("Content-Type", "application/json");
|
||||
return res.status(400).send({
|
||||
status: false,
|
||||
error: "Your prompt contains content that is not allowed",
|
||||
reason: response.data.results[0].reason,
|
||||
contact: "https://discord.pawan.krd"
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (req.body.stream) {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`https://api.openai.com/v1/completions`, req.body,
|
||||
{
|
||||
responseType: "stream",
|
||||
headers: {
|
||||
Accept: "text/event-stream",
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${key.apikey}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
res.setHeader("content-type", "text/event-stream");
|
||||
|
||||
for await (const message of streamCompletion(response.data)) {
|
||||
try {
|
||||
const parsed = JSON.parse(message);
|
||||
delete parsed.id;
|
||||
delete parsed.created;
|
||||
res.write(`data: ${JSON.stringify(parsed)}\n\n`);
|
||||
} catch (error) {
|
||||
if (DEBUG) console.error("Could not JSON parse stream message", message, error);
|
||||
}
|
||||
}
|
||||
|
||||
res.write(`data: [DONE]`);
|
||||
res.end();
|
||||
} catch (error) {
|
||||
try {
|
||||
if (error.response && error.response.data) {
|
||||
let errorResponseStr = "";
|
||||
|
||||
for await (const message of error.response.data) {
|
||||
errorResponseStr += message;
|
||||
}
|
||||
|
||||
errorResponseStr = errorResponseStr.replace(/org-[a-zA-Z0-9]+/, orgId);
|
||||
|
||||
const errorResponseJson = JSON.parse(errorResponseStr);
|
||||
return res.status(error.response.status).send(errorResponseJson);
|
||||
} else {
|
||||
if (DEBUG) console.error("Could not JSON parse stream message", error);
|
||||
return res.status(500).send({
|
||||
status: false,
|
||||
error: "something went wrong!"
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
return res.status(500).send({
|
||||
status: false,
|
||||
error: "something went wrong!"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`https://api.openai.com/v1/completions`, req.body,
|
||||
{
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${key.apikey}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
delete response.data.id;
|
||||
delete response.data.created;
|
||||
|
||||
return res.status(200).send(response.data);
|
||||
} catch (error) {
|
||||
try {
|
||||
error.response.data.error.message = error.response.data.error.message.replace(/org-[a-zA-Z0-9]+/, orgId);
|
||||
return res.status(error.response.status).send(error.response.data);
|
||||
}
|
||||
catch (e) {
|
||||
if (DEBUG) console.log(e);
|
||||
return res.status(500).send({
|
||||
status: false,
|
||||
error: "something went wrong!"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function chatCompletions(req, res) {
|
||||
let orgId = generateId();
|
||||
let key = getOpenAIKey(true);
|
||||
|
||||
if (DEBUG) console.log(`[CHAT] [${req.user.data.id}] [${req.user.data.name}] [MAX-TOKENS:${req.body.max_tokens ?? "unset"}] ${prompt}`);
|
||||
|
||||
if (MODERATION) {
|
||||
try {
|
||||
let prompt = "";
|
||||
try {
|
||||
req.body.messages.forEach(element => {
|
||||
prompt += element.content;
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
return res.status(400).send({
|
||||
status: false,
|
||||
error: "messages is required! and must be an array of objects with content and author properties"
|
||||
});
|
||||
}
|
||||
|
||||
let openAi = new OpenAIApi(new Configuration({ apiKey: key.apikey }));
|
||||
let response = await openAi.createModeration({
|
||||
input: prompt,
|
||||
});
|
||||
|
||||
if (response.data.results[0].flagged) {
|
||||
res.set("Content-Type", "application/json");
|
||||
return res.status(400).send({
|
||||
status: false,
|
||||
error: "Your prompt contains content that is not allowed",
|
||||
reason: response.data.results[0].reason,
|
||||
support: "https://discord.pawan.krd"
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (req.body.stream) {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`https://api.openai.com/v1/chat/completions`, req.body,
|
||||
{
|
||||
responseType: "stream",
|
||||
headers: {
|
||||
Accept: "text/event-stream",
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${key.apikey}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
res.setHeader("content-type", "text/event-stream");
|
||||
|
||||
for await (const message of streamCompletion(response.data)) {
|
||||
try {
|
||||
const parsed = JSON.parse(message);
|
||||
delete parsed.id;
|
||||
delete parsed.created;
|
||||
const { content } = parsed.choices[0].delta;
|
||||
if (content) {
|
||||
res.write(`data: ${JSON.stringify(parsed)}\n\n`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (DEBUG) console.error("Could not JSON parse stream message", message, error);
|
||||
}
|
||||
}
|
||||
|
||||
res.write(`data: [DONE]`);
|
||||
res.end();
|
||||
} catch (error) {
|
||||
try {
|
||||
if (error.response && error.response.data) {
|
||||
let errorResponseStr = "";
|
||||
|
||||
for await (const message of error.response.data) {
|
||||
errorResponseStr += message;
|
||||
}
|
||||
|
||||
errorResponseStr = errorResponseStr.replace(/org-[a-zA-Z0-9]+/, orgId);
|
||||
|
||||
const errorResponseJson = JSON.parse(errorResponseStr);
|
||||
return res.status(error.response.status).send(errorResponseJson);
|
||||
} else {
|
||||
if (DEBUG) console.error("Could not JSON parse stream message", error);
|
||||
return res.status(500).send({
|
||||
status: false,
|
||||
error: "something went wrong!"
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
if (DEBUG) console.log(e);
|
||||
return res.status(500).send({
|
||||
status: false,
|
||||
error: "something went wrong!"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`https://api.openai.com/v1/chat/completions`, req.body,
|
||||
{
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${key.apikey}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
delete response.data.id;
|
||||
delete response.data.created;
|
||||
|
||||
return res.status(200).send(response.data);
|
||||
} catch (error) {
|
||||
try {
|
||||
error.response.data.error.message = error.response.data.error.message.replace(/org-[a-zA-Z0-9]+/, orgId);
|
||||
return res.status(error.response.status).send(error.response.data);
|
||||
}
|
||||
catch (e) {
|
||||
if (DEBUG) console.log(e);
|
||||
return res.status(500).send({
|
||||
status: false,
|
||||
error: "something went wrong!"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { completions, chatCompletions };
|
||||
Reference in New Issue
Block a user