Rushikesh-Sontakke
Complete project
4adc200
Raw
History Blame Contribute Delete
7.08 kB
// inject viewport meta if missing (fix iOS auto zoom & layout scale)
(function () {
if (!document.querySelector('meta[name="viewport"]')) {
var m = document.createElement('meta');
m.name = 'viewport';
m.content = 'width=device-width, initial-scale=1, viewport-fit=cover';
document.head.appendChild(m);
}
})();
// Chinese (backend / DB) -> English display maps.
// NOTE: <select> option VALUES stay Chinese so the /match backend keeps working;
// these maps are only for human-readable display.
const COLOR_EN = {
"白色": "White", "透明": "Transparent", "黑色": "Black", "棕色": "Brown",
"紅色": "Red", "橘色": "Orange", "皮膚色": "Beige", "黃色": "Yellow",
"綠色": "Green", "藍色": "Blue", "紫色": "Purple", "粉紅色": "Pink", "灰色": "Gray"
};
const COLOR_HEX = {
"白色": "#ffffff", "透明": "#e5e7eb", "黑色": "#1f2937", "棕色": "#92400e",
"紅色": "#dc2626", "橘色": "#ea580c", "皮膚色": "#e8c9a0", "黃色": "#facc15",
"綠色": "#16a34a", "藍色": "#2563eb", "紫色": "#7c3aed", "粉紅色": "#ec4899", "灰色": "#9ca3af"
};
const SHAPE_EN = { "圓形": "Round", "橢圓形": "Oval", "其他": "Other" };
// ===== DOM elements =====
const uploadInput = document.getElementById('uploadInput');
const customFileButton = document.getElementById('customFileButton');
const fileInput = document.getElementById('fileInput');
const textField = document.getElementById('recognizedText');
const color1Select = document.getElementById('color1');
const color2Select = document.getElementById('color2');
const shapeSelect = document.getElementById('shape');
const confirmButton = document.getElementById('match-button');
const summaryBox = document.getElementById('detectionSummary');
const resultModal = document.getElementById('resultModal');
customFileButton.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', (event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => Detection(e.target.result);
reader.readAsDataURL(file);
}
});
uploadInput.addEventListener('change', (event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => Detection(e.target.result);
reader.readAsDataURL(file);
}
});
// Render the HSV color/shape detection summary
function renderSummary(colors, shape) {
if ((!colors || colors.length === 0) && !shape) {
summaryBox.hidden = true;
return;
}
const chips = [];
(colors || []).forEach((c) => {
if (!c) return;
const hex = COLOR_HEX[c] || '#cccccc';
const name = COLOR_EN[c] || c;
chips.push(`<span class="ds-chip"><span class="ds-swatch" style="background:${hex}"></span>${name}</span>`);
});
if (shape) {
chips.push(`<span class="ds-chip">⬭ ${SHAPE_EN[shape] || shape}</span>`);
}
summaryBox.innerHTML =
`<div class="ds-title">Detected — HSV color &amp; shape recognition</div>
<div class="ds-chips">${chips.join('')}</div>`;
summaryBox.hidden = false;
}
async function Detection(imageData) {
try {
const res = await fetch('/upload', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ image: imageData })
});
const ctype = res.headers.get('content-type') || '';
if (!res.ok || !ctype.includes('application/json')) {
const text = await res.text();
console.warn('[UPLOAD] non-JSON response:', text);
alert('🚨 Server error, please try again later.');
return;
}
const data = await res.json();
if (!data.ok) {
alert('❌ Error: ' + (data.error || 'Unknown error'));
return;
}
const result = data.result || {};
// Show cropped pill image
const container = document.getElementById('photo-container');
container.innerHTML = '';
if (result.cropped_image) {
const img = new Image();
img.src = result.cropped_image;
img.alt = 'Cropped pill';
container.appendChild(img);
}
// Fill editable fields
const colors = result['顏色'] || [];
textField.value = (result['文字辨識'] || []).join(', ');
color1Select.value = colors[0] || '';
color2Select.value = colors[1] || '';
shapeSelect.value = result['外型'] || '';
// Highlight the HSV detection result
renderSummary(colors, result['外型']);
} catch (err) {
alert('🚨 Image recognition failed: ' + err.message);
}
}
// Build a drug result card
function buildCard(drug) {
const card = document.createElement('div');
card.className = 'candidate-card';
card.innerHTML = `
${drug.drug_image ? `<img src="${drug.drug_image}" alt="${drug.name || 'Medication'}">` : ''}
<h3>${drug.name || 'Unknown'}</h3>
<p><strong>Indications:</strong> ${drug.symptoms || 'N/A'}</p>
<p><strong>Precautions:</strong> ${drug.precautions || 'N/A'}</p>
<p><strong>Side effects:</strong> ${drug.side_effects || 'N/A'}</p>
`;
return card;
}
function showModal(cards) {
const candidateList = document.getElementById('candidateList');
candidateList.innerHTML = '';
cards.forEach((c) => candidateList.appendChild(c));
resultModal.hidden = false;
}
confirmButton.addEventListener('click', async () => {
const payload = {
texts: textField.value.trim().split(',').map(t => t.trim()).filter(Boolean),
colors: [color1Select.value.trim(), color2Select.value.trim()].filter(Boolean),
shape: shapeSelect.value.trim()
};
try {
const response = await fetch('/match', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload)
});
const text = await response.text();
let result;
try {
result = JSON.parse(text);
} catch (err) {
alert('⚠️ JSON parse error: ' + err.message + '\nRaw response:\n' + text);
return;
}
if (result.error) {
alert('❌ Error: ' + result.error);
return;
}
if (result.candidates) {
showModal(result.candidates.map(buildCard));
} else if (result.name) {
showModal([buildCard(result)]);
} else {
alert('❌ No matching results found.');
}
} catch (error) {
alert('🚨 Request failed: ' + error.message);
}
});
// Modal close handlers (attached once)
document.getElementById('closeModal').addEventListener('click', () => {
resultModal.hidden = true;
});
resultModal.addEventListener('click', (e) => {
if (e.target.id === 'resultModal') resultModal.hidden = true;
});