changed design. added dockerfile

This commit is contained in:
2026-05-01 01:41:34 +02:00
parent 476bb1f045
commit d1384be6f4
7 changed files with 155 additions and 79 deletions
+19
View File
@@ -0,0 +1,19 @@
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
+7 -4
View File
@@ -1,13 +1,16 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head>
<head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>cnn_website</title> <title>cnn_website</title>
</head> </head>
<body>
<body>
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/main.jsx"></script> <script type="module" src="/src/main.jsx"></script>
</body> </body>
</html> </html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

+69 -36
View File
@@ -1,73 +1,77 @@
.custom-button { .custom-button {
height: 40px; height: 40px;
padding: 10px 20px; padding: 10px 20px;
background: #333; background: rgb(25, 100, 126);
color: rgb(225, 225, 225); color: rgb(255, 255, 255);
cursor: pointer; cursor: pointer;
border-radius: 6px; border-radius: 20px;
border: 1px solid black; border: 1px solid rgb(25, 100, 126);
user-select: none;
} }
.custom-button:hover { .custom-button:hover {
border-color: white; border-color: white;
color: white;
} }
.custom-button.inactive { .custom-button.inactive {
background: #333; opacity: 0.3;
color: rgb(117, 117, 117);
cursor: default; cursor: default;
} }
.custom-button.inactive:hover { .custom-button.inactive:hover {
border-color: black; border-color: rgb(25, 100, 126);
} }
.explanation-box a, #site-headline {
span, margin-left: 100px;
p { margin-bottom: 50px;
line-height: 1.7; color: rgb(47, 168, 208);
font-size: 17px; font-weight: bold;
font-size: 4rem;
position: relative;
width: fit-content;
}
#site-headline::before {
content: "";
position: absolute;
bottom: 0;
right: 0;
height: 4px;
width: 100%;
border-radius: 20px;
background: rgb(47, 168, 208);
} }
a { a {
color: rgb(0, 0, 0);
text-decoration: none; text-decoration: none;
border-bottom: 1px solid black; border-bottom: 1px solid rgb(255, 255, 255);
padding-bottom: 2px; padding-bottom: 2px;
transition: border-color 0.15s; transition: border-color 0.15s;
} }
a:hover { a:hover {
border-color: white; border-color: #c59eff;
} }
.explanation-box { .explanation-box {
flex: 1; flex: 1;
min-width: 0; min-width: 300px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex-wrap: wrap; flex-wrap: wrap;
overflow: hidden; overflow: hidden;
border: 3px solid rgb(48, 62, 74);
margin: 50px;
padding: 50px;
border-radius: 12px; border-radius: 12px;
background: rgb(97, 125, 151); margin: 180px 50px 0px 50px;
} }
.image-box { .image-box {
border: 2px solid black;
background: rgb(69, 69, 69);
height: 400px; height: 400px;
width: 600px;
margin-top: 40px; margin-top: 40px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: 8px;
background: rgba(0, 0, 0, 0.5);
cursor: pointer; cursor: pointer;
} }
@@ -77,26 +81,55 @@ a:hover {
.image-box img { .image-box img {
border: 2px solid black; border: 2px solid black;
border-Radius: 8px; border-Radius: 10px;
} }
.image-box:hover p {
color: white;
}
.image-box img:hover { .image-box img:hover {
border-color: white; border-color: white;
} }
.image-box.active {
background: none;
border: none;
}
.content-block { .content-block {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 20px; gap: 20px;
height: 40px; height: 40px;
} }
/* HTML: <div class="loader"></div> */
.loader {
width: 85px;
height: 25px;
--g1: conic-gradient(from 90deg at left 3px top 3px, #0000 90deg, #fff 0);
--g2: conic-gradient(from -90deg at bottom 3px right 3px, #0000 90deg, #fff 0);
background: var(--g1), var(--g1), var(--g1), var(--g2), var(--g2), var(--g2);
background-position: left, center, right;
background-repeat: no-repeat;
animation: l8 1s infinite;
}
@keyframes l8 {
0% {
background-size: 25px 100%, 25px 100%, 25px 100%
}
20% {
background-size: 25px 50%, 25px 100%, 25px 100%
}
40% {
background-size: 25px 50%, 25px 50%, 25px 100%
}
60% {
background-size: 25px 100%, 25px 50%, 25px 50%
}
80% {
background-size: 25px 100%, 25px 100%, 25px 50%
}
100% {
background-size: 25px 100%, 25px 100%, 25px 100%
}
}
+27 -23
View File
@@ -7,26 +7,24 @@ function Bird_CNN() {
? "http://134.60.154.7:8000" ? "http://134.60.154.7:8000"
: "http://134.60.154.7:8000"; : "http://134.60.154.7:8000";
const boxRef = useRef(null);
const [file, setFile] = useState(null); const [file, setFile] = useState(null);
const [birdClass, setBirdClass] = useState(null); const [birdClass, setBirdClass] = useState(null);
const [confidence, setConfidence] = useState(null); const [confidence, setConfidence] = useState(null);
const [error, setError] = useState(false); const [error, setError] = useState(false);
const [preview, setPreview] = useState(null); const [preview, setPreview] = useState("\\cherry_bird.jpeg");
const [loading, setLoading] = useState(false);
const fileInputRef = useRef(null); const fileInputRef = useRef(null);
const uploadButton = useRef(null); const uploadButton = useRef(null);
const scrollRef = useRef(null);
const handleImageDivClick = () => { const handleImageDivClick = () => {
fileInputRef.current.click(); fileInputRef.current.click();
} }
const handleImage = (e) => { const handleImage = (e) => {
setFile(e.target.files[0]);
if (e.target.files[0]) { if (e.target.files[0]) {
setFile(e.target.files[0]);
setPreview(URL.createObjectURL(e.target.files[0])); setPreview(URL.createObjectURL(e.target.files[0]));
boxRef.current.classList.add("active");
uploadButton.current.classList.remove("inactive"); uploadButton.current.classList.remove("inactive");
} }
setConfidence(null); setConfidence(null);
@@ -36,15 +34,20 @@ function Bird_CNN() {
const sendImage = async (e) => { const sendImage = async (e) => {
if (!file) return; if (!file) return;
scrollRef.current.scrollIntoView({ behavior: "smooth" });
const formData = new FormData(); const formData = new FormData();
formData.append("file", file); formData.append("file", file);
try { try {
setLoading(true);
const response = await fetch(`${apiUrl}/predict`, { const response = await fetch(`${apiUrl}/predict`, {
method: "POST", method: "POST",
body: formData, body: formData,
}); });
setLoading(false);
if (!response.ok) { if (!response.ok) {
setError(true); setError(true);
} }
@@ -65,23 +68,23 @@ function Bird_CNN() {
return ( return (
<> <>
<div className='site-box' style={{ marginTop: "150px" }}> <div className='site-box' style={{ marginTop: "150px" }}>
<h1 id='site-headline' style={{ marginLeft: "100px", marginBottom: "50px" }}>Bird Species Expert</h1> <h1 id='site-headline'>Bird Species Expert</h1>
<div className='content-box' style={{ display: "flex" }}> <div className='content-box' style={{ display: "flex", flexWrap: "wrap", justifyContent: "center" }}>
<div className='explanation-box'> <div className='explanation-box left'>
<h2 style={{ color: "Blue" }}>Explanation</h2> <h2 style={{ color: "rgb(47, 168, 208)" }}>Explanation</h2>
<span>Select an image and upload it to our bird expert. You will receive a classification and how certain the expert is with her opinion. Be aware that the expert may not be always right.</span> <span>Select an image and upload it to our bird expert. You will receive a classification and how certain the expert is with her opinion. Be aware that the expert may not be always right.</span>
</div> </div>
<div className='request-box' style={{ height: "700px", width: "700px", display: "flex", flexDirection: "column", alignItems: "center", flex: "0 0 auto" }}> <div className='request-box' style={{ height: "800px", width: "700px", display: "flex", flexDirection: "column", alignItems: "center", flex: "0 0 auto", margin: "100px 50px 0px 50px" }}>
<div className='input-box' style={{ width: "500px", height: "40px", display: "flex", justifyContent: "space-evenly", alignItems: "center" }}> <div className='input-box' style={{ marginBottom: "80px", width: "500px", height: "40px", display: "flex", justifyContent: "space-evenly", alignItems: "center" }}>
<div> <div>
<input ref={fileInputRef} type="file" id='fileUpload' accept="image/jpeg" onChange={handleImage} style={{ display: "none" }} /> <input disabled={loading} ref={fileInputRef} type="file" id='fileUpload' accept="image/jpeg" onChange={handleImage} style={{ display: "none" }} />
<label htmlFor="fileUpload" className="custom-button"> <label htmlFor="fileUpload" className="custom-button">
Select an image Select Image
</label> </label>
</div> </div>
<div> <div>
<button id='button-send' onClick={sendImage} style={{ display: "none" }} /> <button id='button-send' onClick={sendImage} style={{ display: "none" }} disabled={loading} />
<label htmlFor="button-send" className="custom-button inactive" ref={uploadButton}> <label htmlFor="button-send" className="custom-button inactive" ref={uploadButton}>
Ask Expert Ask Expert
</label> </label>
@@ -89,19 +92,20 @@ function Bird_CNN() {
</div> </div>
<div ref={boxRef} className='image-box' onClick={handleImageDivClick}> <div className='image-box' onClick={handleImageDivClick} disabled={loading}>
{!preview && <p>Select Image</p>}
{preview && (
<img <img
src={preview} src={preview}
alt="preview" alt="preview"
style={{ height: "400px" }} style={{ maxHeight: "400px", height: "auto", userSelect: "none", maxWidth: "700px" }}
/> />
)} </div>
<div ref={scrollRef} style={{ paddingTop: "10px", marginTop: "30px", width: "85px", height: "35px", display: "flex", justifyContent: "center", minHeight: "25px", minWidth: "85px" }}>
{loading && <div className='loader'></div>}
</div> </div>
<div className='response-block' style={{ width: "80%", margin: "40px", display: "flex", flexDirection: "column", alignItems: "flex-start", gap: "10px" }}> <div className='response-block' style={{ marginTop: "40px", width: "80%", margin: "40px", display: "flex", flexDirection: "column", alignItems: "flex-start", gap: "10px" }}>
<div className='content-block class'> <div className='content-block class'>
<h3>Bird Species: </h3> <h3>Bird Species: </h3>
<p id='bird-class-text'>{birdClass}</p> <p id='bird-class-text'>{birdClass}</p>
@@ -114,7 +118,7 @@ function Bird_CNN() {
</div> </div>
</div> </div>
<div className='explanation-box'> <div className='explanation-box right'>
<h3>Model Architecture</h3> <h3>Model Architecture</h3>
<div style={{ display: "inline" }}> <div style={{ display: "inline" }}>
<span>The model used for classification is a convolutional neural network (CNN) based on depthwise separable convolutions, as introduced in the </span> <span>The model used for classification is a convolutional neural network (CNN) based on depthwise separable convolutions, as introduced in the </span>
@@ -123,7 +127,7 @@ function Bird_CNN() {
</div> </div>
</div> </div>
</div> </div>
</div> </div >
</> </>
); );
} }
+3 -4
View File
@@ -5,8 +5,7 @@
gap: 20px; gap: 20px;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background-color: rgb(33, 122, 194); background-color: rgb(25, 100, 126);
border: 1px solid black; border-bottom-left-radius: 40px;
border-bottom-left-radius: 8px; border-bottom-right-radius: 40px;
border-bottom-right-radius: 8px;
} }
+19 -1
View File
@@ -1,3 +1,21 @@
html {
font-family: system-ui, sans-serif;
color: rgb(255, 255, 255);
font-size: 1rem;
line-height: 1.7;
}
body { body {
background: rgb(130, 170, 205); background: rgb(4, 0, 10);
}
a {
color: rgb(255, 255, 255);
}
h1,
h2,
h3,
h4 {
font-family: 'Times New Roman', serif;
} }