Spaces:
Runtime error
Runtime error
Esteves Enzo
commited on
Commit
•
96d9078
1
Parent(s):
68b3088
update style + method to send file
Browse files- app/api/check-hair-color/route.ts +5 -9
- components/form/hook.ts +7 -2
- components/form/index.tsx +6 -3
- components/form/main_color.tsx +36 -0
- components/form/progress.tsx +22 -0
- components/form/results.tsx +16 -20
app/api/check-hair-color/route.ts
CHANGED
@@ -1,22 +1,18 @@
|
|
1 |
import fs from 'fs';
|
2 |
|
3 |
import PipelineSingleton from './pipeline';
|
|
|
4 |
|
5 |
export async function POST(request: Request) {
|
6 |
const res = await request.formData();
|
7 |
-
const file = res.get('file') as
|
8 |
|
9 |
const classifier: any = await PipelineSingleton.getInstance();
|
10 |
if (!classifier) {
|
11 |
return Response.json({ success: false })
|
12 |
}
|
13 |
-
|
14 |
-
|
15 |
-
const
|
16 |
-
// convert buffer to url data
|
17 |
-
const fileData = Buffer.from(fileBuffer).toString('base64');
|
18 |
-
const fileUrl = `data:${file.type};base64,${fileData}`;
|
19 |
-
console.log(fileUrl);
|
20 |
-
const result = await classifier(fileUrl, { topk: 4 });
|
21 |
return Response.json({ data: result })
|
22 |
}
|
|
|
1 |
import fs from 'fs';
|
2 |
|
3 |
import PipelineSingleton from './pipeline';
|
4 |
+
import { RawImage } from '@xenova/transformers';
|
5 |
|
6 |
export async function POST(request: Request) {
|
7 |
const res = await request.formData();
|
8 |
+
const file = res.get('file') as Blob;
|
9 |
|
10 |
const classifier: any = await PipelineSingleton.getInstance();
|
11 |
if (!classifier) {
|
12 |
return Response.json({ success: false })
|
13 |
}
|
14 |
+
// read blob as data url
|
15 |
+
const file_url = await RawImage.fromBlob(file)
|
16 |
+
const result = await classifier(file_url, { topk: 4 });
|
|
|
|
|
|
|
|
|
|
|
17 |
return Response.json({ data: result })
|
18 |
}
|
components/form/hook.ts
CHANGED
@@ -9,7 +9,9 @@ export const useClassifier = () => {
|
|
9 |
setLoading(true);
|
10 |
const formData = new FormData();
|
11 |
|
12 |
-
|
|
|
|
|
13 |
const res = await fetch("/api/check-hair-color", {
|
14 |
method: "POST",
|
15 |
body: formData,
|
@@ -20,9 +22,12 @@ export const useClassifier = () => {
|
|
20 |
setLoading(false);
|
21 |
}
|
22 |
|
|
|
|
|
23 |
return {
|
24 |
results,
|
25 |
loading,
|
26 |
-
submit
|
|
|
27 |
}
|
28 |
}
|
|
|
9 |
setLoading(true);
|
10 |
const formData = new FormData();
|
11 |
|
12 |
+
const fileToBlob = new Blob([file], { type: file.type });
|
13 |
+
|
14 |
+
formData.append("file", fileToBlob);
|
15 |
const res = await fetch("/api/check-hair-color", {
|
16 |
method: "POST",
|
17 |
body: formData,
|
|
|
22 |
setLoading(false);
|
23 |
}
|
24 |
|
25 |
+
const reset = () => setResults([]);
|
26 |
+
|
27 |
return {
|
28 |
results,
|
29 |
loading,
|
30 |
+
submit,
|
31 |
+
reset
|
32 |
}
|
33 |
}
|
components/form/index.tsx
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
"use client";
|
2 |
-
import React from "react";
|
3 |
import { useRef } from "react";
|
4 |
import { TiUpload, TiTrash } from "react-icons/ti";
|
5 |
|
@@ -10,7 +10,7 @@ import { useClassifier } from "./hook";
|
|
10 |
export const FormUpload = () => {
|
11 |
const inputRef = useRef<HTMLInputElement>(null);
|
12 |
const [file, setFile] = React.useState<File | null>(null);
|
13 |
-
const { loading, results, submit } = useClassifier();
|
14 |
|
15 |
const handleUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
16 |
const file = e.target.files?.[0];
|
@@ -19,6 +19,8 @@ export const FormUpload = () => {
|
|
19 |
}
|
20 |
};
|
21 |
|
|
|
|
|
22 |
return (
|
23 |
<>
|
24 |
<div className="w-full">
|
@@ -32,7 +34,7 @@ export const FormUpload = () => {
|
|
32 |
<div
|
33 |
className="w-full h-full bg-slate-900 mx-auto rounded-xl relative bg-cover bg-center flex items-start justify-end p-3 gap-2"
|
34 |
style={{
|
35 |
-
backgroundImage: `url(${
|
36 |
}}
|
37 |
>
|
38 |
<div
|
@@ -41,6 +43,7 @@ export const FormUpload = () => {
|
|
41 |
e.stopPropagation();
|
42 |
e.preventDefault();
|
43 |
setFile(null);
|
|
|
44 |
}}
|
45 |
>
|
46 |
<TiTrash className="w-6 h-6 text-slate-700 group-hover:text-red-600 mx-auto" />
|
|
|
1 |
"use client";
|
2 |
+
import React, { useMemo } from "react";
|
3 |
import { useRef } from "react";
|
4 |
import { TiUpload, TiTrash } from "react-icons/ti";
|
5 |
|
|
|
10 |
export const FormUpload = () => {
|
11 |
const inputRef = useRef<HTMLInputElement>(null);
|
12 |
const [file, setFile] = React.useState<File | null>(null);
|
13 |
+
const { loading, results, submit, reset } = useClassifier();
|
14 |
|
15 |
const handleUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
16 |
const file = e.target.files?.[0];
|
|
|
19 |
}
|
20 |
};
|
21 |
|
22 |
+
const fileUrl = useMemo(() => file && URL.createObjectURL(file), [file]);
|
23 |
+
|
24 |
return (
|
25 |
<>
|
26 |
<div className="w-full">
|
|
|
34 |
<div
|
35 |
className="w-full h-full bg-slate-900 mx-auto rounded-xl relative bg-cover bg-center flex items-start justify-end p-3 gap-2"
|
36 |
style={{
|
37 |
+
backgroundImage: `url(${fileUrl})`,
|
38 |
}}
|
39 |
>
|
40 |
<div
|
|
|
43 |
e.stopPropagation();
|
44 |
e.preventDefault();
|
45 |
setFile(null);
|
46 |
+
reset();
|
47 |
}}
|
48 |
>
|
49 |
<TiTrash className="w-6 h-6 text-slate-700 group-hover:text-red-600 mx-auto" />
|
components/form/main_color.tsx
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useMemo } from "react";
|
2 |
+
|
3 |
+
import { ResultsInterface } from "./results";
|
4 |
+
|
5 |
+
export const MainColor = ({ result }: { result: ResultsInterface }) => {
|
6 |
+
const mainColor = useMemo(() => {
|
7 |
+
switch (result.label) {
|
8 |
+
case "black hair":
|
9 |
+
return "#000000";
|
10 |
+
case "blond hair":
|
11 |
+
return "#ffcc00";
|
12 |
+
case "brown hair":
|
13 |
+
return "#663300";
|
14 |
+
case "white hair":
|
15 |
+
return "#cccccc";
|
16 |
+
case "red hair":
|
17 |
+
return "#ff6600";
|
18 |
+
case "completely bald":
|
19 |
+
return "transparent";
|
20 |
+
}
|
21 |
+
}, [result]);
|
22 |
+
|
23 |
+
return (
|
24 |
+
<div className="flex items-center justify-between">
|
25 |
+
<p className="text-slate-400 text-sm font-semibold capitalize">
|
26 |
+
{result.label}
|
27 |
+
</p>
|
28 |
+
<div
|
29 |
+
className="w-[20px] h-[20px] rounded-full border border-slate-950 ring-[1px] ring-slate-400"
|
30 |
+
style={{
|
31 |
+
backgroundColor: mainColor,
|
32 |
+
}}
|
33 |
+
/>
|
34 |
+
</div>
|
35 |
+
);
|
36 |
+
};
|
components/form/progress.tsx
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { ResultsInterface } from "./results";
|
2 |
+
|
3 |
+
export const Progress = ({ result }: { result: ResultsInterface }) => {
|
4 |
+
return (
|
5 |
+
<div key={result.label}>
|
6 |
+
<div className="flex items-center justify-between border-b border-dashed border-slate-500/20 pb-1">
|
7 |
+
<p className="text-slate-400 font-medium text-xs">{result.label}</p>
|
8 |
+
<p className="text-slate-300 font-bold text-xs">
|
9 |
+
{(result.score * 100).toFixed(3)}%
|
10 |
+
</p>
|
11 |
+
</div>
|
12 |
+
<div className="w-full h-1.5 rounded-full bg-slate-700 mt-2">
|
13 |
+
<div
|
14 |
+
className="bg-gradient-to-r from-indigo-600 to-teal-500 h-full blur-[1px] rounded-full"
|
15 |
+
style={{
|
16 |
+
width: `${result.score * 100}%`,
|
17 |
+
}}
|
18 |
+
></div>
|
19 |
+
</div>
|
20 |
+
</div>
|
21 |
+
);
|
22 |
+
};
|
components/form/results.tsx
CHANGED
@@ -1,12 +1,21 @@
|
|
1 |
import Image from "next/image";
|
|
|
|
|
|
|
2 |
|
3 |
import HFSad from "@/assets/hfsad.svg";
|
4 |
-
|
|
|
|
|
|
|
|
|
5 |
label: string;
|
6 |
score: number;
|
7 |
}
|
8 |
|
9 |
export const Results = ({ results }: { results: ResultsInterface[] }) => {
|
|
|
|
|
10 |
return (
|
11 |
<div className="w-full flex-col flex gap-5">
|
12 |
{results?.length > 0 ? (
|
@@ -14,25 +23,12 @@ export const Results = ({ results }: { results: ResultsInterface[] }) => {
|
|
14 |
<p className="text-white font-bold text-xl text-center uppercase tracking-widest">
|
15 |
Results
|
16 |
</p>
|
17 |
-
{
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
<p className="text-slate-300 font-bold text-xs">
|
24 |
-
{(result.score * 100).toFixed(3)}%
|
25 |
-
</p>
|
26 |
-
</div>
|
27 |
-
<div className="w-full h-1.5 rounded-full bg-slate-700 mt-2">
|
28 |
-
<div
|
29 |
-
className="bg-gradient-to-r from-indigo-600 to-teal-500 h-full blur-[1px] rounded-full"
|
30 |
-
style={{
|
31 |
-
width: `${result.score * 100}%`,
|
32 |
-
}}
|
33 |
-
></div>
|
34 |
-
</div>
|
35 |
-
</div>
|
36 |
))}
|
37 |
</>
|
38 |
) : (
|
|
|
1 |
import Image from "next/image";
|
2 |
+
import { useMemo, useState } from "react";
|
3 |
+
import { TbChevronDown } from "react-icons/tb";
|
4 |
+
import classNames from "classnames";
|
5 |
|
6 |
import HFSad from "@/assets/hfsad.svg";
|
7 |
+
|
8 |
+
import { Progress } from "./progress";
|
9 |
+
import { MainColor } from "./main_color";
|
10 |
+
|
11 |
+
export interface ResultsInterface {
|
12 |
label: string;
|
13 |
score: number;
|
14 |
}
|
15 |
|
16 |
export const Results = ({ results }: { results: ResultsInterface[] }) => {
|
17 |
+
const primaryResult = useMemo(() => results[0], [results]);
|
18 |
+
|
19 |
return (
|
20 |
<div className="w-full flex-col flex gap-5">
|
21 |
{results?.length > 0 ? (
|
|
|
23 |
<p className="text-white font-bold text-xl text-center uppercase tracking-widest">
|
24 |
Results
|
25 |
</p>
|
26 |
+
<MainColor result={primaryResult} />
|
27 |
+
<p className="text-slate-600 text-left uppercase text-xs font-semibold">
|
28 |
+
Details
|
29 |
+
</p>
|
30 |
+
{results?.slice(1, results.length)?.map((result) => (
|
31 |
+
<Progress result={result} key={result.label} />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
))}
|
33 |
</>
|
34 |
) : (
|