Spaces:
Running
Running
work in progress. end page and error management are missing
Browse files- app/api/[questId]/score/route.ts +45 -0
- app/api/start/route.ts +29 -12
- app/layout.tsx +16 -4
- app/page.tsx +21 -4
- app/quizz/layout.tsx +27 -0
- app/quizz/page.tsx +9 -0
- assets/css/globals.css +2 -2
- assets/images/sparkles-2.svg +4 -0
- assets/images/sparkles.svg +4 -0
- components/button/button.tsx +1 -1
- components/quizz/card.tsx +82 -0
- components/quizz/hooks/useQuizz.ts +53 -27
- components/quizz/index.tsx +24 -42
- components/quizz/layout-score.tsx +12 -0
- components/react-query/hydrate.tsx +9 -0
- components/react-query/providers.tsx +11 -0
- data/dev.db +0 -0
- next.config.js +1 -1
- package-lock.json +0 -0
- package.json +4 -0
- prisma/schema.prisma +8 -2
- tailwind.config.ts +1 -1
- utils/index.ts +1 -0
- utils/type.ts +1 -0
app/api/[questId]/score/route.ts
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { NextRequest } from "next/server";
|
2 |
+
import { PrismaClient } from '@prisma/client'
|
3 |
+
|
4 |
+
const prisma = new PrismaClient()
|
5 |
+
|
6 |
+
export async function POST(request: NextRequest, { params }: { params: { questId: string } }) {
|
7 |
+
const { questId } = params;
|
8 |
+
const { promptId } = await request.json()
|
9 |
+
|
10 |
+
if (!promptId) {
|
11 |
+
return Response.json({
|
12 |
+
message: "promptId is required",
|
13 |
+
}, { statusText: "Missing body fields", status: 404 });
|
14 |
+
}
|
15 |
+
|
16 |
+
const quest = await prisma.quest.findUnique({
|
17 |
+
where: {
|
18 |
+
id: questId
|
19 |
+
}
|
20 |
+
})
|
21 |
+
|
22 |
+
if (!quest?.id) {
|
23 |
+
return Response.json({
|
24 |
+
message: "Quest doesn't exist",
|
25 |
+
}, { statusText: "Not Found", status: 404 });
|
26 |
+
}
|
27 |
+
|
28 |
+
// even if the answer is wrong, we delete the quest, not needed anymore
|
29 |
+
// await prisma.quest.delete({
|
30 |
+
// where: {
|
31 |
+
// id: questId
|
32 |
+
// }
|
33 |
+
// })
|
34 |
+
|
35 |
+
if (quest.prompt_id_correct !== promptId) {
|
36 |
+
return Response.json({
|
37 |
+
ok: false,
|
38 |
+
message: "Wrong answer",
|
39 |
+
}, { statusText: "Wrong answer", status: 400 });
|
40 |
+
}
|
41 |
+
|
42 |
+
return Response.json({
|
43 |
+
ok: true,
|
44 |
+
})
|
45 |
+
}
|
app/api/start/route.ts
CHANGED
@@ -4,11 +4,6 @@ import { PrismaClient } from '@prisma/client'
|
|
4 |
const prisma = new PrismaClient()
|
5 |
|
6 |
export async function POST(request: NextRequest,) {
|
7 |
-
|
8 |
-
if (!prisma.prompt) {
|
9 |
-
return Response.json({}, { statusText: "No table found", status: 500 });
|
10 |
-
}
|
11 |
-
|
12 |
const all_prompts = await prisma.prompt.findMany({
|
13 |
orderBy: {
|
14 |
id: 'desc'
|
@@ -19,20 +14,42 @@ export async function POST(request: NextRequest,) {
|
|
19 |
return Response.json({}, { statusText: "Not enough prompts. Try again.", status: 500 });
|
20 |
}
|
21 |
|
22 |
-
const prompts: Array<{ prompt: string, file_name: string, id:
|
23 |
-
const prompts_ids: Set<
|
24 |
|
25 |
const is_correct = Math.floor(Math.random() * 3)
|
|
|
26 |
while (prompts.length < 3) {
|
27 |
const random_prompt = all_prompts[Math.floor(Math.random() * all_prompts.length)]
|
28 |
if (!prompts_ids.has(random_prompt.id)) {
|
29 |
-
prompts.push(
|
30 |
-
...random_prompt,
|
31 |
-
is_correct: prompts.length === is_correct
|
32 |
-
})
|
33 |
prompts_ids.add(random_prompt.id)
|
34 |
}
|
35 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
|
37 |
-
return Response.json(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
}
|
|
|
4 |
const prisma = new PrismaClient()
|
5 |
|
6 |
export async function POST(request: NextRequest,) {
|
|
|
|
|
|
|
|
|
|
|
7 |
const all_prompts = await prisma.prompt.findMany({
|
8 |
orderBy: {
|
9 |
id: 'desc'
|
|
|
14 |
return Response.json({}, { statusText: "Not enough prompts. Try again.", status: 500 });
|
15 |
}
|
16 |
|
17 |
+
const prompts: Array<{ prompt: string, file_name: string, id: string, is_correct?: boolean }> = []
|
18 |
+
const prompts_ids: Set<string> = new Set()
|
19 |
|
20 |
const is_correct = Math.floor(Math.random() * 3)
|
21 |
+
|
22 |
while (prompts.length < 3) {
|
23 |
const random_prompt = all_prompts[Math.floor(Math.random() * all_prompts.length)]
|
24 |
if (!prompts_ids.has(random_prompt.id)) {
|
25 |
+
prompts.push(random_prompt,)
|
|
|
|
|
|
|
26 |
prompts_ids.add(random_prompt.id)
|
27 |
}
|
28 |
}
|
29 |
+
|
30 |
+
const prompt_correct = prompts[is_correct]
|
31 |
+
|
32 |
+
if (!prompt_correct) {
|
33 |
+
return Response.json({
|
34 |
+
message: "Not enough prompts. Try again."
|
35 |
+
}, { statusText: "Not enough prompts. Try again.", status: 500 });
|
36 |
+
}
|
37 |
+
|
38 |
+
const quest = await prisma.quest.create({
|
39 |
+
data: {
|
40 |
+
prompt_id_correct: prompt_correct.id
|
41 |
+
}
|
42 |
+
})
|
43 |
|
44 |
+
return Response.json(
|
45 |
+
{
|
46 |
+
data: {
|
47 |
+
prompts,
|
48 |
+
question: prompt_correct?.prompt,
|
49 |
+
quest_id: quest.id
|
50 |
+
},
|
51 |
+
ok: true,
|
52 |
+
status: 200
|
53 |
+
}
|
54 |
+
);
|
55 |
}
|
app/layout.tsx
CHANGED
@@ -1,23 +1,35 @@
|
|
|
|
1 |
import type { Metadata } from "next";
|
2 |
import { Inter } from "next/font/google";
|
3 |
-
import "
|
4 |
|
5 |
-
|
|
|
6 |
|
7 |
export const metadata: Metadata = {
|
8 |
title: "Create Next App",
|
9 |
description: "Generated by create next app",
|
10 |
};
|
11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
export default function RootLayout({
|
13 |
children,
|
14 |
}: {
|
15 |
children: React.ReactNode;
|
16 |
}) {
|
17 |
return (
|
18 |
-
<html lang="en">
|
19 |
<body>
|
20 |
-
|
|
|
|
|
21 |
<div id="background__noisy" />
|
22 |
</body>
|
23 |
</html>
|
|
|
1 |
+
import { cache } from "react";
|
2 |
import type { Metadata } from "next";
|
3 |
import { Inter } from "next/font/google";
|
4 |
+
import { QueryClient } from "@tanstack/react-query";
|
5 |
|
6 |
+
import Providers from "@/components/react-query/providers";
|
7 |
+
import "@/assets/css/globals.css";
|
8 |
|
9 |
export const metadata: Metadata = {
|
10 |
title: "Create Next App",
|
11 |
description: "Generated by create next app",
|
12 |
};
|
13 |
|
14 |
+
const inter = Inter({
|
15 |
+
subsets: ["latin"],
|
16 |
+
display: "swap",
|
17 |
+
variable: "--font-inter",
|
18 |
+
});
|
19 |
+
|
20 |
+
export const getQueryClient = () => cache(() => new QueryClient());
|
21 |
+
|
22 |
export default function RootLayout({
|
23 |
children,
|
24 |
}: {
|
25 |
children: React.ReactNode;
|
26 |
}) {
|
27 |
return (
|
28 |
+
<html lang="en" className={`${inter.variable}`}>
|
29 |
<body>
|
30 |
+
<div className="pb-16 h-screen overflow-auto">
|
31 |
+
<Providers>{children}</Providers>
|
32 |
+
</div>
|
33 |
<div id="background__noisy" />
|
34 |
</body>
|
35 |
</html>
|
app/page.tsx
CHANGED
@@ -1,17 +1,34 @@
|
|
1 |
-
import { Button } from "@/components/button/button";
|
2 |
-
import { Quizz } from "@/components/quizz";
|
3 |
import Link from "next/link";
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
export default function Home() {
|
6 |
return (
|
7 |
<div className="w-full min-h-screen mx-auto max-w-4xl relative flex items-center justify-center">
|
8 |
<div className="relative grid grid-cols-1 gap-8 py-12 px-6">
|
9 |
-
<h1 className="text-center text-5xl md:text-7xl font-extrabold bg-clip-text text-transparent bg-gradient-to-b from-white to-white/80">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
Which image is the <br />
|
11 |
<span className="bg-gradient-to-tr from-green to-cyan bg-clip-text">
|
12 |
good
|
13 |
</span>{" "}
|
14 |
one?
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
</h1>
|
16 |
<h2 className="text-white text-xl md:text-2xl text-center font-light">
|
17 |
Time to test if you now how to compose the perfect prompt. <br />
|
@@ -24,7 +41,7 @@ export default function Home() {
|
|
24 |
</Button>
|
25 |
</Link>
|
26 |
</div>
|
27 |
-
<div className="absolute top-1/2 left-1/2 will-transform -translate-x-1/2 -translate-y-1/2 bg-gradient-to-br from-green to-cyan blur-[120px] md:blur-[140px] w-full h-full rounded-full z-[-1] opacity-
|
28 |
</div>
|
29 |
</div>
|
30 |
);
|
|
|
|
|
|
|
1 |
import Link from "next/link";
|
2 |
+
import Image from "next/image";
|
3 |
+
|
4 |
+
import { Button } from "@/components/button/button";
|
5 |
+
import SparklingEffect from "@/assets/images/sparkles.svg";
|
6 |
+
import SparklingEffect2 from "@/assets/images/sparkles-2.svg";
|
7 |
|
8 |
export default function Home() {
|
9 |
return (
|
10 |
<div className="w-full min-h-screen mx-auto max-w-4xl relative flex items-center justify-center">
|
11 |
<div className="relative grid grid-cols-1 gap-8 py-12 px-6">
|
12 |
+
<h1 className="text-center text-5xl md:text-7xl font-extrabold bg-clip-text text-transparent bg-gradient-to-b from-white to-white/80 relative">
|
13 |
+
<Image
|
14 |
+
src={SparklingEffect}
|
15 |
+
width={30}
|
16 |
+
height={50}
|
17 |
+
alt="Sparkling effect"
|
18 |
+
className="absolute top-8 left-0 -translate-x-[calc(100%+1rem)]"
|
19 |
+
/>
|
20 |
Which image is the <br />
|
21 |
<span className="bg-gradient-to-tr from-green to-cyan bg-clip-text">
|
22 |
good
|
23 |
</span>{" "}
|
24 |
one?
|
25 |
+
<Image
|
26 |
+
src={SparklingEffect2}
|
27 |
+
width={30}
|
28 |
+
height={50}
|
29 |
+
alt="Sparkling effect"
|
30 |
+
className="absolute -top-6 right-0 translate-x-[calc(100%+1rem)]"
|
31 |
+
/>
|
32 |
</h1>
|
33 |
<h2 className="text-white text-xl md:text-2xl text-center font-light">
|
34 |
Time to test if you now how to compose the perfect prompt. <br />
|
|
|
41 |
</Button>
|
42 |
</Link>
|
43 |
</div>
|
44 |
+
<div className="absolute top-1/2 left-1/2 will-transform -translate-x-1/2 -translate-y-1/2 bg-gradient-to-br from-green to-cyan blur-[120px] md:blur-[140px] w-full h-full max-h-[250px] rounded-full z-[-1] opacity-40"></div>
|
45 |
</div>
|
46 |
</div>
|
47 |
);
|
app/quizz/layout.tsx
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { LayoutScore } from "@/components/quizz/layout-score";
|
2 |
+
|
3 |
+
export default function RootLayout({
|
4 |
+
children,
|
5 |
+
}: {
|
6 |
+
children: React.ReactNode;
|
7 |
+
}) {
|
8 |
+
return (
|
9 |
+
<div className="min-h-screen relative">
|
10 |
+
<div className="absolute top-0 left-1/2 will-transform -translate-x-1/2 -translate-y-1/2 bg-gradient-to-br from-green to-cyan blur-[120px] md:blur-[180px] w-full h-full max-w-5xl max-h-[250px] rounded-full z-[-1] opacity-50"></div>
|
11 |
+
<header>
|
12 |
+
<div className="flex items-center justify-center md:justify-between px-6 py-5 container mx-auto">
|
13 |
+
<p className="text-center text-xl font-extrabold bg-clip-text text-transparent bg-gradient-to-b from-white to-white/80 relative">
|
14 |
+
Which image is the{" "}
|
15 |
+
<span className="bg-gradient-to-tr from-green to-cyan bg-clip-text">
|
16 |
+
good
|
17 |
+
</span>{" "}
|
18 |
+
one?
|
19 |
+
</p>
|
20 |
+
<LayoutScore className="hidden md:block" />
|
21 |
+
</div>
|
22 |
+
<div className="bg-gradient-to-r from-transparent via-white/70 to-transparent w-full h-[1px] opacity-20" />
|
23 |
+
</header>
|
24 |
+
<main className="px-6 pt-6 lg:pt-20 max-w-7xl mx-auto">{children}</main>
|
25 |
+
</div>
|
26 |
+
);
|
27 |
+
}
|
app/quizz/page.tsx
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { QuizzContent } from "@/components/quizz";
|
2 |
+
|
3 |
+
export default function Quizz() {
|
4 |
+
return (
|
5 |
+
<div className="grid grid-cols-1 gap-12">
|
6 |
+
<QuizzContent />
|
7 |
+
</div>
|
8 |
+
);
|
9 |
+
}
|
assets/css/globals.css
CHANGED
@@ -2,8 +2,8 @@
|
|
2 |
@tailwind components;
|
3 |
@tailwind utilities;
|
4 |
|
5 |
-
body {
|
6 |
-
@apply z-[1] relative bg-black min-h-screen;
|
7 |
font-family: 'Trap', sans-serif;
|
8 |
}
|
9 |
|
|
|
2 |
@tailwind components;
|
3 |
@tailwind utilities;
|
4 |
|
5 |
+
html, body {
|
6 |
+
@apply z-[1] relative bg-black min-h-screen overflow-hidden;
|
7 |
font-family: 'Trap', sans-serif;
|
8 |
}
|
9 |
|
assets/images/sparkles-2.svg
ADDED
|
assets/images/sparkles.svg
ADDED
|
components/button/button.tsx
CHANGED
@@ -21,7 +21,7 @@ export const Button: React.FC<Props> = ({
|
|
21 |
className={classNames(
|
22 |
`font-action px-6 w-full sm:w-auto py-3 sm:px-7 sm:py-4 text-sm sm:text-base uppercase font-semibold flex items-center justify-center gap-2.5 transition-all duration-200 tracking-widest ${className}`,
|
23 |
{
|
24 |
-
"bg-white text-[#0F110F]": theme === "primary",
|
25 |
disabled,
|
26 |
}
|
27 |
)}
|
|
|
21 |
className={classNames(
|
22 |
`font-action px-6 w-full sm:w-auto py-3 sm:px-7 sm:py-4 text-sm sm:text-base uppercase font-semibold flex items-center justify-center gap-2.5 transition-all duration-200 tracking-widest ${className}`,
|
23 |
{
|
24 |
+
"bg-white text-[#0F110F] hover:-translate-y-1": theme === "primary",
|
25 |
disabled,
|
26 |
}
|
27 |
)}
|
components/quizz/card.tsx
ADDED
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from "react";
|
2 |
+
import Image from "next/image";
|
3 |
+
import Confetti from "react-confetti";
|
4 |
+
|
5 |
+
import { PromptType } from "@/utils/type";
|
6 |
+
import { useQuizz } from "./hooks/useQuizz";
|
7 |
+
import classNames from "classnames";
|
8 |
+
|
9 |
+
interface Props {
|
10 |
+
prompt: PromptType;
|
11 |
+
}
|
12 |
+
|
13 |
+
export const Card: React.FC<Props> = ({ prompt }) => {
|
14 |
+
const { onScore, success } = useQuizz();
|
15 |
+
|
16 |
+
return (
|
17 |
+
<div
|
18 |
+
className={classNames(
|
19 |
+
"w-[340px] h-[340px] p-5 relative z-[1] cursor-pointer group",
|
20 |
+
{
|
21 |
+
"!cursor-not-allowed": success,
|
22 |
+
}
|
23 |
+
)}
|
24 |
+
onClick={success ? () => {} : async () => await onScore(prompt.id)}
|
25 |
+
>
|
26 |
+
<div
|
27 |
+
className={classNames(
|
28 |
+
"absolute bottom-0 w-full left-0 px-0 py-0 flex justify-between items-center translate-y-10 transition-all duration-200",
|
29 |
+
{
|
30 |
+
"opacity-0 pointer-events-none": success !== prompt.id,
|
31 |
+
}
|
32 |
+
)}
|
33 |
+
>
|
34 |
+
<p
|
35 |
+
className={classNames("text-[90px] transition-all duration-200", {
|
36 |
+
"-translate-x-1/2 translate-y-1/2": success !== prompt.id,
|
37 |
+
})}
|
38 |
+
>
|
39 |
+
🎉
|
40 |
+
</p>
|
41 |
+
<p
|
42 |
+
className={classNames(
|
43 |
+
"font-extrabold text-white text-7xl pr-6 pb-10 transition-all duration-200",
|
44 |
+
{
|
45 |
+
"translate-y-full": success !== prompt.id,
|
46 |
+
}
|
47 |
+
)}
|
48 |
+
>
|
49 |
+
+1
|
50 |
+
</p>
|
51 |
+
</div>
|
52 |
+
{success === prompt.id && (
|
53 |
+
<Confetti width={340} height={340} numberOfPieces={100} />
|
54 |
+
)}
|
55 |
+
<Image
|
56 |
+
src={`https://huggingface.co/datasets/enzostvs/what-is-the-prompt/resolve/main/${prompt.file_name}?expose=true`}
|
57 |
+
alt="Generated image"
|
58 |
+
width={340}
|
59 |
+
height={340}
|
60 |
+
className={classNames(
|
61 |
+
"object-center object-cover w-full h-full select-none hover:ring-4 ring-white transition-all duration-100",
|
62 |
+
{
|
63 |
+
"ring-4 !ring-[#E5AC00] hover:ring-[#E5AC00]":
|
64 |
+
success === prompt.id,
|
65 |
+
"!ring-transparent": success && success !== prompt.id,
|
66 |
+
}
|
67 |
+
)}
|
68 |
+
/>
|
69 |
+
<div
|
70 |
+
className={classNames(
|
71 |
+
"w-full h-full blur-md lg:blur-md top-0 left-0 absolute z-[-1] opacity-20 transition-all duration-200",
|
72 |
+
{
|
73 |
+
"group-hover:opacity-40": !success,
|
74 |
+
}
|
75 |
+
)}
|
76 |
+
style={{
|
77 |
+
backgroundImage: `url(https://huggingface.co/datasets/enzostvs/what-is-the-prompt/resolve/main/${prompt.file_name}?expose=true)`,
|
78 |
+
}}
|
79 |
+
/>
|
80 |
+
</div>
|
81 |
+
);
|
82 |
+
};
|
components/quizz/hooks/useQuizz.ts
CHANGED
@@ -1,40 +1,66 @@
|
|
1 |
-
import {
|
|
|
2 |
|
3 |
import { PromptType } from "@/utils/type";
|
|
|
4 |
|
5 |
export const useQuizz = () => {
|
6 |
-
const
|
7 |
-
const [loading, setLoading] = useState<boolean>(false);
|
8 |
-
const [error, setError] = useState<string | undefined>(undefined);
|
9 |
-
|
10 |
-
const generate = async () => {
|
11 |
-
setLoading(true);
|
12 |
-
const response = await fetch(`/api/start`, { method: 'GET' });
|
13 |
-
const res = await response.json();
|
14 |
-
|
15 |
-
if (res.error) {
|
16 |
-
setError(res.error);
|
17 |
-
setLoading(false);
|
18 |
-
return;
|
19 |
-
}
|
20 |
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
|
|
|
|
|
|
|
|
25 |
|
26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
|
28 |
-
const
|
29 |
-
|
30 |
-
|
31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
|
33 |
return {
|
34 |
-
|
35 |
loading,
|
36 |
error,
|
37 |
-
|
38 |
-
|
|
|
|
|
39 |
}
|
40 |
}
|
|
|
1 |
+
import { useState } from "react";
|
2 |
+
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
3 |
|
4 |
import { PromptType } from "@/utils/type";
|
5 |
+
import { sleep } from "@/utils";
|
6 |
|
7 |
export const useQuizz = () => {
|
8 |
+
const client = useQueryClient();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
|
10 |
+
const { data: success } = useQuery<string | null>(['success'], () => null, {
|
11 |
+
refetchOnWindowFocus: false,
|
12 |
+
refetchOnMount: false,
|
13 |
+
refetchOnReconnect: false,
|
14 |
+
retry: false,
|
15 |
+
});
|
16 |
+
|
17 |
+
const setSuccess = (s: string | null) => client.setQueryData(['success'], s);
|
18 |
|
19 |
+
const { data: score } = useQuery<number>(['score'], () => 0, {
|
20 |
+
refetchOnWindowFocus: false,
|
21 |
+
refetchOnMount: false,
|
22 |
+
refetchOnReconnect: false,
|
23 |
+
retry: false,
|
24 |
+
});
|
25 |
+
|
26 |
+
const onScore = async (id: string) => {
|
27 |
+
const request = await fetch(`/api/${data?.quest_id}/score`, {
|
28 |
+
method: 'POST',
|
29 |
+
body: JSON.stringify({ promptId: id })
|
30 |
+
});
|
31 |
+
|
32 |
+
const response = await request.clone().json().catch(() => ({}));
|
33 |
+
if (response?.ok) {
|
34 |
+
client.setQueryData(['score'], (score: number | undefined) => {
|
35 |
+
return (score ?? 0) + 1;
|
36 |
+
});
|
37 |
+
setSuccess(id);
|
38 |
+
await sleep(3000);
|
39 |
+
setSuccess(null);
|
40 |
+
await sleep(100);
|
41 |
+
client.invalidateQueries(['quizz']);
|
42 |
+
refetch()
|
43 |
+
}
|
44 |
+
};
|
45 |
|
46 |
+
const { data, isFetching: loading, error, refetch } = useQuery<{ prompts: PromptType[], quest_id: string, question: string }>(['quizz'], async () => {
|
47 |
+
const request = await fetch(`/api/start`, { method: 'POST' });
|
48 |
+
const response = await request.clone().json().catch(() => ({}));
|
49 |
+
return response.data;
|
50 |
+
}, {
|
51 |
+
refetchOnWindowFocus: false,
|
52 |
+
refetchOnMount: false,
|
53 |
+
refetchOnReconnect: false,
|
54 |
+
retry: false,
|
55 |
+
});
|
56 |
|
57 |
return {
|
58 |
+
prompts: data?.prompts ?? [],
|
59 |
loading,
|
60 |
error,
|
61 |
+
question: data?.question,
|
62 |
+
onScore,
|
63 |
+
score,
|
64 |
+
success
|
65 |
}
|
66 |
}
|
components/quizz/index.tsx
CHANGED
@@ -3,51 +3,33 @@
|
|
3 |
import Image from "next/image";
|
4 |
|
5 |
import { useQuizz } from "./hooks/useQuizz";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
|
7 |
-
export const Quizz = () => {
|
8 |
-
const { loading, quizz, error, generate, correctPrompt } = useQuizz();
|
9 |
return (
|
10 |
<>
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
className="w-full h-[450px] rounded-lg overflow-hidden"
|
29 |
-
>
|
30 |
-
<Image
|
31 |
-
src={`https://huggingface.co/datasets/enzostvs/what-is-the-prompt/resolve/main/${item?.file_name}?expose=true`}
|
32 |
-
alt="Generated image"
|
33 |
-
className="object-center object-cover w-full h-full"
|
34 |
-
width={250}
|
35 |
-
height={250}
|
36 |
-
/>
|
37 |
-
</div>
|
38 |
-
))}
|
39 |
-
</div>
|
40 |
-
{/* <div className="bg-gray-600 p-4 rounded-lg text-sm text-white">
|
41 |
-
{JSON.stringify(quizz)}
|
42 |
-
</div> */}
|
43 |
-
</>
|
44 |
-
)}
|
45 |
-
<button
|
46 |
-
className="bg-blue-500 text-white text-sm rounded font-semibold px-4 py-2"
|
47 |
-
onClick={generate}
|
48 |
-
>
|
49 |
-
Start a quizz
|
50 |
-
</button>
|
51 |
</>
|
52 |
);
|
53 |
};
|
|
|
3 |
import Image from "next/image";
|
4 |
|
5 |
import { useQuizz } from "./hooks/useQuizz";
|
6 |
+
import { PromptType } from "@/utils/type";
|
7 |
+
import { Card } from "./card";
|
8 |
+
|
9 |
+
export const QuizzContent = () => {
|
10 |
+
const { loading, prompts, question } = useQuizz();
|
11 |
+
|
12 |
+
if (loading) return <p>Loading...</p>;
|
13 |
|
|
|
|
|
14 |
return (
|
15 |
<>
|
16 |
+
<p className="font-extrabold bg-clip-text text-transparent bg-gradient-to-b from-white to-white/80 text-2xl lg:text-[40px] text-center lg:!leading-snug relative">
|
17 |
+
<span className="font-action text-4xl lg:text-7xl !leading-3 text-white translate-y-2 lg:translate-y-4 inline-block pr-2">
|
18 |
+
“
|
19 |
+
</span>
|
20 |
+
{question}
|
21 |
+
<span className="font-action text-4xl lg:text-7xl text-white !leading-3 relative translate-y-5 lg:translate-y-12 pl-2 inline-block">
|
22 |
+
”
|
23 |
+
</span>
|
24 |
+
</p>
|
25 |
+
<p className="text-white/70 font-normal text-lg lg:text-xl text-center">
|
26 |
+
Guess which image is related to the prompt below
|
27 |
+
</p>
|
28 |
+
<div className="flex flex-col lg:flex-row items-center justify-center gap-6 lg:gap-16">
|
29 |
+
{prompts?.map((prompt: PromptType, i: number) => (
|
30 |
+
<Card prompt={prompt} key={i} />
|
31 |
+
))}
|
32 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
</>
|
34 |
);
|
35 |
};
|
components/quizz/layout-score.tsx
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client";
|
2 |
+
|
3 |
+
import { useQuizz } from "./hooks/useQuizz";
|
4 |
+
|
5 |
+
export const LayoutScore = ({ className }: { className?: string }) => {
|
6 |
+
const { score } = useQuizz();
|
7 |
+
return (
|
8 |
+
<p className={`text-white font-extrabold text-xl ${className}`}>
|
9 |
+
{score ?? 0} point{(score ?? 0) > 1 && "s"} 🎉
|
10 |
+
</p>
|
11 |
+
);
|
12 |
+
};
|
components/react-query/hydrate.tsx
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client";
|
2 |
+
import React from "react";
|
3 |
+
import { Hydrate as HydrationBoundary } from "@tanstack/react-query";
|
4 |
+
|
5 |
+
function Hydrate(props: { children: React.ReactNode }) {
|
6 |
+
return <HydrationBoundary>{props.children}</HydrationBoundary>;
|
7 |
+
}
|
8 |
+
|
9 |
+
export default Hydrate;
|
components/react-query/providers.tsx
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client";
|
2 |
+
import * as React from "react";
|
3 |
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
4 |
+
|
5 |
+
export default function Providers({ children }: { children: React.ReactNode }) {
|
6 |
+
const [queryClient] = React.useState(() => new QueryClient());
|
7 |
+
|
8 |
+
return (
|
9 |
+
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
10 |
+
);
|
11 |
+
}
|
data/dev.db
CHANGED
Binary files a/data/dev.db and b/data/dev.db differ
|
|
next.config.js
CHANGED
@@ -17,7 +17,7 @@ const nextConfig = {
|
|
17 |
hostname: "aeiljuispo.cloudimg.io",
|
18 |
},
|
19 |
],
|
20 |
-
}
|
21 |
}
|
22 |
|
23 |
module.exports = nextConfig
|
|
|
17 |
hostname: "aeiljuispo.cloudimg.io",
|
18 |
},
|
19 |
],
|
20 |
+
}
|
21 |
}
|
22 |
|
23 |
module.exports = nextConfig
|
package-lock.json
CHANGED
The diff for this file is too large to render.
See raw diff
|
|
package.json
CHANGED
@@ -11,10 +11,14 @@
|
|
11 |
"dependencies": {
|
12 |
"@huggingface/hub": "^0.12.1",
|
13 |
"@prisma/client": "^5.6.0",
|
|
|
14 |
"classnames": "^2.3.2",
|
|
|
15 |
"next": "14.0.2",
|
|
|
16 |
"prisma": "^5.6.0",
|
17 |
"react": "^18",
|
|
|
18 |
"react-dom": "^18",
|
19 |
"react-use": "^17.4.0"
|
20 |
},
|
|
|
11 |
"dependencies": {
|
12 |
"@huggingface/hub": "^0.12.1",
|
13 |
"@prisma/client": "^5.6.0",
|
14 |
+
"@tanstack/react-query": "^4.32.6",
|
15 |
"classnames": "^2.3.2",
|
16 |
+
"install": "^0.13.0",
|
17 |
"next": "14.0.2",
|
18 |
+
"npm": "^10.2.4",
|
19 |
"prisma": "^5.6.0",
|
20 |
"react": "^18",
|
21 |
+
"react-confetti": "^6.1.0",
|
22 |
"react-dom": "^18",
|
23 |
"react-use": "^17.4.0"
|
24 |
},
|
prisma/schema.prisma
CHANGED
@@ -4,11 +4,17 @@ generator client {
|
|
4 |
|
5 |
datasource db {
|
6 |
provider = "sqlite"
|
7 |
-
url = "file
|
8 |
}
|
9 |
|
10 |
model Prompt {
|
11 |
-
id
|
12 |
prompt String
|
13 |
file_name String
|
14 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
datasource db {
|
6 |
provider = "sqlite"
|
7 |
+
url = "file:../data/dev.db"
|
8 |
}
|
9 |
|
10 |
model Prompt {
|
11 |
+
id String @id @default(uuid())
|
12 |
prompt String
|
13 |
file_name String
|
14 |
}
|
15 |
+
|
16 |
+
model Quest {
|
17 |
+
id String @id @default(uuid())
|
18 |
+
prompt_id_correct String
|
19 |
+
created_at DateTime @default(now())
|
20 |
+
}
|
tailwind.config.ts
CHANGED
@@ -10,7 +10,7 @@ const config: Config = {
|
|
10 |
extend: {
|
11 |
fontFamily: {
|
12 |
sans: ["Trap", "serif"],
|
13 |
-
action: [
|
14 |
},
|
15 |
backgroundImage: {
|
16 |
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
|
|
|
10 |
extend: {
|
11 |
fontFamily: {
|
12 |
sans: ["Trap", "serif"],
|
13 |
+
action: ['var(--font-inter)', "sans-serif"]
|
14 |
},
|
15 |
backgroundImage: {
|
16 |
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
|
utils/index.ts
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
utils/type.ts
CHANGED
@@ -4,6 +4,7 @@ export interface QuizzType {
|
|
4 |
|
5 |
export interface PromptType {
|
6 |
prompt: string,
|
|
|
7 |
is_correct: boolean,
|
8 |
file_name?: string,
|
9 |
}
|
|
|
4 |
|
5 |
export interface PromptType {
|
6 |
prompt: string,
|
7 |
+
id: string,
|
8 |
is_correct: boolean,
|
9 |
file_name?: string,
|
10 |
}
|