initial commit

This commit is contained in:
Omer Sabic 2023-06-06 07:06:06 +02:00
commit 8a8eb4c654
6 changed files with 1738 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.env
node_modules/

90
api.js Normal file
View File

@ -0,0 +1,90 @@
"use strict"
const crypto = require('crypto');
const { v4: uuidv4 } = require('uuid');
const UserAgent = require('fake-useragent');
const {HttpsProxyAgent} = require('https-proxy-agent');
class ChatCompletion {
static async create(messages) {
const url = "https://chat.getgpt.world/api/chat/stream";
// const url = "https://api.ipify.org";
const headers = {
"Content-Type": "application/json",
"Referer": "https://chat.getgpt.world/",
'User-Agent': UserAgent(),
"agent": new HttpsProxyAgent("https://qmvnozfp:jgr74lg0t6hn@185.199.229.156:5074")
};
const data = JSON.stringify({
"messages": messages,
"frequency_penalty": 0,
"max_tokens": 5000,
"model": "gpt-3.5-turbo",
"presence_penalty": 0,
"temperature": 1,
"top_p": 1,
// "stream": true,
"uuid": uuidv4()
});
const signature = ChatCompletion.encrypt(data);
const response = await fetch(url, {
method: 'post',
headers: headers,
body: JSON.stringify({ "signature": signature }),
});
const reader = response.body.getReader();
let content = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = new TextDecoder().decode(value);
content += chunk;
}
const chunks = content.split('data: ');
let final = "";
for (let i = 1; i < chunks.length; i++) {
const data = chunks[i];
if (!data || data.includes("[DONE]")) {
return final;
}
const dataJson = JSON.parse(data);
const content = dataJson.choices[0].delta.content;
if (content) {
final += content
}
}
}
static randomToken(e) {
const token = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const n = token.length;
let result = "";
for (let i = 0; i < e; i++) {
const randomIndex = Math.floor(Math.random() * n);
result += token.charAt(randomIndex);
}
return result;
}
static encrypt(e) {
const t = ChatCompletion.randomToken(16);
const n = ChatCompletion.randomToken(16);
const r = Buffer.from(e, 'utf-8');
const cipher = crypto.createCipheriv('aes-128-cbc', t, n);
let ciphertext = cipher.update(r, 'utf-8', 'hex');
ciphertext += cipher.final('hex');
return ciphertext + t + n;
}
static __padData(data) {
const block_size = 16;
const padding_size = block_size - (data.length % block_size);
const padding = Buffer.alloc(padding_size, padding_size);
return Buffer.concat([data, padding]);
}
}
module.exports = ChatCompletion

4
captain-definition Normal file
View File

@ -0,0 +1,4 @@
{
"schemaVersion": 2,
"templateId": "node/18.6.0"
}

135
main.js Normal file
View File

@ -0,0 +1,135 @@
require('dotenv').config()
const express = require("express");
const fileUpload = require("express-fileupload");
const pdfParse = require("pdf-parse");
const cors = require('cors');
const cheerio = require('cheerio');
const chat = require('./api');
const PocketBase = require('pocketbase/cjs');
const { Configuration, OpenAIApi } = require("openai");
const configuration = new Configuration({
apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);
const app = express();
const pb = new PocketBase("https://pocketbase.omersabic.com")
app.use(cors());
app.use(fileUpload());
const supportedAds = ['www.azubiyo.de']
app.post("/create-letter", async (req, res) => {
if (!req.files && !req.files.pdfFile || !req.body.job || !req.body.extra || !req.body.language || !req.body.auth) {
res.status(400);
res.end();
}
// Check user auth token and tokens quantity
pb.authStore.save(req.body.auth, null);
await pb.collection("users").authRefresh()
if (pb.authStore.model.tokens < 1) {
return res.send({ error: "not enough tokens", content: "You don't have enough tokens to run this service." })
}
await pb.collection('users').update(pb.authStore.model.id, {
tokens: pb.authStore.model.tokens - 1
});
let parsedURL = new URL(req.body.job);
if (!supportedAds.includes(parsedURL.host)) throw Error(`We don't support ${parsedURL.host} job postings for now.`);
let parsedCV = await parsePDF(req.files.pdfFile);
let jobAd = await scrapeJobAd(req.body.job);
let prompt = createPrompt(parsedCV, jobAd, req.body.extra, parsedURL.host);
let finishedLetter = await writeLetter(prompt, req.body.language)
await pb.collection('letters').create({
user: pb.authStore.model.id,
content: finishedLetter
})
res.send({ content: finishedLetter });
});
app.get("/test", async (req, res) => {
let result = await chat.create([
{
"content": "You are ChatGPT, a large language model trained by OpenAI.\nCarefully heed the user's instructions. \nRespond using Markdown.",
"role": "system"
},
{ "role": "user", "content": "What is the sum of 2+2" }
]);
res.send(result);
})
async function writeLetter(prompt, language) {
// try {
// // let result = await chat.create(prompt);
// let result = "test result so I don't waste money on openai"
// return result;
// }
// catch (e) {
// console.log(e)
// }
return (await openai.createChatCompletion({
model: "gpt-3.5-turbo",
messages: [
{ "role": "system", "content": `You are a career advisor, you help people get jobs by writing their cover letters.\nCarefully heed the provided data. \nRespond only with the cover letter and no additional content. Make the cover letters appeal towards the job description. Write in ${language}`},
{ "role": "user", "content": prompt}
],
temperature: 1,
max_tokens: 1500
})).data.choices[0].message.content;
}
async function scrapeJobAd(url) {
let parsedURL = new URL(url);
if (parsedURL.host !== "www.azubiyo.de") throw Error('We only support azubiyo.de job postings for now.');
let data = await (await fetch(url)).text()
const $ = cheerio.load(data);
$('.stellenanzeige-content style').remove()
return $('.stellenanzeige-content').text()
}
async function parsePDF(file) {
return (await pdfParse(file)).text
}
function createPrompt(cv, job, extra, jobAdURL) {
// return [
// { "role": "system", "content": "You are a career advisor, you help people get jobs by writing their cover letters.\nCarefully heed the provided data. \nRespond only with the cover letter and no additional content."},
// // { "role": "user", "content": `CV: ${cv}` },
// // { "role": "user", "content": `Job Ad (found on ${jobAdURL}: ${job}` },
// // { "role": "user", "content": `Extra information: ${extra}` },
// { "role": "user", "content": "Please generate a compelling cover letter that highlights the person's skills, experiences, and suitability for the job position. Make sure to mention specific qualifications and align the cover letter with the requirements mentioned in the job ad. Keep the letter to 1 page at the longest. Write a unique opening paragraph. Don't copy text directly from the job ad.`" }
// ]
return `Generate a cover letter for a job application based on the following information:
CV (parsed from PDF): ${cv}
Job (found on ${jobAdURL}): ${job}
Extra infromation: ${extra}
Please generate a compelling cover letter that highlights the person's skills, experiences, and suitability for the job position. Make sure to mention specific qualifications and align the cover letter with the requirements mentioned in the job ad. Keep the letter to 1 page at the longest. Write a unique opening paragraph. Don't copy text directly from the job ad.`
}
app.listen(process.env.PORT);

1476
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

30
package.json Normal file
View File

@ -0,0 +1,30 @@
{
"name": "jobai-backend",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon main.js",
"start": "node main.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^1.4.0",
"cheerio": "^1.0.0-rc.12",
"cors": "^2.8.5",
"dotenv": "^16.1.4",
"express": "^4.18.2",
"express-fileupload": "^1.4.0",
"fake-useragent": "^1.0.1",
"https-proxy-agent": "^7.0.0",
"node-fetch": "^3.3.1",
"nodemon": "^2.0.22",
"openai": "^3.2.1",
"pdf-parse": "^1.1.1",
"pocketbase": "^0.15.1",
"uuid": "^9.0.0"
}
}