Rice_Quality / index.html
CodeRuler's picture
Upload folder using huggingface_hub
356cbb3 verified
Raw
History Blame Contribute Delete
19.1 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Rice Quality AI | Smart Grading</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<style>
:root {
--primary: #2e7d32;
--primary-light: #60ad5e;
--accent: #fbc02d;
--bg: #f5f5f5;
--card: #ffffff;
--text: #333333;
}
body {
font-family: 'Inter', sans-serif;
background-color: var(--bg);
color: var(--text);
margin: 0;
padding: 20px;
display: flex;
justify-content: center;
}
.container {
max-width: 900px;
width: 100%;
}
header {
text-align: center;
margin-bottom: 30px;
}
h1 { color: var(--primary); margin-bottom: 5px; }
p.subtitle { color: #666; margin-top: 0; }
.card {
background: var(--card);
padding: 30px;
border-radius: 16px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
margin-bottom: 20px;
}
#upload-zone {
border: 2px dashed #ccc;
border-radius: 12px;
padding: 40px 20px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
background: #fafafa;
}
#upload-zone:hover {
border-color: var(--primary);
background: #f1f8e9;
}
#file-input { display: none; }
.btn {
background: var(--primary);
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
width: 100%;
font-size: 16px;
margin-top: 20px;
transition: background 0.2s;
}
.btn:disabled { background: #ccc; cursor: not-allowed; }
.btn:hover:not(:disabled) { background: #1b5e20; }
#preview {
max-width: 100%;
border-radius: 8px;
margin-top: 20px;
display: none;
}
#results {
display: none;
animation: fadeIn 0.5s;
}
.visual-container {
display: flex;
gap: 20px;
margin-bottom: 30px;
flex-wrap: wrap;
}
.image-box {
flex: 2;
min-width: 300px;
text-align: center;
}
.legend-box {
flex: 1;
min-width: 200px;
background: #f9f9f9;
padding: 15px;
border-radius: 12px;
border: 1px solid #eee;
align-self: center;
}
.legend-title {
font-weight: 700;
font-size: 14px;
margin-bottom: 10px;
color: #444;
border-bottom: 2px solid var(--primary);
display: inline-block;
}
.legend-item {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
font-size: 13px;
}
.color-swatch {
width: 16px;
height: 16px;
border-radius: 4px;
}
.result-item {
display: flex;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px solid #eee;
}
.result-item:last-child { border-bottom: none; }
.label { font-weight: 600; color: #555; }
.value { color: var(--primary); font-weight: 700; }
.grade-badge {
background: var(--accent);
padding: 4px 12px;
border-radius: 20px;
font-size: 14px;
}
.loader {
display: none;
text-align: center;
margin: 20px 0;
}
.spinner {
width: 30px;
height: 30px;
border: 4px solid #f3f3f3;
border-top: 4px solid var(--primary);
border-radius: 50%;
animation: spin 1s linear infinite;
display: inline-block;
}
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
.lang-pills {
display: flex;
gap: 10px;
justify-content: center;
margin-bottom: 20px;
}
.pill {
font-size: 14px;
background: #e0e0e0;
padding: 6px 15px;
border: 2px solid transparent;
border-radius: 20px;
color: #666;
cursor: pointer;
font-weight: 600;
transition: all 0.2s;
}
.pill:hover { background: #d0d0d0; }
.pill.active {
background: var(--primary);
color: white;
border-color: var(--primary-light);
}
#annotated-preview {
max-width: 100%;
border-radius: 8px;
border: 2px solid var(--primary);
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>🌾 <span data-i18n="title">Rice Quality AI</span></h1>
<p class="subtitle" data-i18n="subtitle">Instant Grading & Market Value</p>
<div class="lang-pills">
<button class="pill active" onclick="setLanguage('en')">English</button>
<button class="pill" onclick="setLanguage('hi')">हिंदी</button>
<button class="pill" onclick="setLanguage('pa')">ਪੰਜਾਬੀ</button>
</div>
</header>
<div class="card">
<div id="upload-zone" onclick="document.getElementById('file-input').click()">
<div id="upload-prompt">
<p data-i18n="upload_prompt">📸 Tap to Capture or Upload Image</p>
<small data-i18n="upload_hint">For best results, use a white background</small>
</div>
<img id="preview" src="#" alt="Preview">
</div>
<input type="file" id="file-input" accept="image/*">
<button id="analyze-btn" class="btn" disabled data-i18n="analyze_btn">Analyze Sample</button>
</div>
<div id="loader" class="loader">
<div class="spinner"></div>
<p data-i18n="loading">AI Engine Scanning...</p>
</div>
<div id="results" class="card">
<div class="visual-container">
<div class="image-box">
<p style="font-weight: 600; color: #555; margin-bottom: 10px;" data-i18n="visual_proof">Visual Analysis Result</p>
<img id="annotated-preview" src="#" alt="Annotated Result">
</div>
<div class="legend-box">
<span class="legend-title" data-i18n="legend">Legend</span>
<div id="legend-items">
<!-- Legend items will be injected here -->
</div>
</div>
</div>
<div class="result-item">
<span class="label" data-i18n="res_variety">Detected Variety</span>
<span id="res-variety" class="value">-</span>
</div>
<div class="result-item">
<span class="label" data-i18n="res_grade">Quality Grade</span>
<span id="res-grade" class="grade-badge">-</span>
</div>
<div class="result-item">
<span class="label" data-i18n="res_head">Head Rice</span>
<span id="res-head" class="value">-</span>
</div>
<div class="result-item">
<span class="label" data-i18n="res_count">Head Rice Count</span>
<span id="res-count" class="value" style="color: #2e7d32;">-</span>
</div>
<div class="result-item">
<span class="label" data-i18n="res_broken">Broken Grains</span>
<span id="res-broken" class="value">-</span>
</div>
<div class="result-item">
<span class="label" data-i18n="res_foreign">Foreign Matter</span>
<span id="res-foreign" class="value">-</span>
</div>
<div class="result-item">
<span class="label" data-i18n="res_price">Est. Market Value</span>
<span id="res-price" class="value" style="color: #d32f2f;">-</span>
</div>
<div class="result-item">
<span class="label" data-i18n="res_ratio">L/W Ratio</span>
<span id="res-ratio" class="value">-</span>
</div>
<div class="result-item" id="res-status-row">
<span class="label" data-i18n="res_status">Status</span>
<span id="res-status" class="value">-</span>
</div>
</div>
</div>
<script>
const translations = {
en: {
title: "Rice Quality AI",
subtitle: "Instant Grading & Market Value",
upload_prompt: "📸 Tap to Capture or Upload Image",
upload_hint: "For best results, use a white background",
analyze_btn: "Analyze Sample",
loading: "AI Engine Scanning...",
visual_proof: "Visual Analysis Result",
legend: "Category Legend",
res_variety: "Detected Variety",
res_grade: "Quality Grade",
res_head: "Head Rice",
res_count: "Head Rice Count",
res_broken: "Broken Grains",
res_foreign: "Foreign Matter",
res_price: "Est. Market Value",
res_ratio: "L/W Ratio",
res_status: "Status",
classes: ['Lower Admixture', 'Broken', 'Chalky', 'Damaged', 'Dehusked', 'Discoloured', 'Inorganic Foreign', 'Organic Foreign', 'Good Grain', 'Red Grain']
},
hi: {
title: "चावल गुणवत्ता AI",
subtitle: "त्वरित ग्रेडिंग और बाजार मूल्य",
upload_prompt: "📸 फोटो लें या इमेज अपलोड करें",
upload_hint: "बेहतर परिणामों के लिए सफेद बैकग्राउंड का उपयोग करें",
analyze_btn: "नमूने का विश्लेषण करें",
loading: "AI इंजन स्कैन कर रहा है...",
visual_proof: "दृश्य विश्लेषण परिणाम",
legend: "श्रेणी स्पष्टीकरण",
res_variety: "चावल की किस्म",
res_grade: "गुणवत्ता श्रेणी",
res_head: "साबुत चावल",
res_count: "साबुत चावल की संख्या",
res_broken: "टूटे हुए चावल",
res_foreign: "बाहरी तत्व",
res_price: "अनुमानित बाजार मूल्य",
res_ratio: "औसत लंबाई/चौड़ाई अनुपात",
res_status: "किस्म की स्थिति",
classes: ['निम्न मिश्रण', 'टूटा हुआ', 'चाक जैसा', 'क्षतिग्रस्त', 'बिना छिलके वाला', 'फीका पड़ा हुआ', 'अकार्बनिक विदेशी', 'जैविक विदेशी', 'अच्छा दाना', 'लाल दाना']
},
pa: {
title: "ਚਾਵਲ ਗੁਣਵੱਤਾ AI",
subtitle: "ਤੁਰੰਤ ਗ੍ਰੇਡਿੰਗ ਅਤੇ ਬਾਜ਼ਾਰ ਮੁੱਲ",
upload_prompt: "📸 ਫੋਟੋ ਲਓ ਜਾਂ ਚਿੱਤਰ ਅਪਲੋਡ ਕਰੋ",
upload_hint: "ਵਧੀਆ ਨਤੀਜਿਆਂ ਲਈ ਚਿੱਟੇ ਬੈਕਗ੍ਰਾਊਂਡ ਦੀ ਵਰਤੋਂ ਕਰੋ",
analyze_btn: "ਨਮੂਨੇ ਦਾ ਵਿਸ਼ਲੇਸ਼ਣ ਕਰੋ",
loading: "AI ਇੰਜਣ ਸਕੈਨ ਕਰ ਰਿਹਾ ਹੈ...",
visual_proof: "ਵਿਜ਼ੂਅਲ ਵਿਸ਼ਲੇਸ਼ਣ ਨਤੀਜਾ",
legend: "ਸ਼੍ਰੇਣੀ ਦੀ ਵਿਆਖਿਆ",
res_variety: "ਚਾਵਲ ਦੀ ਕਿਸਮ",
res_grade: "ਗੁਣਵੱਤਾ ਦਰਜਾ",
res_head: "ਸਾਬਤ ਚਾਵਲ",
res_count: "ਸਾਬਤ ਚਾਵਲ ਦੀ ਗਿਣਤੀ",
res_broken: "ਟੁੱਟੇ ਹੋਏ ਚਾਵਲ",
res_foreign: "ਬਾਹਰੀ ਤੱਤ",
res_price: "ਅਨੁਮਾਨਿਤ ਬਾਜ਼ਾਰ ਮੁੱਲ",
res_ratio: "ਔਸਤ ਲੰਬਾਈ/ਚੌੜਾਈ ਅਨੁਪਾਤ",
res_status: "ਕਿਸਮ ਦੀ ਸਥਿਤੀ",
classes: ['ਘੱਟ ਮਿਸ਼ਰਣ', 'ਟੁੱਟਿਆ ਹੋਇਆ', 'ਚਾਕ ਵਰਗਾ', 'ਖਰਾਬ', 'ਬਿਨਾਂ ਛਿਲਕੇ ਵਾਲਾ', 'ਫਿੱਕਾ ਪਿਆ', 'ਅਕਾਰਬਨਿਕ ਬਾਹਰੀ', 'ਜੈਵਿਕ ਬਾਹਰੀ', 'ਵਧੀਆ ਦਾਣਾ', 'ਲਾਲ ਦਾਣਾ']
}
};
// Diverse high-contrast palette matching the backend (HEX format)
const colors = [
"#E6194B", // Red (admixture)
"#FFE119", // Yellow (broken)
"#0082C8", // Blue (chalky)
"#F58230", // Orange (damaged)
"#911EB4", // Purple (dehusked)
"#46F0F0", // Cyan (discoloured)
"#F032E6", // Magenta (inorganic)
"#D2F53C", // Lime (organic)
"#3CB44B", // Green (good grain)
"#FABED4" // Pink (red grain)
];
let currentLang = 'en';
function setLanguage(lang) {
currentLang = lang;
// Update buttons
document.querySelectorAll('.pill').forEach(btn => {
btn.classList.toggle('active', btn.getAttribute('onclick').includes(`'${lang}'`));
});
// Update text content
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
el.innerText = translations[lang][key];
});
renderLegend();
}
function renderLegend() {
const legendContainer = document.getElementById('legend-items');
legendContainer.innerHTML = '';
translations[currentLang].classes.forEach((className, index) => {
const item = document.createElement('div');
item.className = 'legend-item';
item.innerHTML = `
<div class="color-swatch" style="background: ${colors[index % colors.length]}"></div>
<span>${className}</span>
`;
legendContainer.appendChild(item);
});
}
// Initialize legend
renderLegend();
const fileInput = document.getElementById('file-input');
const uploadZone = document.getElementById('upload-zone');
const preview = document.getElementById('preview');
const prompt = document.getElementById('upload-prompt');
const analyzeBtn = document.getElementById('analyze-btn');
const results = document.getElementById('results');
const loader = document.getElementById('loader');
fileInput.addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
preview.src = e.target.result;
preview.style.display = 'block';
prompt.style.display = 'none';
analyzeBtn.disabled = false;
results.style.display = 'none';
}
reader.readAsDataURL(file);
}
});
analyzeBtn.addEventListener('click', async () => {
const file = fileInput.files[0];
if (!file) return;
const formData = new FormData();
formData.append('file', file);
analyzeBtn.disabled = true;
loader.style.display = 'block';
results.style.display = 'none';
try {
const response = await fetch('/analyze', {
method: 'POST',
body: formData
});
const data = await response.json();
document.getElementById('res-variety').innerText = `${data.variety} (${data.confidence})`;
document.getElementById('res-grade').innerText = data.grade;
document.getElementById('res-head').innerText = data.metrics.head_rice;
document.getElementById('res-count').innerText = `${data.metrics.head_count} ${currentLang === 'en' ? 'grains' : currentLang === 'hi' ? 'दाने' : 'ਦਾਣੇ'}`;
document.getElementById('res-broken').innerText = data.metrics.broken_grains;
document.getElementById('res-foreign').innerText = data.metrics.foreign_matter;
document.getElementById('res-price').innerText = data.estimated_value;
document.getElementById('res-ratio').innerText = data.metrics.avg_length_width_ratio;
document.getElementById('res-status').innerText = data.status;
// Show annotated image
const annPreview = document.getElementById('annotated-preview');
annPreview.src = `data:image/jpeg;base64,${data.annotated_image}`;
annPreview.style.display = 'inline-block';
loader.style.display = 'none';
results.style.display = 'block';
analyzeBtn.disabled = false;
} catch (error) {
alert('Error analyzing image. Please try again.');
loader.style.display = 'none';
analyzeBtn.disabled = false;
}
});
</script>
</body>
</html>