Skip to content

Commit

Permalink
Merge branch 'main' into feat/home-page
Browse files Browse the repository at this point in the history
  • Loading branch information
CindyBSydney authored Aug 8, 2024
2 parents 41d8b62 + 90ffdff commit deba8ab
Show file tree
Hide file tree
Showing 16 changed files with 1,150 additions and 151 deletions.
55 changes: 55 additions & 0 deletions app/api/compare/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import ApplicationSingleton from "@/app/app";
import { NextResponse } from "next/server";
import {
type PreTrainedModel,
type Processor,
RawImage,
} from "@xenova/transformers";
import { cosineSimilarity } from "./util";

export async function POST(request: Request) {
try {
const formData = await request.formData();

// Get the two images from the form data
const query_image = formData.get("query_image") as File;
const ans_image = formData.get("ans_image") as File;

// Create Blobs from the images
const query_image_blob = new Blob([query_image], {
type: query_image.type,
});
const ans_image_blob = new Blob([ans_image], { type: ans_image.type });

const [processor, vision_model]: [Processor, PreTrainedModel] =
await ApplicationSingleton.getInstance();

// Read the two images as raw images
const rawImageQuery = await RawImage.fromBlob(query_image_blob);
const rawImageAns = await RawImage.fromBlob(ans_image_blob);

// Tokenize the two images
const tokenizedImageQuery = await processor(rawImageQuery);
const tokenizedImageAns = await processor(rawImageAns);

// Encode the two images
const { image_embeds: embedsQuery } = await vision_model(
tokenizedImageQuery
);
const { image_embeds: embedsAns } = await vision_model(tokenizedImageAns);

const similarity = cosineSimilarity(
Object.values(embedsQuery.data),
Object.values(embedsAns.data)
);

return new NextResponse(JSON.stringify({ okay: "okay", similarity }), {
status: 200,
});
} catch (error) {
console.error(error);
return new NextResponse(JSON.stringify({ error }), {
status: 500,
});
}
}
25 changes: 25 additions & 0 deletions app/api/compare/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Contains helper functions for comparison
export const cosineSimilarity = (
query_embeds: number[],
ans_embeds: number[]
) => {
if (query_embeds.length !== ans_embeds.length) {
throw new Error("Embeddings must be of the same length");
}

let dotProduct = 0;
let normQuery = 0;
let normAns = 0;

for (let i = 0; i < query_embeds.length; ++i) {
const queryValue = query_embeds[i];
const dbValue = ans_embeds[i];

dotProduct += queryValue * dbValue;
normQuery += queryValue * queryValue;
normAns += dbValue * dbValue;
}

const similarity = dotProduct / (Math.sqrt(normQuery) * Math.sqrt(normAns));
return similarity;
};
46 changes: 46 additions & 0 deletions app/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {
AutoProcessor,
CLIPVisionModelWithProjection,
type PreTrainedModel,
type Processor,
} from "@xenova/transformers";

// Use the Singleton pattern to enable lazy construction of the pipeline.
// NOTE: We wrap the class in a function to prevent code duplication.
const S = () =>
class ApplicationSingleton {
static model_id = "Xenova/clip-vit-base-patch16";
static processor: Promise<Processor> | null = null;
static vision_model: Promise<PreTrainedModel> | null = null;

static async getInstance() {
if (this.processor === null) {
this.processor = AutoProcessor.from_pretrained(this.model_id);
}

if (this.vision_model === null) {
this.vision_model = CLIPVisionModelWithProjection.from_pretrained(
this.model_id,
{
quantized: false,
}
);
}

return Promise.all([this.processor, this.vision_model]);
}
};

let ApplicationSingleton;
if (process.env.NODE_ENV !== "production") {
// When running in development mode, attach the pipeline to the
// global object so that it's preserved between hot reloads.
// For more information, see https://vercel.com/guides/nextjs-prisma-postgres
if (!(global as any).ApplicationSingleton) {
(global as any).ApplicationSingleton = S();
}
ApplicationSingleton = (global as any).ApplicationSingleton;
} else {
ApplicationSingleton = S();
}
export default ApplicationSingleton as any;
1 change: 0 additions & 1 deletion app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// pages/index.tsx
import React from 'react';
import AppBar from '../components/layout/AppBar';
import CustomCard from '../components/ui/CustomCard';
Expand Down
74 changes: 69 additions & 5 deletions app/play/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,81 @@
"use client"
import React from 'react';
import GameScreen from '@/components/game/GameScreen'; // Adjust the path if necessary
"use client";
import React, { useState } from 'react';
import GameScreen from '@/components/game/GameScreen';
import MatchScreen from '@/components/game/MatchScreen';
import MintScreen from '@/components/game/MintScreen';
import NFTScreen from '@/components/game/NFTScreen';
import Leaderboard from '@/components/game/Leaderboard';
import AppBar from '@/components/layout/AppBar';

const Play = () => {
const [currentScreen, setCurrentScreen] = useState('game'); // Default to 'game'

const handleGameScreenComplete = () => {
setCurrentScreen('match');
};

const handleMatchScreenComplete = () => {
setCurrentScreen('mint');
};

const handleMintScreenComplete = () => {
setCurrentScreen('nft');
};

const handleNFTScreenComplete = () => {
setCurrentScreen('leaderboard');
};

const handleViewLeaderboard = () => {
setCurrentScreen('leaderboard');
};

const handleGoBack = () => {
switch (currentScreen) {
case 'leaderboard':
setCurrentScreen('nft');
break;
case 'nft':
setCurrentScreen('mint');
break;
case 'mint':
setCurrentScreen('match');
break;
case 'match':
setCurrentScreen('game');
break;
default:
setCurrentScreen('game');
break;
}
};

const handleRestartGame = () => {
setCurrentScreen('game');
};

return (
<div>
<AppBar />
<MatchScreen />
{currentScreen === 'game' && <GameScreen onComplete={handleGameScreenComplete} />}
{currentScreen === 'match' && <MatchScreen onComplete={handleMatchScreenComplete} />}
{currentScreen === 'mint' &&
<MintScreen
onComplete={handleMintScreenComplete}
onViewLeaderboard={handleViewLeaderboard}
/>
}
{currentScreen === 'nft' &&
<NFTScreen
onViewLeaderboard={handleViewLeaderboard}
onRestart={handleRestartGame}
/>
}
{currentScreen === 'leaderboard' &&
<Leaderboard
onGoBack={handleGoBack}
onRestart={handleRestartGame}
/>
}
</div>
);
};
Expand Down
21 changes: 13 additions & 8 deletions components/game/GameScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import ScoreDisplay from '../ui/scoredisplay';
import DrawingCanvas from '../ui/drawingcanvas';
import { CircularProgressbar, buildStyles } from 'react-circular-progressbar';
import 'react-circular-progressbar/dist/styles.css';
import MyToolBar from '../ui/toolbar';

interface GameScreenProps {
onComplete: () => void;
}

const GameScreen: React.FC = () => {
const GameScreen: React.FC<GameScreenProps> = ({ onComplete }) => {
const [score, setScore] = useState(0);
const [timeLeft, setTimeLeft] = useState(60);
const [generatedText, setGeneratedText] = useState('Draw a cat');
Expand All @@ -24,15 +26,16 @@ const GameScreen: React.FC = () => {

const handleSubmit = () => {
setScore(Math.floor(Math.random() * 100));
onComplete(); // Trigger the transition to the next screen
};

const timerProgress = (timeLeft / 60) * 100;

return (
<div className="flex flex-col items-center p-4">
<div className="flex justify-between w-full mb-4 mt-10">
<div className="flex flex-col items-center justify-center p-4 md:p-8 lg:p-12 h-screen ">
<div className="flex flex-col md:flex-row items-center justify-between w-full mb-4 mt-10 p-4 rounded-lg">
<ScoreDisplay score={score} />
<div className="w-16 h-16">
<div className="w-16 h-16 sm:w-20 sm:h-20 md:w-24 md:h-24 lg:w-28 lg:h-28">
<CircularProgressbar
value={timerProgress}
text={`${timeLeft}`}
Expand All @@ -44,9 +47,11 @@ const GameScreen: React.FC = () => {
/>
</div>
</div>
<div className="flex flex-col items-center w-full">
<div className="text-xl font-semibold mb-4 animate-pulse">{generatedText}</div>
<DrawingCanvas ref={canvasRef} />
<div className="flex flex-col items-center w-full mb-16">
<div className="text-lg sm:text-xl md:text-2xl lg:text-3xl font-semibold mb-4 text-center">{generatedText}</div>
<div className="w-full h-96 sm:h-[40vh] md:h-[50vh] lg:h-[60vh]">
<DrawingCanvas ref={canvasRef} />
</div>
</div>
<Button onClick={handleSubmit} className="mt-4 animate-bounce">Submit</Button>
</div>
Expand Down
26 changes: 19 additions & 7 deletions components/game/Leaderboard.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
"use client";
import React from 'react';
import LeaderboardTable from '../ui/leaderboardtable';
import { Button } from '../ui/button';

const Leaderboard: React.FC = () => {
return (
<div className="flex flex-col items-center p-4">
<h1 className="text-2xl mb-4">Leaderboard</h1>
<LeaderboardTable />
</div>
);
interface LeaderboardProps {
onGoBack: () => void;
onRestart: () => void;
}

const Leaderboard: React.FC<LeaderboardProps> = ({ onGoBack, onRestart }) => {
return (
<div className="flex flex-col items-center p-4">
<h1 className="text-2xl mb-4 mt-10">Leaderboard</h1>
<LeaderboardTable />
<div className='flex flex-row gap-4'>
<Button onClick={onGoBack} className="mt-4">Go Back</Button>
<Button onClick={onRestart} className="mt-4">Restart Game</Button>
</div>

</div>
);
};

export default Leaderboard;
54 changes: 31 additions & 23 deletions components/game/MatchScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,40 @@
"use client";
import React from 'react';
import { Button } from '../ui/button';
import ImageComparison from '../ui/imagecomparison';

const MatchScreen: React.FC = () => {
return (
<div className="flex flex-col items-center p-6 mt-10 min-h-screen">
{/* Container for Diagonal Cards */}
<div className="relative w-full max-w-lg p-6 bg-white border border-gray-200 rounded shadow-lg">
<div className="absolute inset-0 opacity-30 rounded-lg bg-slate-200"></div>
<div className="relative z-10">
<div className="text-center mb-4">
<p className="text-xl font-semibold text-gray-800">Score: <span className="text-green-600">85%</span></p>
</div>
{/* Diagonal Image Cards */}
<ImageComparison />
{/* Score Display */}
interface MatchScreenProps {
onComplete: () => void;
}

{/* Mint Button */}
<div className='flex justify-center w-full'>
<Button className="mt-6 px-6 py-3">
Mint Drawing
</Button>
</div>

</div>
</div>
const MatchScreen: React.FC<MatchScreenProps> = ({ onComplete }) => {
const handleMintButtonClick = () => {
onComplete(); // Trigger the transition to the MintScreen
};

return (
<div className="flex flex-col items-center p-6 mt-10 min-h-screen">
{/* Container for Diagonal Cards */}
<div className="relative w-full max-w-lg p-6 bg-white border border-gray-200 rounded shadow-lg">
<div className="absolute inset-0 opacity-30 rounded-lg bg-slate-200"></div>
<div className="relative z-10">
<div className="text-center mb-4">
<p className="text-xl font-semibold text-gray-800">
Score: <span className="text-green-600">85%</span>
</p>
</div>
{/* Diagonal Image Cards */}
<ImageComparison />
{/* Mint Button */}
<div className='flex justify-center w-full'>
<Button onClick={handleMintButtonClick} className="mt-6 px-6 py-3">
Mint Drawing
</Button>
</div>
</div>
);
</div>
</div>
);
};

export default MatchScreen;
Loading

0 comments on commit deba8ab

Please sign in to comment.