Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<title>Soil Report Analysis & Recommendations Generator</title> | |
<!-- Include Chart.js library --> | |
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
<style> | |
:root { | |
--primary-color: #007BFF; | |
--primary-dark: #0056b3; | |
--secondary-color: #2c5282; | |
--bg-color: #f4f4f4; | |
--text-color: #333; | |
--white: #ffffff; | |
--error-bg: #f8d7da; | |
--error-text: #721c24; | |
--container-bg: #ffffff; | |
--shadow: 0 2px 8px rgba(0, 0, 0, 0.1); | |
/* Distinct section background colors */ | |
--section-bg-overview: #e0f7fa; | |
--section-bg-composition: #ffe0b2; | |
--section-bg-nutrients: #c8e6c9; | |
--section-bg-ph: #f3e5f5; | |
--section-bg-recommendations: #d1c4e9; | |
} | |
/* Base Styles */ | |
body { | |
font-family: 'Roboto', sans-serif; | |
background-color: var(--bg-color); | |
margin: 0; | |
padding: 0; | |
color: var(--text-color); | |
} | |
.container { | |
max-width: 900px; | |
margin: 50px auto; | |
background: var(--container-bg); | |
padding: 30px; | |
border-radius: 10px; | |
box-shadow: var(--shadow); | |
} | |
/* Title and Header */ | |
.main-title { | |
font-size: 2.5rem; | |
text-align: center; | |
margin-bottom: 30px; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
color: var(--secondary-color); | |
} | |
.title-icon { | |
font-size: 2.5rem; | |
margin-right: 10px; | |
} | |
/* Upload Section */ | |
.upload-section { | |
background: var(--white); | |
padding: 25px; | |
border-radius: 10px; | |
margin-bottom: 30px; | |
box-shadow: var(--shadow); | |
} | |
.upload-section h2 { | |
text-align: center; | |
margin-bottom: 20px; | |
color: var(--secondary-color); | |
} | |
.file-input-container { | |
border: 2px dashed #cbd5e1; | |
padding: 30px; | |
border-radius: 10px; | |
text-align: center; | |
transition: border-color 0.3s ease; | |
cursor: pointer; | |
} | |
.file-input-container:hover { | |
border-color: var(--primary-color); | |
} | |
/* Button Styles */ | |
button, | |
.upload-button { | |
background-color: var(--primary-color); | |
color: var(--white); | |
padding: 12px 24px; | |
border: none; | |
border-radius: 5px; | |
font-size: 1rem; | |
cursor: pointer; | |
display: block; | |
width: 100%; | |
transition: background-color 0.3s ease; | |
margin-top: 20px; | |
} | |
.upload-button:hover { | |
background-color: var(--primary-dark); | |
} | |
/* Loading Spinner */ | |
.loading { | |
text-align: center; | |
margin: 20px 0; | |
} | |
.loading-spinner { | |
width: 40px; | |
height: 40px; | |
border: 4px solid #f3f3f3; | |
border-top: 4px solid var(--primary-color); | |
border-radius: 50%; | |
animation: spin 1s linear infinite; | |
margin: 20px auto; | |
} | |
@keyframes spin { | |
to { | |
transform: rotate(360deg); | |
} | |
} | |
/* Flash Messages */ | |
.flash-message { | |
background-color: var(--error-bg); | |
color: var(--error-text); | |
padding: 10px 15px; | |
border-radius: 5px; | |
margin-bottom: 20px; | |
text-align: center; | |
} | |
/* Markdown Content Styling */ | |
.markdown-body { | |
background-color: var(--white); | |
padding: 30px; | |
border-radius: 8px; | |
border: 1px solid #e1e4e8; | |
margin-top: 20px; | |
line-height: 1.6; | |
} | |
/* Section Heading Styles with distinct colors */ | |
.markdown-body h1 { | |
background-color: var(--section-bg-overview); | |
padding: 10px; | |
border-radius: 5px; | |
font-size: 2em; /* Default for larger screens */ | |
margin-bottom: 15px; | |
} | |
.markdown-body h2 { /* Apply to all H2s initially */ | |
padding: 8px; | |
border-radius: 5px; | |
margin-top: 20px; | |
margin-bottom: 10px; | |
font-size: 1.5em; /* Default for larger screens */ | |
} | |
.markdown-body h2:nth-of-type(1) { | |
background-color: var(--section-bg-overview); | |
} | |
.markdown-body h2:nth-of-type(2) { | |
background-color: var(--section-bg-composition); | |
} | |
.markdown-body h2:nth-of-type(3) { | |
background-color: var(--section-bg-nutrients); | |
} | |
.markdown-body h2:nth-of-type(4) { | |
background-color: var(--section-bg-ph); | |
} | |
.markdown-body h2:nth-of-type(5) { | |
background-color: var(--section-bg-recommendations); | |
} | |
/* Chart Container */ | |
#chartContainer { | |
margin-top: 20px; | |
padding: 20px; | |
background-color: #f9f9f9; | |
border: 1px solid #ddd; | |
border-radius: 5px; | |
display: none; /* Initially hidden until chart code is executed */ | |
} | |
/* Ensure canvas fills the container */ | |
#chartContainer canvas { | |
max-width: 100%; | |
height: auto; /* Maintain aspect ratio */ | |
} | |
/* --- Responsive Adjustments --- */ | |
/* For tablets and smaller desktops (up to 768px wide) */ | |
@media (max-width: 768px) { | |
.container { | |
margin: 20px; | |
padding: 20px; | |
} | |
.main-title { | |
font-size: 2rem; | |
margin-bottom: 25px; | |
} | |
.title-icon { | |
font-size: 2rem; | |
} | |
.upload-section { | |
padding: 20px; | |
margin-bottom: 25px; | |
} | |
.file-input-container { | |
padding: 25px; | |
} | |
.upload-button { | |
font-size: 0.95rem; | |
padding: 10px 20px; | |
margin-top: 15px; | |
} | |
.markdown-body { | |
padding: 20px; | |
} | |
.markdown-body h1 { | |
font-size: 1.8em; | |
} | |
.markdown-body h2 { | |
font-size: 1.3em; | |
margin-top: 15px; | |
} | |
#chartContainer { | |
padding: 15px; | |
} | |
} | |
/* For mobile phones (up to 480px wide) */ | |
@media (max-width: 480px) { | |
.container { | |
margin: 15px; | |
padding: 15px; | |
} | |
.main-title { | |
font-size: 1.6rem; | |
margin-bottom: 20px; | |
text-align: center; /* Ensure centering even if icon is present */ | |
flex-direction: column; /* Stack icon and text */ | |
gap: 5px; | |
} | |
.title-icon { | |
font-size: 1.8rem; | |
margin-right: 0; /* Remove right margin when stacked */ | |
} | |
.upload-section { | |
padding: 15px; | |
margin-bottom: 20px; | |
} | |
.file-input-container { | |
padding: 20px; | |
font-size: 0.9rem; | |
} | |
.upload-button { | |
font-size: 0.9rem; | |
padding: 10px 15px; | |
margin-top: 15px; | |
} | |
.flash-message { | |
padding: 8px 10px; | |
font-size: 0.9rem; | |
} | |
.loading p { | |
font-size: 0.9rem; | |
} | |
.loading-spinner { | |
width: 30px; | |
height: 30px; | |
border-width: 3px; | |
} | |
.markdown-body { | |
padding: 15px; | |
} | |
.markdown-body h1 { | |
font-size: 1.5em; | |
padding: 8px; | |
margin-bottom: 10px; | |
} | |
.markdown-body h2 { | |
font-size: 1.2em; | |
padding: 6px; | |
margin-top: 12px; | |
margin-bottom: 8px; | |
} | |
#chartContainer { | |
padding: 10px; | |
} | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<h1 class="main-title"> | |
<span class="title-icon">🌱</span> | |
Soil Report Analysis & Recommendations Generator | |
</h1> | |
{% with messages = get_flashed_messages() %} | |
{% if messages %} | |
{% for message in messages %} | |
<div class="flash-message">{{ message }}</div> | |
{% endfor %} | |
{% endif %} | |
{% endwith %} | |
<div class="upload-section"> | |
<h2>Upload Soil Reports</h2> | |
<form id="uploadForm"> | |
<div class="file-input-container"> | |
<input type="file" name="files" accept=".pdf,.jpg,.jpeg,.png" multiple required> | |
<p>Drag & drop your soil reports here or click to select</p> | |
</div> | |
<button type="submit" class="upload-button">Generate Soil Analysis & Recommendations</button> | |
</form> | |
</div> | |
<div class="loading" id="loading" style="display:none;"> | |
<div class="loading-spinner"></div> | |
<p>Analyzing soil reports and generating analysis...</p> | |
</div> | |
<div id="summaryContainer" class="markdown-body" style="display: none;"></div> | |
<!-- Chart Container with a canvas element --> | |
<div id="chartContainer"> | |
<!-- The chart image will be inserted here by JavaScript --> | |
</div> | |
</div> | |
<script> | |
document.getElementById('uploadForm').addEventListener('submit', function(e) { | |
e.preventDefault(); | |
const formData = new FormData(this); | |
const loading = document.getElementById('loading'); | |
const summaryContainer = document.getElementById('summaryContainer'); | |
const chartContainer = document.getElementById('chartContainer'); | |
loading.style.display = 'block'; | |
summaryContainer.style.display = 'none'; | |
chartContainer.style.display = 'none'; // Ensure chart is hidden on new submission | |
fetch('/', { method: 'POST', body: formData }) | |
.then(response => response.json()) | |
.then(data => { | |
loading.style.display = 'none'; | |
summaryContainer.style.display = 'block'; | |
if (data.error) { | |
summaryContainer.innerHTML = `<div class="flash-message">${data.error}</div>`; | |
} else { | |
summaryContainer.innerHTML = data.html_summary; | |
if (data.chart_img) { | |
chartContainer.style.display = 'block'; | |
chartContainer.innerHTML = `<img src="${data.chart_img}" alt="Nutrient Chart" style="max-width:100%; height:auto;"/>`; | |
} else { | |
chartContainer.style.display = 'none'; // Hide if no chart data | |
} | |
} | |
}) | |
.catch(error => { | |
loading.style.display = 'none'; | |
summaryContainer.style.display = 'block'; | |
summaryContainer.innerHTML = `<div class="flash-message">Error: An unexpected error occurred. Please try again.</div>`; | |
console.error('Fetch error:', error); | |
}); | |
}); | |
// Drag and drop functionality | |
const fileInput = document.querySelector('input[type="file"]'); | |
const dropZone = document.querySelector('.file-input-container'); | |
// Prevent default drag behaviors | |
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { | |
dropZone.addEventListener(eventName, preventDefaults, false); | |
document.body.addEventListener(eventName, preventDefaults, false); // Prevent drop anywhere on body | |
}); | |
function preventDefaults(e) { | |
e.preventDefault(); | |
e.stopPropagation(); | |
} | |
// Highlight drop zone when dragging over | |
['dragenter', 'dragover'].forEach(eventName => { | |
dropZone.addEventListener(eventName, highlight, false); | |
}); | |
// Remove highlight when dragging leaves or drops | |
['dragleave', 'drop'].forEach(eventName => { | |
dropZone.addEventListener(eventName, unhighlight, false); | |
}); | |
function highlight(e) { | |
dropZone.style.borderColor = '#3b82f6'; | |
dropZone.style.backgroundColor = 'rgba(0, 123, 255, 0.05)'; /* Light blue overlay */ | |
} | |
function unhighlight(e) { | |
dropZone.style.borderColor = '#cbd5e1'; | |
dropZone.style.backgroundColor = ''; /* Reset background */ | |
} | |
// Handle dropped files | |
dropZone.addEventListener('drop', handleDrop, false); | |
function handleDrop(e) { | |
const dt = e.dataTransfer; | |
const files = dt.files; | |
fileInput.files = files; // Assign files to the input element | |
// Optionally, display selected file names | |
if (files.length > 0) { | |
let fileNames = Array.from(files).map(f => f.name).join(', '); | |
dropZone.querySelector('p').textContent = `Selected: ${fileNames}`; | |
} | |
} | |
// Handle explicit file input change (when user clicks and selects) | |
fileInput.addEventListener('change', function() { | |
if (this.files.length > 0) { | |
let fileNames = Array.from(this.files).map(f => f.name).join(', '); | |
dropZone.querySelector('p').textContent = `Selected: ${fileNames}`; | |
} else { | |
dropZone.querySelector('p').textContent = `Drag & drop your soil reports here or click to select`; | |
} | |
}); | |
</script> | |
</body> | |
</html> |