diff --git a/.env.example b/.env.example index 04f13da..c97daf5 100644 --- a/.env.example +++ b/.env.example @@ -39,4 +39,7 @@ S3_BUCKET_NAME=s3 # Github OAuth Provider GITHUB_ID=EXAMPLE_GITHUB_ID -GITHUB_SECRET=EXAMPLE_GITHUB_SECRET \ No newline at end of file +GITHUB_SECRET=EXAMPLE_GITHUB_SECRET + +# OpenAI +OPENAI_API_KEY=EXAMPLE_OPENAI_API_KEY \ No newline at end of file diff --git a/package.json b/package.json index 4c31170..292326b 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "next": "^13.4.13", "next-auth": "^4.22.4", "npm-run-all": "^4.1.5", + "openai": "^4.14.1", "react": "18.2.0", "react-codemirror-merge": "^4.21.15", "react-dom": "18.2.0", diff --git a/prisma/postgres/schema.prisma b/prisma/postgres/schema.prisma index cbedef3..2318b80 100644 --- a/prisma/postgres/schema.prisma +++ b/prisma/postgres/schema.prisma @@ -102,21 +102,22 @@ model CodeSessionUserAuth { } model User { - id String @id @default(cuid()) - name String? - email String? @unique - emailVerified DateTime? - image String? - password String? - accounts Account[] - sessions Session[] - codeSpace CodeSpace[] - attempts QuestionAttempt[] - role Role @default(USER) - codeSessionAuthRecv CodeSessionUserAuth[] @relation(name: "auth_receiver") - matchRequest MatchRequest? - joinRequest JoinRequest[] - sessionMessage SessionMessage[] + id String @id @default(cuid()) + name String? + email String? @unique + emailVerified DateTime? + image String? + password String? + accounts Account[] + sessions Session[] + codeSpace CodeSpace[] + attempts QuestionAttempt[] + role Role @default(USER) + codeSessionAuthRecv CodeSessionUserAuth[] @relation(name: "auth_receiver") + matchRequest MatchRequest? + joinRequest JoinRequest[] + sessionUserAndUserMessages SessionUserAndUserMessage[] + sessionUserAndAIMessages SessionUserAndAIMessage[] } enum Difficulty { @@ -177,7 +178,7 @@ model QuestionAttempt { } -model SessionMessage { +model SessionUserAndUserMessage { id String @id @default(cuid()) sessionId String userId String @@ -188,3 +189,15 @@ model SessionMessage { @@index([sessionId]) } + +model SessionUserAndAIMessage { + id String @id @default(cuid()) + sessionId String + userId String + message String + role String + createdAt DateTime @default(now()) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@index([sessionId]) +} diff --git a/src/components/AIBox.tsx b/src/components/AIBox.tsx new file mode 100644 index 0000000..f6213b3 --- /dev/null +++ b/src/components/AIBox.tsx @@ -0,0 +1,67 @@ +import useAIComm from "~/hooks/useAIComm"; + +const AIBox = ({ + sessionId, + userId, + userName, + className, +}: { + sessionId: string; + userId: string; + userName: string; + className?: string | undefined; +}) => { + const [ + allSessionMessages, + sendMessage, + onTyping, + currentMessage, + isAIResponding, + ] = useAIComm(sessionId, userId); + + return ( +
+
+ {allSessionMessages?.map((message) => { + return ( +
+
+ {message.role === "user" ? userName : "GPT-3.5"} +
+

{message.message}

+
+ ); + })} +
+
+
+ {!isAIResponding && ( + onTyping(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && sendMessage()} + /> + )} + {isAIResponding && ( + + )} +
+
+
+ ); +}; + +export default AIBox; diff --git a/src/env.mjs b/src/env.mjs index 3a2fc2b..464d1e7 100644 --- a/src/env.mjs +++ b/src/env.mjs @@ -29,6 +29,7 @@ export const env = createEnv({ // Add `.min(1) on ID and SECRET if you want to make sure they're not empty GITHUB_ID: z.string().min(1), GITHUB_SECRET: z.string().min(1), + OPENAI_API_KEY: z.string().min(1), }, /** @@ -59,6 +60,7 @@ export const env = createEnv({ S3_BUCKET_NAME: process.env.S3_BUCKET_NAME, GITHUB_ID: process.env.GITHUB_ID, GITHUB_SECRET: process.env.GITHUB_SECRET, + OPENAI_API_KEY: process.env.OPENAI_API_KEY, }, /** * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. diff --git a/src/hooks/useAIComm.ts b/src/hooks/useAIComm.ts new file mode 100644 index 0000000..e165fc5 --- /dev/null +++ b/src/hooks/useAIComm.ts @@ -0,0 +1,102 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import { useState } from "react"; +import type { UserAndAIMessage } from "~/server/api/routers/userAndAIComm"; +import { api } from "~/utils/api"; + +export type AICommResult = [ + UserAndAIMessage[], + () => void, + (value: string) => void, + currentMessage: string, + isAIResponding: boolean, +]; + +export default function useAIComm( + sessionId: string, + userId: string, +): AICommResult { + const [chatState, setChatState] = useState({ + currentMessage: "", + isAIResponding: false, + }); + + const utils = api.useContext(); + + const allSessionMessages = + api.userAndAIMessages.getAllSessionUserAndAIMessages.useQuery({ + sessionId, + userId, + }).data; + + const addMessageMutation = + api.userAndAIMessages.addUserAndAIMessage.useMutation(); + + api.userAndAIMessages.subscribeToSessionUserAndAIMessages.useSubscription( + { sessionId, userId }, + { + onData: (data: UserAndAIMessage) => { + if (allSessionMessages) + allSessionMessages.push({ + ...data, + id: data.id!, + message: data.message, + createdAt: data.createdAt!, + }); + + setChatState((state) => ({ + ...state, + isAIResponding: false, + })); + }, + onError(err) { + console.log("Subscription error: ", err); + void Promise.resolve(utils.userAndUserMessages.invalidate()); + }, + }, + ); + + const sendMessage = () => { + // Don't send empty messages, or messages with only whitespace. Could change this later + if (chatState.currentMessage.trim().length === 0) return; + + allSessionMessages?.push({ + id: "", + sessionId, + userId, + message: chatState.currentMessage, + role: "user", + createdAt: new Date(), + }); + + addMessageMutation.mutate({ + sessionId, + userId, + message: chatState.currentMessage, + }); + + setChatState((state) => ({ + ...state, + currentMessage: "", + isAIResponding: true, + })); + }; + + const onTyping = (value: string) => { + setChatState((prev) => { + return { + ...prev, + currentMessage: value, + }; + }); + }; + + return [ + allSessionMessages as UserAndAIMessage[], + sendMessage, + onTyping, + chatState.currentMessage, + chatState.isAIResponding, + ]; +} diff --git a/src/hooks/useSessionComm.ts b/src/hooks/useSessionComm.ts index 4544f59..c82bf72 100644 --- a/src/hooks/useSessionComm.ts +++ b/src/hooks/useSessionComm.ts @@ -1,9 +1,12 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { useState } from "react"; -import { type Message } from "~/server/api/routers/communication"; +import { type UserAndUserMessage } from "~/server/api/routers/userAndUserComm"; import { api } from "~/utils/api"; export type SessionCommResult = [ - Message[], + UserAndUserMessage[], () => void, (value: string) => void, currentMessage: string, @@ -23,16 +26,18 @@ export default function useSessionComm( const utils = api.useContext(); - const allSessionMessages = api.messages.getAllSessionMessages.useQuery({ - sessionId, - }).data; + const allSessionMessages = + api.userAndUserMessages.getAllSessionUserAndUserMessages.useQuery({ + sessionId, + }).data; - const addMessageMutation = api.messages.addMessage.useMutation(); + const addMessageMutation = + api.userAndUserMessages.addUserAndUserMessage.useMutation(); - api.messages.subscribeToSessionMessages.useSubscription( + api.userAndUserMessages.subscribeToSessionUserAndUserMessages.useSubscription( { sessionId, userId }, { - onData: (data: Message) => { + onData: (data: UserAndUserMessage) => { if (data.message) { if (data.userId !== userId) setChatState((state) => ({ @@ -58,12 +63,13 @@ export default function useSessionComm( }, onError(err) { console.log("Subscription error: ", err); - void Promise.resolve(utils.messages.invalidate()); + void Promise.resolve(utils.userAndUserMessages.invalidate()); }, }, ); - const addWhoIsTypingMutation = api.messages.addWhoIsTyping.useMutation(); + const addWhoIsTypingMutation = + api.userAndUserMessages.addWhoIsTyping.useMutation(); const sendMessage = () => { // Don't send empty messages, or messages with only whitespace. Could change this later @@ -101,7 +107,7 @@ export default function useSessionComm( : ""; return [ - allSessionMessages as Message[], + allSessionMessages as UserAndUserMessage[], sendMessage, onTyping, chatState.currentMessage, diff --git a/src/pages/collab/rooms/[id].tsx b/src/pages/collab/rooms/[id].tsx index 3f8825a..d9280f3 100644 --- a/src/pages/collab/rooms/[id].tsx +++ b/src/pages/collab/rooms/[id].tsx @@ -25,6 +25,7 @@ import { getLanguage } from "~/utils/utils"; import { Tabs, TabList, Tab, TabPanel } from "react-tabs"; import { useSession } from "next-auth/react"; import Chatbox from "~/components/ChatBox"; +import AIBox from "~/components/AIBox"; const SharedEditor = ({ onSave, @@ -267,6 +268,7 @@ const Room = () => { Output Chat + GPT-3.5 { className="row-span-2 w-full h-full p-3 flex flex-col text-black" /> + + +
diff --git a/src/server/api/root.ts b/src/server/api/root.ts index 85da0c4..c6c5765 100644 --- a/src/server/api/root.ts +++ b/src/server/api/root.ts @@ -6,8 +6,9 @@ import { formRouter } from "~/server/api/routers/form"; import answerRouter from "./routers/answer"; import { codeSessionRouter } from "./routers/codeSession"; import { judgeRouter } from "~/server/api/routers/judge"; -import { messagesRouter } from "./routers/communication"; +import { userAndUserMessagesRouter } from "./routers/userAndUserComm"; import { sharedCodeSessionRouter } from "./routers/sharedCodeSession"; +import { userAndAIMessagesRouter } from "./routers/userAndAIComm"; /** * This is the primary router for your server. @@ -22,7 +23,8 @@ export const appRouter = createTRPCRouter({ answer: answerRouter, codeSession: codeSessionRouter, judge: judgeRouter, - messages: messagesRouter, + userAndUserMessages: userAndUserMessagesRouter, + userAndAIMessages: userAndAIMessagesRouter, sharedSession: sharedCodeSessionRouter, }); diff --git a/src/server/api/routers/userAndAIComm.ts b/src/server/api/routers/userAndAIComm.ts new file mode 100644 index 0000000..aeb1a96 --- /dev/null +++ b/src/server/api/routers/userAndAIComm.ts @@ -0,0 +1,129 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +import { observable } from "@trpc/server/observable"; +import { EventEmitter } from "events"; +import { z } from "zod"; +import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc"; +import OpenAI from "openai"; +import type { ChatCompletionRole } from "openai/resources"; + +const ee = new EventEmitter(); +const openai = new OpenAI({ + apiKey: process.env.OPENAI_API_KEY, +}); + +export type UserAndAIMessage = { + id?: string; + sessionId: string; + userId: string; + message: string; + role: ChatCompletionRole; + createdAt?: Date; +}; + +export const userAndAIMessagesRouter = createTRPCRouter({ + getAllSessionUserAndAIMessages: protectedProcedure + .input( + z.object({ + sessionId: z.string(), + userId: z.string(), + }), + ) + .query(async ({ ctx, input }) => { + const { sessionId, userId } = input; + + const messages = + await ctx.prismaPostgres.sessionUserAndAIMessage.findMany({ + where: { + sessionId, + userId, + }, + orderBy: { + createdAt: "asc", + }, + }); + + return messages; + }), + + addUserAndAIMessage: protectedProcedure + .input( + z.object({ + sessionId: z.string(), + userId: z.string(), + message: z.string(), + }), + ) + .mutation(async ({ ctx, input }) => { + const { sessionId, userId, message } = input; + + const messageObject = + await ctx.prismaPostgres.sessionUserAndAIMessage.create({ + data: { + sessionId, + userId, + message, + role: "user", + }, + }); + + const currentSessionMessages = + await ctx.prismaPostgres.sessionUserAndAIMessage.findMany({ + where: { + sessionId, + userId, + }, + orderBy: { + createdAt: "asc", + }, + }); + + const response = await openai.chat.completions.create({ + messages: currentSessionMessages.map((message) => { + return { + role: message.role as ChatCompletionRole, + content: message.message, + }; + }), + model: "gpt-3.5-turbo", + }); + + const aiMessage = response.choices[0]?.message; + + if (aiMessage) { + const aiMessageObject = + await ctx.prismaPostgres.sessionUserAndAIMessage.create({ + data: { + sessionId, + userId, + message: aiMessage.content!, + role: aiMessage.role, + }, + }); + + ee.emit("aiMessage", aiMessageObject); + } + + return messageObject; + }), + + subscribeToSessionUserAndAIMessages: protectedProcedure + .input(z.object({ sessionId: z.string(), userId: z.string() })) + .subscription(({ input }) => { + return observable((emit) => { + const onMessage = (data: UserAndAIMessage) => { + if ( + data.sessionId === input.sessionId && + data.userId === input.userId + ) + emit.next(data); + }; + ee.on("aiMessage", onMessage); + return () => { + ee.off("aiMessage", onMessage); + }; + }); + }), +}); diff --git a/src/server/api/routers/communication.ts b/src/server/api/routers/userAndUserComm.ts similarity index 80% rename from src/server/api/routers/communication.ts rename to src/server/api/routers/userAndUserComm.ts index 1329844..44e8e4f 100644 --- a/src/server/api/routers/communication.ts +++ b/src/server/api/routers/userAndUserComm.ts @@ -9,7 +9,7 @@ import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc"; const ee = new EventEmitter(); -export type Message = { +export type UserAndUserMessage = { id?: string; sessionId: string; userId: string; @@ -20,8 +20,8 @@ export type Message = { }; // Methods include userId as well because users do not have unique names, so who is typing is decided by the userId -export const messagesRouter = createTRPCRouter({ - getAllSessionMessages: protectedProcedure +export const userAndUserMessagesRouter = createTRPCRouter({ + getAllSessionUserAndUserMessages: protectedProcedure .input( z.object({ sessionId: z.string(), @@ -30,7 +30,7 @@ export const messagesRouter = createTRPCRouter({ .query(async ({ ctx, input }) => { const { sessionId } = input; - const messages = await ctx.prismaPostgres.sessionMessage.findMany({ + const messages = await ctx.prismaPostgres.sessionUserAndUserMessage.findMany({ where: { sessionId, }, @@ -42,7 +42,7 @@ export const messagesRouter = createTRPCRouter({ return messages; }), - addMessage: protectedProcedure + addUserAndUserMessage: protectedProcedure .input( z.object({ sessionId: z.string(), @@ -54,7 +54,7 @@ export const messagesRouter = createTRPCRouter({ .mutation(async ({ ctx, input }) => { const { sessionId, userId, userName, message } = input; - const messageObject = await ctx.prismaPostgres.sessionMessage + const messageObject = await ctx.prismaPostgres.sessionUserAndUserMessage .create({ data: { sessionId, @@ -63,9 +63,6 @@ export const messagesRouter = createTRPCRouter({ message, }, }) - .then((req) => { - return req; - }); ee.emit("message", messageObject); ee.emit("typing", { @@ -78,14 +75,14 @@ export const messagesRouter = createTRPCRouter({ return messageObject; }), - subscribeToSessionMessages: protectedProcedure + subscribeToSessionUserAndUserMessages: protectedProcedure .input(z.object({ sessionId: z.string(), userId: z.string() })) .subscription(({ input }) => { - return observable((emit) => { - const onMessage = (data: Message) => { + return observable((emit) => { + const onMessage = (data: UserAndUserMessage) => { if (data.sessionId === input.sessionId) emit.next(data); }; - const onTyping = (data: Message) => { + const onTyping = (data: UserAndUserMessage) => { if ( data.sessionId === input.sessionId && data.userId !== input.userId diff --git a/yarn.lock b/yarn.lock index 137de24..79aaf1f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2059,11 +2059,26 @@ resolved "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz" integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== +"@types/node-fetch@^2.6.4": + version "2.6.7" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.7.tgz#a1abe2ce24228b58ad97f99480fdcf9bbc6ab16d" + integrity sha512-lX17GZVpJ/fuCjguZ5b3TjEbSENxmEk1B2z02yoXSK9WMEWRivhdSY73wWMn6bpcCDAOh6qAdktpKHIlkDk2lg== + dependencies: + "@types/node" "*" + form-data "^4.0.0" + "@types/node@*": version "20.5.9" resolved "https://registry.npmjs.org/@types/node/-/node-20.5.9.tgz" integrity sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ== +"@types/node@^18.11.18": + version "18.18.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.18.7.tgz#bb3a7068dc4ba421b6968f2a259298b3a4e129e8" + integrity sha512-bw+lEsxis6eqJYW8Ql6+yTqkE6RuFtsQPSe5JxXbqYRFQEER5aJA9a5UH9igqDWm3X4iLHIKOHlnAXLM4mi7uQ== + dependencies: + undici-types "~5.26.4" + "@types/node@^18.16.0": version "18.17.12" resolved "https://registry.npmjs.org/@types/node/-/node-18.17.12.tgz" @@ -2427,6 +2442,13 @@ abbrev@1: resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + accepts@^1.3.5: version "1.3.8" resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz" @@ -2457,6 +2479,13 @@ agent-base@6: dependencies: debug "4" +agentkeepalive@^4.2.1: + version "4.5.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" + integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== + dependencies: + humanize-ms "^1.2.1" + ajv-keywords@^3.5.2: version "3.5.2" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" @@ -2692,6 +2721,11 @@ balanced-match@^1.0.0: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base-64@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb" + integrity sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA== + bcrypt@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz" @@ -2847,6 +2881,11 @@ character-entities@^2.0.0: resolved "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz" integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== +charenc@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== + "chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.3: version "3.5.3" resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz" @@ -3104,6 +3143,11 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +crypt@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== + cssesc@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz" @@ -3229,6 +3273,14 @@ diff@^5.0.0: resolved "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz" integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw== +digest-fetch@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/digest-fetch/-/digest-fetch-1.3.0.tgz#898e69264d00012a23cf26e8a3e40320143fc661" + integrity sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA== + dependencies: + base-64 "^0.1.0" + md5 "^2.3.0" + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" @@ -3701,6 +3753,11 @@ esutils@^2.0.2: resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + events@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" @@ -3829,6 +3886,11 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +form-data-encoder@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040" + integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== + form-data@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" @@ -3838,6 +3900,14 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +formdata-node@^4.3.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2" + integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ== + dependencies: + node-domexception "1.0.0" + web-streams-polyfill "4.0.0-beta.3" + fraction.js@^4.2.0: version "4.3.2" resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.2.tgz" @@ -4240,6 +4310,13 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + humanize-number@0.0.2: version "0.0.2" resolved "https://registry.npmjs.org/humanize-number/-/humanize-number-0.0.2.tgz" @@ -4356,6 +4433,11 @@ is-buffer@^2.0.0: resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== +is-buffer@~1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz" @@ -4819,6 +4901,15 @@ markdown-table@^3.0.0: resolved "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz" integrity sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw== +md5@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" + integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== + dependencies: + charenc "0.0.2" + crypt "0.0.2" + is-buffer "~1.1.6" + mdast-util-definitions@^5.0.0: version "5.1.2" resolved "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz" @@ -5368,7 +5459,7 @@ ms@2.1.2: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.1.1: +ms@^2.0.0, ms@^2.1.1: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -5461,6 +5552,11 @@ node-addon-api@^5.0.0: resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz" integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== +node-domexception@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + node-fetch@^2.6.7: version "2.7.0" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" @@ -5641,6 +5737,21 @@ only@~0.0.2: resolved "https://registry.npmjs.org/only/-/only-0.0.2.tgz" integrity sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ== +openai@^4.14.1: + version "4.14.1" + resolved "https://registry.yarnpkg.com/openai/-/openai-4.14.1.tgz#26ffa8e86a57da6595b46350355a754d934e61a1" + integrity sha512-aBb7DVdzSnEUBFHTbnVoitauefvjRuUHS5pa7lm1m5JmHifD+1Hff1RzxYC12ogugVcCmWT99NZNfzyD6n/0IQ== + dependencies: + "@types/node" "^18.11.18" + "@types/node-fetch" "^2.6.4" + abort-controller "^3.0.0" + agentkeepalive "^4.2.1" + digest-fetch "^1.3.0" + form-data-encoder "1.7.2" + formdata-node "^4.3.2" + node-fetch "^2.6.7" + web-streams-polyfill "^3.2.1" + openid-client@^5.4.0: version "5.4.3" resolved "https://registry.npmjs.org/openid-client/-/openid-client-5.4.3.tgz" @@ -6896,6 +7007,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + unified@^10.0.0: version "10.1.2" resolved "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz" @@ -7138,6 +7254,16 @@ web-namespaces@^2.0.0: resolved "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz" integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ== +web-streams-polyfill@4.0.0-beta.3: + version "4.0.0-beta.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38" + integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug== + +web-streams-polyfill@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" + integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz"