diff --git a/package-lock.json b/package-lock.json index 0ee7a0b..7fd144b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,7 +37,7 @@ "next": "13.5.5", "next-auth": "^4.22.3", "next-themes": "^0.2.1", - "openai": "^3.3.0", + "openai": "^4.29.2", "postcss": "8.4.27", "react": "18.2.0", "react-d3-cloud": "^1.0.6", @@ -1333,6 +1333,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.3.tgz", "integrity": "sha512-Yu3+r4Mn/iY6Mf0aihncZQ1qOjOUrCiodbHHY1hds5O+7BbKp9t+Li7zLO13zO8j9L2C6euz8xsYQP0rjGvVXw==" }, + "node_modules/@types/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -1463,6 +1472,17 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -1482,6 +1502,17 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1752,6 +1783,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base-64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==" + }, "node_modules/big-integer": { "version": "1.6.51", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", @@ -1917,6 +1953,14 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "engines": { + "node": "*" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -2048,6 +2092,14 @@ "node": ">= 8" } }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "engines": { + "node": "*" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -2270,6 +2322,15 @@ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" }, + "node_modules/digest-fetch": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/digest-fetch/-/digest-fetch-1.3.0.tgz", + "integrity": "sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==", + "dependencies": { + "base-64": "^0.1.0", + "md5": "^2.3.0" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -2847,6 +2908,14 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/execa": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz", @@ -3012,6 +3081,31 @@ "node": ">= 6" } }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/formdata-node/node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "engines": { + "node": ">= 14" + } + }, "node_modules/fraction.js": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", @@ -3324,6 +3418,14 @@ "node": ">=14.18.0" } }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dependencies": { + "ms": "^2.0.0" + } + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -3445,6 +3547,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -3859,6 +3966,16 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0" } }, + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -4079,6 +4196,43 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", @@ -4284,20 +4438,30 @@ } }, "node_modules/openai": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-3.3.0.tgz", - "integrity": "sha512-uqxI/Au+aPRnsaQRe8CojU0eCR7I0mBiKjD3sNMzY6DaC1ZVrc85u98mtJW6voDug8fgGN+DIZmTDxTthxb7dQ==", - "dependencies": { - "axios": "^0.26.0", - "form-data": "^4.0.0" + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.29.2.tgz", + "integrity": "sha512-cPkT6zjEcE4qU5OW/SoDDuXEsdOLrXlAORhzmaguj5xZSPlgKvLhi27sFWhLKj07Y6WKNWxcwIbzm512FzTBNQ==", + "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" + }, + "bin": { + "openai": "bin/cli" } }, - "node_modules/openai/node_modules/axios": { - "version": "0.26.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", - "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.26", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.26.tgz", + "integrity": "sha512-+wiMJsIwLOYCvUqSdKTrfkS8mpTp+MPINe6+Np4TAGFWWRWiBQ5kSq9nZGCSPkzx9mvT+uEukzpX4MOSCydcvw==", "dependencies": { - "follow-redirects": "^1.14.8" + "undici-types": "~5.26.4" } }, "node_modules/openid-client": { @@ -5460,6 +5624,11 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -5609,6 +5778,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "node_modules/untildify": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", @@ -5728,6 +5902,28 @@ "node": ">=10.13.0" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index f62be35..d358131 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "next": "13.5.5", "next-auth": "^4.22.3", "next-themes": "^0.2.1", - "openai": "^3.3.0", + "openai": "^4.29.2", "postcss": "8.4.27", "react": "18.2.0", "react-d3-cloud": "^1.0.6", diff --git a/src/app/api/game/route.ts b/src/app/api/game/route.ts index 54ec720..d29f70f 100644 --- a/src/app/api/game/route.ts +++ b/src/app/api/game/route.ts @@ -1,29 +1,30 @@ -import { prisma } from "@/lib/db"; -import { getAuthSession } from "@/lib/nextauth"; -import { quizCreationSchema } from "@/schemas/forms/quiz"; -import { NextResponse } from "next/server"; -import { z } from "zod"; -import axios from "axios"; +import {prisma} from '@/lib/db'; +import {getAuthSession} from '@/lib/nextauth'; +import {quizCreationSchema} from '@/schemas/forms/quiz'; +import {NextResponse} from 'next/server'; +import {z} from 'zod'; +import axios from 'axios'; export async function POST(req: Request, res: Response) { try { const session = await getAuthSession(); if (!session?.user) { return NextResponse.json( - { error: "You must be logged in to create a game." }, + {error: 'You must be logged in to create a game.'}, { status: 401, - } + }, ); } const body = await req.json(); - const { topic, type, amount } = quizCreationSchema.parse(body); + const {topic, type, amount} = quizCreationSchema.parse(body); const game = await prisma.game.create({ data: { gameType: type, timeStarted: new Date(), userId: session.user.id, topic, + timeEnded: null, // so we can tell if the user completed the quiz or not }, }); await prisma.topic_count.upsert({ @@ -41,16 +42,16 @@ export async function POST(req: Request, res: Response) { }, }); - const { data } = await axios.post( + const {data} = await axios.post( `${process.env.API_URL as string}/api/questions`, { amount, topic, type, - } + }, ); - if (type === "mcq") { + if (type === 'mcq') { type mcqQuestion = { question: string; answer: string; @@ -72,14 +73,14 @@ export async function POST(req: Request, res: Response) { answer: question.answer, options: JSON.stringify(options), gameId: game.id, - questionType: "mcq", + questionType: 'mcq', }; }); await prisma.question.createMany({ data: manyData, }); - } else if (type === "open_ended") { + } else if (type === 'open_ended') { type openQuestion = { question: string; answer: string; @@ -90,27 +91,27 @@ export async function POST(req: Request, res: Response) { question: question.question, answer: question.answer, gameId: game.id, - questionType: "open_ended", + questionType: 'open_ended', }; }), }); } - return NextResponse.json({ gameId: game.id }, { status: 200 }); + return NextResponse.json({gameId: game.id}, {status: 200}); } catch (error) { if (error instanceof z.ZodError) { return NextResponse.json( - { error: error.issues }, + {error: error.issues}, { status: 400, - } + }, ); } else { return NextResponse.json( - { error: "An unexpected error occurred." }, + {error: 'An unexpected error occurred.'}, { status: 500, - } + }, ); } } @@ -120,20 +121,20 @@ export async function GET(req: Request, res: Response) { const session = await getAuthSession(); if (!session?.user) { return NextResponse.json( - { error: "You must be logged in to create a game." }, + {error: 'You must be logged in to create a game.'}, { status: 401, - } + }, ); } const url = new URL(req.url); - const gameId = url.searchParams.get("gameId"); + const gameId = url.searchParams.get('gameId'); if (!gameId) { return NextResponse.json( - { error: "You must provide a game id." }, + {error: 'You must provide a game id.'}, { status: 400, - } + }, ); } @@ -147,25 +148,25 @@ export async function GET(req: Request, res: Response) { }); if (!game) { return NextResponse.json( - { error: "Game not found." }, + {error: 'Game not found.'}, { status: 404, - } + }, ); } return NextResponse.json( - { game }, + {game}, { status: 400, - } + }, ); } catch (error) { return NextResponse.json( - { error: "An unexpected error occurred." }, + {error: 'An unexpected error occurred.'}, { status: 500, - } + }, ); } } diff --git a/src/app/api/questions/route.ts b/src/app/api/questions/route.ts index 65dbc72..9958415 100644 --- a/src/app/api/questions/route.ts +++ b/src/app/api/questions/route.ts @@ -1,75 +1,85 @@ -import { strict_output } from "@/lib/gpt"; -import { getAuthSession } from "@/lib/nextauth"; -import { getQuestionsSchema } from "@/schemas/questions"; -import { NextResponse } from "next/server"; -import { ZodError } from "zod"; +import {strict_output} from '@/lib/gpt'; +import {getAuthSession} from '@/lib/nextauth'; +import {getQuestionsSchema} from '@/schemas/questions'; +import {NextResponse} from 'next/server'; +import {ZodError} from 'zod'; -export const runtime = "nodejs"; +export const runtime = 'nodejs'; export const maxDuration = 500; export async function POST(req: Request, res: Response) { try { const session = await getAuthSession(); - // if (!session?.user) { - // return NextResponse.json( - // { error: "You must be logged in to create a game." }, - // { - // status: 401, - // } - // ); - // } const body = await req.json(); - const { amount, topic, type } = getQuestionsSchema.parse(body); - let questions: any; - if (type === "open_ended") { - questions = await strict_output( - "You are a helpful AI that is able to generate a pair of question and answers, the length of each answer should not be more than 15 words, store all the pairs of answers and questions in a JSON array", - new Array(amount).fill( - `You are to generate a random hard open-ended questions about ${topic}` - ), - { - question: "question", - answer: "answer with max length of 15 words", - } - ); - } else if (type === "mcq") { - questions = await strict_output( - "You are a helpful AI that is able to generate mcq questions and answers, the length of each answer should not be more than 15 words, store all answers and questions and options in a JSON array", - new Array(amount).fill( - `You are to generate a random hard mcq question about ${topic}` - ), - { - question: "question", - answer: "answer with max length of 15 words", - option1: "option1 with max length of 15 words", - option2: "option2 with max length of 15 words", - option3: "option3 with max length of 15 words", + // const {amount, topic, type} = getQuestionsSchema.parse(body); + const {amount, topic, type} = body; + + // Define a maximum chunk size + const chunkSize = 5; // Adjust this value as needed + + // Create an array to hold all generated questions + let allQuestions: any[] = []; + // Create prompts based on the requested amount in batches + for (let i = 0; i < amount; i += chunkSize) { + const prompts: string[] = []; + // Generate prompts for the current batch + for (let j = i; j < Math.min(i + chunkSize, amount); j++) { + prompts.push( + `You are to generate a random hard ${type} question about ${topic}`, + ); + let questionsBatch: any; + if (type === 'open_ended') { + questionsBatch = await strict_output( + 'You are a helpful AI that is able to generate a pair of question and answers, the length of each answer should not be more than 15 words, store all the pairs of answers and questions in a JSON array', + new Array(amount).fill( + `You are to generate a random hard open-ended questions about ${topic}`, + ), + { + question: 'question', + answer: 'answer with max length of 15 words', + }, + ); + } else if (type === 'mcq') { + questionsBatch = await strict_output( + 'You are a helpful AI that is able to generate mcq questions and answers, the length of each answer should not be more than 15 words, store all answers and questions and options in a JSON array', + new Array(amount).fill( + `You are to generate a random hard mcq question about ${topic}`, + ), + { + question: 'question', + answer: 'answer with max length of 15 words', + option1: 'option1 with max length of 15 words', + option2: 'option2 with max length of 15 words', + option3: 'option3 with max length of 15 words', + }, + ); } - ); + allQuestions = allQuestions.concat(questionsBatch); + } } return NextResponse.json( { - questions: questions, + questions: allQuestions, }, { status: 200, - } + }, ); } catch (error) { if (error instanceof ZodError) { return NextResponse.json( - { error: error.issues }, + {error: error.issues}, { status: 400, - } + }, ); } else { - console.error("elle gpt error", error); + console.error('elle gpt error', error); return NextResponse.json( - { error: "An unexpected error occurred." }, + {error: 'An unexpected error occurred. ' + error}, { status: 500, - } + }, ); } } diff --git a/src/app/history/page.tsx b/src/app/history/page.tsx index 7a2bba6..43d4bab 100644 --- a/src/app/history/page.tsx +++ b/src/app/history/page.tsx @@ -1,18 +1,19 @@ -import HistoryComponent from "@/components/HistoryComponent"; -import { getAuthSession } from "@/lib/nextauth"; -import { redirect } from "next/navigation"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import React from "react"; -import Link from "next/link"; -import { buttonVariants } from "@/components/ui/button"; -import { LucideLayoutDashboard } from "lucide-react"; +import HistoryComponent from '@/components/HistoryComponent'; +import {getAuthSession} from '@/lib/nextauth'; +import {redirect} from 'next/navigation'; +import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card'; +import React from 'react'; +import Link from 'next/link'; +import {buttonVariants} from '@/components/ui/button'; +import {LucideLayoutDashboard} from 'lucide-react'; +import IncompleteGames from '@/components/IncompleteGames'; type Props = {}; const History = async (props: Props) => { const session = await getAuthSession(); if (!session?.user) { - return redirect("/"); + return redirect('/'); } return (
@@ -26,8 +27,9 @@ const History = async (props: Props) => {
- + + diff --git a/src/components/HistoryComponent.tsx b/src/components/HistoryComponent.tsx index daeba61..96b77db 100644 --- a/src/components/HistoryComponent.tsx +++ b/src/components/HistoryComponent.tsx @@ -1,31 +1,34 @@ -import { prisma } from "@/lib/db"; -import { Clock, CopyCheck, Edit2 } from "lucide-react"; -import Link from "next/link"; -import React from "react"; -import MCQCounter from "./MCQCounter"; +import {prisma} from '@/lib/db'; +import {Clock, CopyCheck, Edit2} from 'lucide-react'; +import Link from 'next/link'; +import React from 'react'; +import MCQCounter from './MCQCounter'; type Props = { limit: number; userId: string; }; -const HistoryComponent = async ({ limit, userId }: Props) => { +const HistoryComponent = async ({limit, userId}: Props) => { const games = await prisma.game.findMany({ take: limit, where: { userId, + NOT: { + timeEnded: null, // we only want to pull the games that have been completed + }, }, orderBy: { - timeStarted: "desc", + timeStarted: 'desc', }, }); return (
- {games.map((game) => { + {games.map(game => { return (
- {game.gameType === "mcq" ? ( + {game.gameType === 'mcq' ? ( ) : ( @@ -33,16 +36,16 @@ const HistoryComponent = async ({ limit, userId }: Props) => {
+ href={`/statistics/${game.id}`}> {game.topic} + Completed

{new Date(game.timeEnded ?? 0).toLocaleDateString()}

- {game.gameType === "mcq" ? "Multiple Choice" : "Open-Ended"} + {game.gameType === 'mcq' ? 'Multiple Choice' : 'Open-Ended'}

diff --git a/src/components/IncompleteGames.tsx b/src/components/IncompleteGames.tsx new file mode 100644 index 0000000..aa3dd47 --- /dev/null +++ b/src/components/IncompleteGames.tsx @@ -0,0 +1,57 @@ +import {prisma} from '@/lib/db'; +import {Clock, CopyCheck, Edit2} from 'lucide-react'; +import Link from 'next/link'; +import React from 'react'; + +type Props = { + limit: number; + userId: string; +}; + +const IncompleteGames = async ({limit, userId}: Props) => { + const games = await prisma.game.findMany({ + take: limit, + where: { + userId, + timeEnded: null, // fetch all the games that are not completed + }, + orderBy: { + timeStarted: 'desc', + }, + }); + return ( +
+ {games.map(game => { + return ( +
+
+ {game.gameType === 'mcq' ? ( + + ) : ( + + )} +
+ // Redirect them to go and complete the game + + {game.topic} + Incomplete + +

+ + {new Date(game.timeEnded ?? 0).toLocaleDateString()} +

+

+ {game.gameType === 'mcq' ? 'Multiple Choice' : 'Open-Ended'} +

+
+
+
+ ); + })} +
+ ); +}; + +export default IncompleteGames; diff --git a/src/lib/gpt.ts b/src/lib/gpt.ts index e5dfbc7..b45f2f8 100644 --- a/src/lib/gpt.ts +++ b/src/lib/gpt.ts @@ -1,9 +1,8 @@ -import { Configuration, OpenAIApi } from "openai"; +import OpenAI from 'openai'; -const configuration = new Configuration({ +const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); -const openai = new OpenAIApi(configuration); interface OutputFormat { [key: string]: string | string[] | OutputFormat; @@ -13,12 +12,12 @@ export async function strict_output( system_prompt: string, user_prompt: string | string[], output_format: OutputFormat, - default_category: string = "", + default_category: string = '', output_value_only: boolean = false, - model: string = "gpt-3.5-turbo", + model: string = 'gpt-3.5-turbo', temperature: number = 1, num_tries: number = 3, - verbose: boolean = false + verbose: boolean = false, ): Promise< { question: string; @@ -33,11 +32,11 @@ export async function strict_output( const list_output: boolean = /\[.*?\]/.test(JSON.stringify(output_format)); // start off with no error message - let error_msg: string = ""; + let error_msg: string = ''; for (let i = 0; i < num_tries; i++) { let output_format_prompt: string = `\nYou are to output the following in json format: ${JSON.stringify( - output_format + output_format, )}. \nDo not put quotation marks or escape character \\ in the output fields.`; if (list_output) { @@ -55,31 +54,31 @@ export async function strict_output( } // Use OpenAI to get a response - const response = await openai.createChatCompletion({ + const response = await openai.chat.completions.create({ temperature: temperature, model: model, messages: [ { - role: "system", + role: 'system', content: system_prompt + output_format_prompt + error_msg, }, - { role: "user", content: user_prompt.toString() }, + {role: 'user', content: user_prompt.toString()}, ], }); let res: string = - response.data.choices[0].message?.content?.replace(/'/g, '"') ?? ""; + response.choices[0].message?.content?.replace(/'/g, '"') ?? ''; // ensure that we don't replace away apostrophes in text res = res.replace(/(\w)"(\w)/g, "$1'$2"); if (verbose) { console.log( - "System prompt:", - system_prompt + output_format_prompt + error_msg + 'System prompt:', + system_prompt + output_format_prompt + error_msg, ); - console.log("\nUser prompt:", user_prompt); - console.log("\nGPT response:", res); + console.log('\nUser prompt:', user_prompt); + console.log('\nGPT response:', res); } // try-catch block to ensure output format is adhered to @@ -88,7 +87,7 @@ export async function strict_output( if (list_input) { if (!Array.isArray(output)) { - throw new Error("Output format not in a list of json"); + throw new Error('Output format not in a list of json'); } } else { output = [output]; @@ -119,8 +118,8 @@ export async function strict_output( output[index][key] = default_category; } // if the output is a description format, get only the label - if (output[index][key].includes(":")) { - output[index][key] = output[index][key].split(":")[0]; + if (output[index][key].includes(':')) { + output[index][key] = output[index][key].split(':')[0]; } } } @@ -138,8 +137,8 @@ export async function strict_output( return list_input ? output : output[0]; } catch (e) { error_msg = `\n\nResult: ${res}\n\nError message: ${e}`; - console.log("An exception occurred:", e); - console.log("Current invalid json format:", res); + console.log('An exception occurred:', e); + console.log('Current invalid json format:', res); } }