Garden-Review Plant Doctor
https://cdn.tailwindcss.com
/* Custom styles for better aesthetics and responsiveness, inspired by Garden-Review */
body {
font-family: ‘Inter’, sans-serif; /* Keeping Inter, but focusing on greens */
background-color: #e6ffe6; /* Lighter, soft green background for a fresh garden feel */
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 20px;
box-sizing: border-box;
}
.container {
background-color: #ffffff;
border-radius: 1.5rem; /* Consistent rounded corners */
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.15); /* Slightly stronger, more prominent shadow */
padding: 2.5rem;
max-width: 650px; /* Slightly wider container */
width: 100%;
text-align: center;
display: flex;
flex-direction: column;
gap: 1.75rem; /* Increased space between elements for better readability */
border: 1px solid #d4edda; /* Subtle light green border */
}
.upload-area {
border: 2px dashed #90ee90; /* Garden-Review inspired light green dashed border */
border-radius: 1rem;
padding: 2.5rem; /* More padding */
cursor: pointer;
transition: border-color 0.3s ease, background-color 0.3s ease;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 200px; /* Increased min height */
background-color: #fafffafa; /* Very light, almost white background */
}
.upload-area:hover {
border-color: #3cb371; /* Medium sea green on hover */
background-color: #f0fff0; /* Very light green on hover */
}
.file-input {
display: none;
}
.image-preview {
max-width: 100%;
max-height: 350px; /* Taller preview image */
border-radius: 1rem; /* More rounded image corners */
object-fit: contain;
margin-top: 1.5rem; /* More space above image */
border: 2px solid #a0e6a0; /* More noticeable light green border */
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08); /* Subtle image shadow */
}
.diagnose-button {
background-image: linear-gradient(to right, #4CAF50, #2E8B57); /* Stronger green gradient */
color: white;
padding: 1rem 2rem; /* Larger button */
border-radius: 0.75rem;
font-weight: 700; /* Bolder text */
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 6px 15px rgba(46, 139, 87, 0.4); /* Stronger green shadow */
border: none;
outline: none;
letter-spacing: 0.05em; /* Slightly spaced letters */
}
.diagnose-button:hover {
opacity: 0.95;
transform: translateY(-3px); /* More pronounced lift */
box-shadow: 0 8px 20px rgba(46, 139, 87, 0.5); /* Enhanced shadow on hover */
}
.diagnose-button:disabled {
background-image: linear-gradient(to right, #b0c4de, #778899); /* Light steel blue to slate gray when disabled */
cursor: not-allowed;
box-shadow: none;
transform: none;
}
.loading-spinner {
border: 4px solid rgba(0, 0, 0, 0.1);
border-left-color: #2E8B57; /* Darker green spinner */
border-radius: 50%;
width: 35px; /* Slightly larger spinner */
height: 35px;
animation: spin 1s linear infinite;
margin: 1.5rem auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.diagnosis-result {
background-color: #f0fff0; /* Very light green background for results */
border-radius: 1rem;
padding: 1.75rem; /* More padding */
text-align: left;
border: 1px solid #c8e6c9; /* Light green border */
white-space: pre-wrap;
word-wrap: break-word;
line-height: 1.6; /* Increased line height for readability */
color: #333; /* Darker text for better contrast */
font-size: 1.05rem; /* Slightly larger text */
}
.error-message {
color: #d32f2f; /* Darker red for errors */
font-weight: 600;
margin-top: 1rem;
}
/* Adjusted text colors for better harmony with the green theme */
.text-gray-800 {
color: #2E8B57; /* Darker green for main heading */
}
.text-gray-600 {
color: #4CAF50; /* Medium green for sub-heading */
}
.text-gray-500 {
color: #6B8E23; /* Olive green for upload instruction */
}
.text-blue-500 {
color: #3CB371; /* Medium sea green for ‘click to upload’ */
}
.text-gray-700 {
color: #333; /* Dark gray for diagnosis text */
}
Upload a picture of your plant to diagnose diseases or pests.
Drag & drop an image or click to upload
Diagnose Plant
const imageUpload = document.getElementById(‘imageUpload’);
const uploadArea = document.getElementById(‘uploadArea’);
const imagePreview = document.getElementById(‘imagePreview’);
const diagnoseButton = document.getElementById(‘diagnoseButton’);
const loadingSpinner = document.getElementById(‘loadingSpinner’);
const diagnosisResult = document.getElementById(‘diagnosisResult’);
const diagnosisText = document.getElementById(‘diagnosisText’);
const errorMessageDiv = document.getElementById(‘errorMessage’);
let base64ImageData = ”;
// Handle file selection via click
uploadArea.addEventListener(‘click’, () => {
imageUpload.click();
});
// Handle file selection via drag and drop
uploadArea.addEventListener(‘dragover’, (e) => {
e.preventDefault();
uploadArea.classList.add(‘border-blue-500’); // Tailwind class for hover effect
uploadArea.style.backgroundColor = ‘#f0fff0’; // Custom hover background
});
uploadArea.addEventListener(‘dragleave’, () => {
uploadArea.classList.remove(‘border-blue-500’);
uploadArea.style.backgroundColor = ‘#fafffafa’; // Revert background
});
uploadArea.addEventListener(‘drop’, (e) => {
e.preventDefault();
uploadArea.classList.remove(‘border-blue-500’);
uploadArea.style.backgroundColor = ‘#fafffafa’; // Revert background
const files = e.dataTransfer.files;
if (files.length > 0) {
handleFile(files[0]);
}
});
// Handle file input change
imageUpload.addEventListener(‘change’, (event) => {
const file = event.target.files[0];
if (file) {
handleFile(file);
}
});
function handleFile(file) {
errorMessageDiv.classList.add(‘hidden’); // Hide any previous errors
if (!file.type.startsWith(‘image/’)) {
errorMessageDiv.textContent = ‘Please upload an image file (e.g., JPEG, PNG).’;
errorMessageDiv.classList.remove(‘hidden’);
resetDiagnosisArea();
return;
}
const reader = new FileReader();
reader.onload = (e) => {
imagePreview.src = e.target.result;
imagePreview.classList.remove(‘hidden’);
// Extract base64 part, removing the data:image/png;base64, prefix
base64ImageData = e.target.result.split(‘,’)[1];
diagnoseButton.disabled = false;
diagnosisResult.classList.add(‘hidden’); // Hide previous diagnosis
diagnosisText.textContent = ”; // Clear previous diagnosis text
};
reader.readAsDataURL(file);
}
diagnoseButton.addEventListener(‘click’, async () => {
if (!base64ImageData) {
errorMessageDiv.textContent = ‘Please upload an image first.’;
errorMessageDiv.classList.remove(‘hidden’);
return;
}
loadingSpinner.classList.remove(‘hidden’);
diagnoseButton.disabled = true;
diagnosisResult.classList.add(‘hidden’);
errorMessageDiv.classList.add(‘hidden’);
try {
let chatHistory = [];
// More detailed prompt for better context for the AI
const prompt = `Analyze the uploaded image of a plant, flower, or tree to identify any potential diseases or pests.
Provide a concise diagnosis, detailing the specific problem if one is found.
If the plant appears healthy, state that clearly.
For any detected issues, offer practical, actionable advice for treatment or prevention.
Ensure the response focuses solely on plant health and care.`;
chatHistory.push({ role: “user”, parts: [{ text: prompt }] });
const payload = {
contents: [
{
role: “user”,
parts: [
{ text: prompt },
{
inlineData: {
mimeType: “image/png”, // Assuming images will be treated as PNG for base64 encoding
data: base64ImageData
}
}
]
}
],
};
const apiKey = “”; // If you want to use models other than gemini-2.0-flash or imagen-3.0-generate-002, provide an API key here. Otherwise, leave this as-is.
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`;
const response = await fetch(apiUrl, {
method: ‘POST’,
headers: { ‘Content-Type’: ‘application/json’ },
body: JSON.stringify(payload)
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(`API error: ${response.status} – ${errorData.error.message || ‘Unknown error’}`);
}
const result = await response.json();
if (result.candidates && result.candidates.length > 0 &&
result.candidates[0].content && result.candidates[0].content.parts &&
result.candidates[0].content.parts.length > 0) {
const text = result.candidates[0].content.parts[0].text;
diagnosisText.textContent = text;
diagnosisResult.classList.remove(‘hidden’);
} else {
diagnosisText.textContent = “Could not get a diagnosis. Please try another image.”;
diagnosisResult.classList.remove(‘hidden’);
}
} catch (error) {
console.error(“Error during diagnosis:”, error);
errorMessageDiv.textContent = `Failed to get a diagnosis: ${error.message}. Please try again.`;
errorMessageDiv.classList.remove(‘hidden’);
resetDiagnosisArea();
} finally {
loadingSpinner.classList.add(‘hidden’);
diagnoseButton.disabled = false;
}
});
function resetDiagnosisArea() {
imagePreview.classList.add(‘hidden’);
imagePreview.src = ‘#’;
base64ImageData = ”;
diagnoseButton.disabled = true;
diagnosisResult.classList.add(‘hidden’);
diagnosisText.textContent = ”;
imageUpload.value = ”; // Clear file input
}