function renderFoodClassificationInterface() {
// Defining the HTML content as a template literal
const htmlContent = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Food Classification Tool</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet"/>
<style>
/* CSS Variables for easy theme management */
:root {
--primary-color: #04AA6D;
--secondary-color: #026D4D;
--background-color: #1f1f1f;
--container-bg: #282828;
--text-color: #ffffff;
--input-bg: #f0f0f0;
--input-text: #333333;
--error-color: #e74c3c;
--table-header-bg: #04AA6D;
--table-row-bg: #3e3e3e;
--highlight-bg: rgba(255, 235, 59, 0.2);
--transition-duration: 0.3s;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Roboto', sans-serif;
background-color: var(--background-color);
color: var(--text-color);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 20px;
}
.container {
background-color: var(--container-bg);
border-radius: 12px;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
padding: 30px;
width: 100%;
max-width: 1000px;
animation: fadeIn 1s ease;
}
header {
text-align: center;
margin-bottom: 30px;
}
header h1 {
font-size: 2.5rem;
font-weight: 700;
color: var(--primary-color);
margin-bottom: 10px;
transition: color var(--transition-duration);
}
header h1:hover {
color: var(--secondary-color);
}
header h2 {
font-size: 1.5rem;
font-weight: 500;
color: #ccc;
}
.input-group {
display: flex;
flex-direction: column;
gap: 15px;
margin-bottom: 30px;
align-items: center;
}
.input-field {
width: 100%;
padding: 12px 15px;
border: 2px solid var(--primary-color);
border-radius: 8px;
font-size: 1rem;
background-color: var(--input-bg);
color: var(--input-text);
transition: border-color var(--transition-duration), box-shadow var(--transition-duration);
}
.input-field:focus {
border-color: var(--secondary-color);
outline: none;
box-shadow: 0 0 5px var(--secondary-color);
}
.classify-button {
padding: 12px 20px;
border: none;
border-radius: 8px;
background-color: var(--primary-color);
color: var(--text-color);
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: background-color var(--transition-duration), transform 0.2s ease;
width: 100%;
max-width: 300px;
}
.classify-button:hover {
background-color: var(--secondary-color);
transform: translateY(-2px);
}
.classify-button:active {
transform: translateY(0);
}
.result-section {
margin-top: 30px;
display: flex;
flex-direction: row;
gap: 20px;
align-items: flex-start;
justify-content: center;
animation: fadeIn 1s ease;
}
.image-preview {
flex: 1;
max-width: 300px;
display: flex;
justify-content: center;
align-items: center;
transition: transform var(--transition-duration);
}
.image-preview img {
max-width: 100%;
height: auto;
border-radius: 8px;
transition: opacity 0.5s ease;
}
.image-preview img.hidden {
opacity: 0;
}
.image-preview img.visible {
opacity: 1;
}
.results-table {
flex: 2;
width: 100%;
max-width: 600px;
overflow-x: auto;
}
.result-table {
width: 100%;
border-collapse: collapse;
background-color: var(--table-row-bg);
border-radius: 8px;
overflow: hidden;
transition: transform var(--transition-duration);
}
.result-table th, .result-table td {
padding: 12px 15px;
text-align: left;
transition: background-color var(--transition-duration);
}
.result-table th {
background-color: var(--table-header-bg);
color: var(--text-color);
font-weight: 700;
font-size: 1rem;
}
.result-table tr:nth-child(even) {
background-color: #424242;
}
.result-table tr:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.result-table tr.most-accurate {
background-color: var(--highlight-bg);
}
.errorText {
color: var(--error-color);
font-weight: 600;
margin-top: 20px;
text-align: center;
}
.facts-section {
margin-top: 30px;
width: 100%;
max-width: 1000px;
background-color: var(--container-bg);
border-radius: 8px;
padding: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
animation: fadeIn 1s ease;
}
.facts-section h3 {
margin-bottom: 10px;
color: var(--primary-color);
}
.facts-section p {
line-height: 1.6;
}
.facts-section.hidden {
display: none;
}
footer {
margin-top: 40px;
padding: 15px;
text-align: center;
background-color: var(--container-bg);
color: #aaa;
border-radius: 8px;
font-size: 0.9rem;
}
.spinner {
border: 4px solid rgba(255, 255, 255, 0.1);
width: 50px;
height: 50px;
border-radius: 50%;
border-left-color: var(--primary-color);
animation: spin 1s linear infinite;
margin: 30px auto;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@media (max-width: 800px) {
.result-section {
flex-direction: column;
align-items: center;
}
.image-preview {
max-width: 100%;
}
.results-table {
max-width: 100%;
}
}
@media (max-width: 600px) {
header h1 {
font-size: 2rem;
}
header h2 {
font-size: 1.2rem;
}
.result-table th, .result-table td {
padding: 10px 12px;
font-size: 0.9rem;
}
.spinner {
width: 40px;
height: 40px;
}
.facts-section {
padding: 15px;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>Food Classification Tool</h1>
<h2>Classify Food by Image Upload</h2>
</header>
<div class="input-group">
<input type="file" id="imageUpload" accept="image/*" class="input-field" onchange="previewImage();" aria-label="Upload Food Image" />
<button onclick="classifyFoodByUpload()" class="classify-button">Classify Uploaded Image</button>
</div>
<div id="classificationResult" class="result-section">
<div class="image-preview">
<img id="imageDisplay" src="#" alt="Uploaded Image Preview" class="hidden"/>
</div>
<div class="results-table">
<!-- Results will be injected here -->
</div>
</div>
</div>
<div id="factsSection" class="facts-section hidden">
<h3>Facts About the Food</h3>
<p id="foodFacts">Loading facts...</p>
</div>
<footer>
Sometimes the AI analysis may be incorrect.
</footer>
<script>
const AI_MODELS = [
{
name: "Kaludi/food-category-classification-v2.0",
displayName: "Kaludi Food Classifier"
},
{
name: "nateraw/food",
displayName: "nateraw Food Classifier"
},
{
name: "WinKawaks/vit-tiny-patch16-224",
displayName: "WinKawaks ViT-Tiny"
},
{
name: "microsoft/resnet-50",
displayName: "Microsoft ResNet-50"
},
{
name: "google/vit-base-patch16-224",
displayName: "Google ViT-Base"
},
{
name: "facebook/deit-base-patch16-224",
displayName: "Facebook DeiT-Base"
}
];
const HUGGINGFACE_API_TOKEN = "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"; // Replace with your actual HuggingFace API token
const OPENAI_API_KEY = "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"; // Replace with your actual OpenAI API key
async function classifyFoodByUpload() {
const fileInput = document.getElementById('imageUpload');
const file = fileInput.files[0];
const resultsContainer = document.querySelector('.results-table');
const factsSection = document.getElementById('factsSection');
const foodFacts = document.getElementById('foodFacts');
if (!file) {
displayError('classificationResult', 'ERROR: Please select an image to upload.');
factsSection.classList.add('hidden');
return;
}
displayLoading('classificationResult');
foodFacts.textContent = "Loading facts...";
factsSection.classList.add('hidden');
const reader = new FileReader();
reader.onload = async function(e) {
try {
const imageData = e.target.result;
const classificationPromises = AI_MODELS.map(model => queryFoodClassification(imageData, model.name));
const classificationResults = await Promise.all(classificationPromises);
let mostAccurateIndex = -1;
let highestConfidence = -1;
let topLabel = "";
classificationResults.forEach((result, index) => {
if (result && result.length > 0) {
if (result[0].score > highestConfidence) {
highestConfidence = result[0].score;
mostAccurateIndex = index;
topLabel = result[0].label;
}
}
});
if (mostAccurateIndex === -1) {
displayError('classificationResult', 'ERROR: Unable to classify the image.');
return;
}
displayResult('classificationResult', formatFoodClassificationResults(classificationResults, mostAccurateIndex));
await fetchFoodFacts(topLabel);
} catch (error) {
displayError('classificationResult', \`ERROR: \${error.message}\`);
console.error(error);
}
};
reader.onerror = function() {
displayError('classificationResult', 'ERROR: Failed to read the image file.');
};
reader.readAsArrayBuffer(file);
}
async function queryFoodClassification(imageData, modelName) {
const API_URL = \`https://api-inference.huggingface.co/models/\${modelName}\`;
if (!HUGGINGFACE_API_TOKEN || HUGGINGFACE_API_TOKEN === "False") {
throw new Error("Please replace the HuggingFace API token with your actual token.");
}
try {
const response = await fetch(API_URL, {
headers: {
Authorization: \`Bearer \${HUGGINGFACE_API_TOKEN}\`,
"Content-Type": "application/octet-stream"
},
method: "POST",
body: imageData
});
if (!response.ok) {
const errorDetails = await response.text();
throw new Error(\`HuggingFace API error: \${response.status} \${response.statusText} - \${errorDetails}\`);
}
const result = await response.json();
if (result.error) {
throw new Error(\`HuggingFace API error: \${result.error}\`);
}
return result;
} catch (error) {
console.error(\`Error with model \${modelName}: \`, error);
return null;
}
}
function formatFoodClassificationResults(data, mostAccurateIndex) {
if (!Array.isArray(data) || data.length === 0) {
return '<p>No classification results available.</p>';
}
let tableHTML = \`
<table class="result-table">
<thead>
<tr>
<th>Model</th>
<th>Label</th>
<th>Confidence</th>
</tr>
</thead>
<tbody>
\`;
data.forEach((classification, index) => {
if (classification && classification.length > 0) {
const topClassification = classification[0];
const displayName = AI_MODELS[index].displayName;
const confidencePercentage = (topClassification.score * 100).toFixed(2) + '%';
const rowClass = index === mostAccurateIndex ? 'most-accurate' : '';
tableHTML += \`
<tr class="\${rowClass}">
<td>\${displayName}</td>
<td>\${topClassification.label}</td>
<td>\${confidencePercentage}</td>
</tr>
\`;
} else {
const displayName = AI_MODELS[index].displayName;
tableHTML += \`
<tr>
<td>\${displayName}</td>
<td colspan="2">Failed to retrieve results.</td>
</tr>
\`;
}
});
tableHTML += '</tbody></table>';
return tableHTML;
}
function previewImage() {
const fileInput = document.getElementById('imageUpload');
const file = fileInput.files[0];
const imageDisplay = document.getElementById('imageDisplay');
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
imageDisplay.src = e.target.result;
imageDisplay.classList.remove('hidden');
imageDisplay.classList.add('visible');
};
reader.readAsDataURL(file);
} else {
imageDisplay.classList.add('hidden');
imageDisplay.classList.remove('visible');
}
}
function displayResult(containerId, tableHTML) {
const container = document.getElementById(containerId);
container.querySelector('.results-table').innerHTML = tableHTML;
}
function displayError(containerId, message) {
const container = document.getElementById(containerId);
container.querySelector('.results-table').innerHTML = \`<p class="errorText">\${message}</p>\`;
const factsSection = document.getElementById('factsSection');
factsSection.classList.add('hidden');
}
function displayLoading(containerId) {
const container = document.getElementById(containerId);
container.querySelector('.results-table').innerHTML = \`<div class="spinner" role="status" aria-label="Loading"></div>\`;
}
async function fetchFoodFacts(foodLabel) {
const factsSection = document.getElementById('factsSection');
const foodFacts = document.getElementById('foodFacts');
foodFacts.textContent = "Fetching facts...";
factsSection.classList.remove('hidden');
try {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': \`Bearer \${OPENAI_API_KEY}\`
},
body: JSON.stringify({
model: "gpt-4",
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: \`Please tell me about this food: \${foodLabel}\` }
],
max_tokens: 150,
temperature: 0.7
})
});
if (!response.ok) {
const errorDetails = await response.text();
throw new Error(\`OpenAI API error: \${response.status} \${response.statusText} - \${errorDetails}\`);
}
const data = await response.json();
if (!data.choices || data.choices.length === 0) {
throw new Error("No response from OpenAI API.");
}
const facts = data.choices[0].message.content.trim();
foodFacts.textContent = facts;
} catch (error) {
console.error(error);
foodFacts.textContent = "Failed to fetch facts. Please try again later.";
}
}
renderFoodClassificationInterface();
</script>
</body>
</html>
`;
document.open();
document.write(htmlContent);
document.close();
}
renderFoodClassificationInterface();