-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into feat/home-page
- Loading branch information
Showing
16 changed files
with
1,150 additions
and
151 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
Oops, something went wrong.