-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
446 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}`) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }); | ||
} | ||
} |
Oops, something went wrong.