This commit is contained in:
Pawan Osman
2023-03-24 07:19:18 +03:00
parent 98de76fb58
commit 85bf57fcac
7 changed files with 438 additions and 1 deletions
+1 -1
View File
@@ -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
+23
View File
@@ -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",
];
+45
View File
@@ -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 }
+33
View File
@@ -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} ...`);
});
+46
View File
@@ -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 }
+20
View File
@@ -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"
}
+270
View File
@@ -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 };