240519P's picture
Update index.html
1e89649 verified
Raw
History Blame Contribute Delete
6.13 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SnapChef Edibility Checker</title>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@latest"></script>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 30px;
font-family: Arial, sans-serif;
background: linear-gradient(135deg, #effaf3, #d9f2e3);
color: #18392b;
}
.container {
width: 100%;
max-width: 520px;
padding: 36px;
text-align: center;
background: white;
border-radius: 24px;
box-shadow: 0 14px 40px rgba(0, 0, 0, 0.12);
}
h1 {
margin-top: 0;
margin-bottom: 8px;
font-size: 32px;
}
.subtitle {
margin-bottom: 26px;
color: #5f746a;
line-height: 1.5;
}
.upload-box {
display: block;
padding: 25px;
border: 2px dashed #4ba870;
border-radius: 16px;
cursor: pointer;
background: #f4fbf6;
transition: 0.2s;
}
.upload-box:hover {
background: #eaf7ee;
}
input[type="file"] {
display: none;
}
#preview {
display: none;
width: 100%;
max-height: 320px;
margin-top: 22px;
object-fit: contain;
border-radius: 16px;
background: #f2f2f2;
}
button {
width: 100%;
margin-top: 22px;
padding: 14px;
border: none;
border-radius: 12px;
background: #218c52;
color: white;
font-size: 17px;
font-weight: bold;
cursor: pointer;
}
button:hover {
background: #187441;
}
button:disabled {
background: #9bad9f;
cursor: not-allowed;
}
#status {
margin-top: 20px;
color: #5f746a;
}
#result {
display: none;
margin-top: 22px;
padding: 20px;
border-radius: 16px;
font-size: 20px;
font-weight: bold;
}
.edible {
color: #176b3a;
background: #e4f8eb;
border: 1px solid #78ca95;
}
.inedible {
color: #a32929;
background: #fdeaea;
border: 1px solid #e89898;
}
.confidence {
display: block;
margin-top: 8px;
font-size: 15px;
font-weight: normal;
}
.warning {
margin-top: 24px;
font-size: 12px;
line-height: 1.5;
color: #78877f;
}
</style>
</head>
<body>
<main class="container">
<h1>SnapChef</h1>
<p class="subtitle">
Upload a food image to check whether it appears edible or inedible
</p>
<label class="upload-box" for="imageInput">
Click here to choose a food image
</label>
<input
id="imageInput"
type="file"
accept="image/png, image/jpeg, image/webp"
>
<img id="preview" alt="selected food image">
<button id="predictButton" disabled>
Check Edibility
</button>
<p id="status">Loading model...</p>
<div id="result"></div>
<p class="warning">
This prediction is produced by an AI model and should not replace proper
food safety checks
</p>
</main>
<script>
let model = null
const imageInput = document.getElementById("imageInput")
const preview = document.getElementById("preview")
const predictButton = document.getElementById("predictButton")
const statusText = document.getElementById("status")
const resultBox = document.getElementById("result")
async function loadModel() {
try {
model = await tf.loadGraphModel("./model.json")
statusText.textContent = "Model ready"
if (imageInput.files.length > 0) {
predictButton.disabled = false
}
} catch (error) {
console.error(error)
statusText.textContent = "Failed to load the model"
}
}
imageInput.addEventListener("change", function () {
const file = imageInput.files[0]
if (!file) {
return
}
preview.src = URL.createObjectURL(file)
preview.style.display = "block"
resultBox.style.display = "none"
if (model) {
predictButton.disabled = false
}
})
predictButton.addEventListener("click", async function () {
if (!model || !preview.src) {
return
}
predictButton.disabled = true
statusText.textContent = "Analysing image..."
resultBox.style.display = "none"
let inputTensor
let predictionTensor
try {
inputTensor = tf.tidy(function () {
const imageTensor = tf.browser.fromPixels(preview)
const resizedTensor = tf.image.resizeBilinear(
imageTensor,
[224, 224]
)
return resizedTensor
.toFloat()
.expandDims(0)
})
predictionTensor = model.predict(inputTensor)
const predictionData = await predictionTensor.data()
const inedibleProbability = predictionData[0]
let label
let confidence
if (inedibleProbability >= 0.5) {
label = "Inedible"
confidence = inedibleProbability
resultBox.className = "inedible"
} else {
label = "Edible"
confidence = 1 - inedibleProbability
resultBox.className = "edible"
}
resultBox.innerHTML = `
${label}
<span class="confidence">
Confidence: ${(confidence * 100).toFixed(2)}%
</span>
`
resultBox.style.display = "block"
statusText.textContent = "Analysis complete"
} catch (error) {
console.error(error)
statusText.textContent = "Prediction failed"
} finally {
if (inputTensor) {
inputTensor.dispose()
}
if (predictionTensor) {
predictionTensor.dispose()
}
predictButton.disabled = false
}
})
loadModel()
</script>
</body>
</html>