Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
Bensigo committed Oct 10, 2023
1 parent 2b474ff commit e8fce9d
Show file tree
Hide file tree
Showing 8 changed files with 446 additions and 0 deletions.
121 changes: 121 additions & 0 deletions src/pages/api/v2/chat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { type DailyTherapySession, PrismaClient } from "@prisma/client";
import { type NextApiResponse, type NextApiRequest } from "next";
import { Configuration, OpenAIApi } from "openai";
import { env } from "@/env.mjs";
import { authOptions, getServerAuthSession } from "@/server/auth";
import { type IncomingMessage } from "http";



const config = new Configuration({
apiKey: env.OPEN_AI,
});
const openai = new OpenAIApi(config)

let content = ''

export default async function POST(req: NextApiRequest, res: NextApiResponse) {

// handle auth
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const session = await getServerAuthSession({ req, res, authOptions });
if(!session){
return res.status(401).send("Unauthorized")
}


const prisma = new PrismaClient();

const { sessionId, messages } = JSON.parse(req.body as string) as { messages: any[], sessionId: string };

const dailySession = await prisma.dailyTherapySession.findFirst({
where: {
isActive: true,
id: sessionId
},
include: {
messages: true,
},
});

if (!dailySession) {
return res.status(400).send("Invalid Therapy Session")

}

// const isValidChatSession = isValidSession(dailySession)
// if (!isValidChatSession){
// res.status(400).send("Expired session")
// return;
// }

const response = await openai.createChatCompletion({
stream: true,
model: "gpt-3.5-turbo",
messages,
temperature: 0.6,
}, { responseType: 'stream'});


const stream = response.data as unknown as IncomingMessage;

stream.on('data', (chunk: Buffer) => processChunk(chunk, res));

stream.on('error', (err: Error) => {
console.log(err);
res.status(500).send("Internal Server Error");
});

stream.on('end', () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const msg = messages[messages.length - 1].content
const saveMessagesToDatabase = async () => await prisma.message.createMany({
data: [
{
sender: "user",
sessionId,
content: msg,
},
{
sender: "assistant",
sessionId,
content,
},
],
});
void saveMessagesToDatabase();
return;
})

return res.status(200);
}

function processChunk(chunk: Buffer, res: NextApiResponse) {
const payloads = chunk.toString().split("\n\n");
for (const payload of payloads) {
if (payload.includes('[DONE]')) return;
if (payload.startsWith("data:")) {
const data = JSON.parse(payload.replace("data: ", ""));
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const chunkContent: string | undefined = data.choices[0].delta?.content;
if (chunkContent) {
content += chunkContent;
res.write(chunkContent);
}
}
}
}

function isValidSession(dailySession: DailyTherapySession) {
const startTime = new Date(dailySession.start);
const currentTime = new Date();

const timeDifference = Math.abs(
startTime.getTime() - currentTime.getTime()
);
const minutesDifference = Math.ceil(timeDifference / (1000 * 60));

return minutesDifference < 30
}
18 changes: 18 additions & 0 deletions src/server/api/utils/level.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

export const getLevel = (score: number) => {
if (score >= 0 && score <= 200) {
return 'Eden';
} else if (score >= 201 && score <= 1000) {
return 'Alpha';
} else if (score >= 1001 && score <= 6000) {
return 'Omega';
} else if (score >= 6001 && score <= 15000) {
return 'Titan';
} else if (score >= 15001 && score <= 40000) {
return 'Zenith';
} else if (score >= 40001) {
return 'GodMode';
} else {
return;
}
};
114 changes: 114 additions & 0 deletions src/server/api/utils/openai.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { env } from "@/env.mjs";
import { OpenAIStream, StreamingTextResponse } from 'ai'
import { type ChatCompletionRequestMessage, Configuration, OpenAIApi } from "openai";

const config = new Configuration({
apiKey: env.OPEN_AI,
});

const ai = new OpenAIApi(config);

export function extractQuestionsFromString(text: string): string[] {
const lines = text.split('\n');
const questions: string[] = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i]?.trim();
// find a better way because text can return without count in the future
if (line && /^[1-5]\./.test(line)) {
const questionLine = line.slice(line.indexOf('.') + 1);
const question = questionLine.trim();

questions.push(question);
}
}
return questions;
}

// 'user' | 'system' |'assistant' only roles
export async function chatCompletionAi(msgs: {role: string, content: string }[], temperature = 0.2){
try {
const resp = await ai.createChatCompletion({
model: "gpt-3.5-turbo",
messages: msgs as ChatCompletionRequestMessage[],
temperature
}, { timeout: 10000 })
return resp.data.choices[0]?.message?.content;
}catch (err: unknown){
console.log(err);
}
}

export async function queryOpenAi(prompt: string, temperature = 0.7) {
try {

// note: sometimes you get network error due to overload
const resp = await ai.createChatCompletion(
{
model: "gpt-3.5-turbo",
messages: [{ role: 'user', content: prompt }],
temperature,
// n: 1
}

);
const data = resp.data.choices[0]?.message?.content;

return data;
} catch (err: unknown) {

console.log({ err })

}
}


const que = `You are playing the role of a therapist.
I will provide you with a question and answer,
and I need you to perform sentiment analysis on the user's mood based on their responses.
Your task is to evaluate the sentiment for the answer based on the corresponding question and return a sentiment analysis score ranging from 0 to 1
`


export async function getSentimentAnalysis(data: {question: string, answer: string }[]): Promise<number>{
const resp =await Promise.all(data.map((item) => queryOpenAi(`${que} \n que: ${item.question} \n ans: ${item.answer}`,0)))
const scores = resp.map((feedback) => {
const sentiment = feedback?.split('\n')[0]
const score = sentiment?.split(':')[1];
return parseFloat(score || '');
})
const total = scores.reduce((sum, score) => sum + score, 0);
const average = total / scores.length;
return average;
}

export function extractSentimentAnalysis(rawStr: string){
const rawScores = rawStr.split('\n')[0] as string;

const scores = JSON.parse(rawScores) as number[];

if (scores.length > 0){
const averageMood = (scores.reduce((curr, acc) => curr + acc)) / scores.length;
return averageMood.toFixed(2);
}
return 0;
}


export function extractArrayFromString(text: string): string[] {


const lines = text.split('\n');
const list: string[] = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i]?.trim();
// find a better way because text can return without count in the future
if (line && /^[1-9]\./.test(line)) {
const questionLine = line.slice(line.indexOf('.') + 1);
const question = questionLine.trim();

list.push(question);
}
}
return list;
}
33 changes: 33 additions & 0 deletions src/server/api/utils/sendgrid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { env } from "@/env.mjs";
import sendgrid from "@sendgrid/mail"


export type SendGridInput = {
to: string | string[];
from: { name?: string; email: string };
subject: string;
templateId: string;
data?: Record<string, any>
}

if (env && env.SENDGRID_API_KEY){
sendgrid.setApiKey(env.SENDGRID_API_KEY);
}



export async function mailWithSendgrid (params: SendGridInput){
try {
if (process.env.VERCEL_ENV === 'prview' || process.env.VERCEL_ENV === 'development')return;

await sendgrid.send({
to: params.to,
from: params.from,
subject: params.subject,
templateId: params.templateId
});
}catch (err: unknown){
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
console.log(`error from send grid: ${err}`)
}
}
50 changes: 50 additions & 0 deletions src/server/crons/expiredQuestToInActive.cron.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { PrismaClient } from "@prisma/client";

// trigger job everyday at 3:00 am
export async function expiredQuestToInActive() {
try {
const prisma = new PrismaClient();
const batchSize = 20; // Number of quests to update in each batch
let totalUpdated = 0;
const quests = await prisma.quest.findMany({
where: {
endDate: {
lte: new Date(),
},
isActive: true
},
});

if (quests.length === 0) {
console.log("No quests to update.");
await prisma.$disconnect();
return;
}

console.log(`Total quests to update: ${quests.length}`);

while (totalUpdated < quests.length) {
const questToUpdate = quests.slice(
totalUpdated,
totalUpdated + batchSize
);
const idsToUpdate = questToUpdate.map((quest) => quest.id);
await prisma.quest.updateMany({
where: {
id: {
in: idsToUpdate,
},
},
data: {
isActive: false,
},
});

totalUpdated += questToUpdate.length;
}
console.log(`Total quests updated: ${totalUpdated} of ${quests.length}`);
await prisma.$disconnect();
} catch (err) {
console.log({ err });
}
}
Loading

0 comments on commit e8fce9d

Please sign in to comment.