Fix wizard: header preview uses cover, add image matching step after HTML import

This commit is contained in:
John Mizerek 2026-02-12 20:31:29 -08:00
parent a1b557cdc7
commit 112f343ecf
2 changed files with 149 additions and 15 deletions

View file

@ -633,7 +633,7 @@
<div>
<label style="display: block; margin-bottom: 8px; font-weight: 500;">Header Image</label>
<p style="color: #666; font-size: 13px; margin-bottom: 8px;">Displayed at the top of your menu. Recommended: 1200x400px</p>
<div id="headerPreview" style="width: 100%; height: 120px; background: #fff; border-radius: 8px; margin-bottom: 8px; background-size: contain; background-position: center; background-repeat: no-repeat; border: 1px solid #ddd;"></div>
<div id="headerPreview" style="width: 100%; height: 120px; background: #fff; border-radius: 8px; margin-bottom: 8px; background-size: cover; background-position: center; background-repeat: no-repeat; border: 1px solid #ddd;"></div>
<button class="btn btn-secondary" onclick="Portal.uploadHeader()" style="width: 100%;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 8px;">
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12"/>

View file

@ -1049,7 +1049,9 @@
items: []
},
currentStep: 1,
imageObjectUrls: [] // Store object URLs for uploaded images
imageObjectUrls: [], // Store object URLs for uploaded images
imageMappings: [], // For matching uploaded images to items (from HTML import)
itemImages: {} // item ID -> File object for matched images
};
// Image preview functions
@ -1328,11 +1330,15 @@
config.extractedData = result.DATA;
config.sourceUrl = result.sourceUrl;
// Store image mappings for matching uploaded images to items
config.imageMappings = result.DATA.imageMappings || [];
// Log debug info
console.log('=== URL IMPORT RESPONSE ===');
console.log('Source URL:', result.sourceUrl);
console.log('Pages processed:', result.pagesProcessed);
console.log('Images found:', result.imagesFound);
console.log('Image mappings:', config.imageMappings.length);
console.log('Extracted data:', result.DATA);
if (result.steps) {
console.log('Steps:', result.steps);
@ -1342,8 +1348,11 @@
// Remove loading message and start conversation flow
document.getElementById('conversation').innerHTML = '';
// In add-menu mode, skip business info and header
if (config.businessId && config.menuId) {
// If we have image mappings from the HTML, show image matching step first
if (config.imageMappings.length > 0) {
showImageMatchingStep();
} else if (config.businessId && config.menuId) {
// In add-menu mode, skip business info and header
showCategoriesStep();
} else {
showBusinessInfoStep();
@ -1396,14 +1405,34 @@
<p style="font-size:13px;color:var(--gray-500);margin-top:8px;">Extracting menu data from HTML content.</p>
`);
let result = null;
try {
console.log('Sending HTML content, length:', htmlContent.length);
const response = await fetch(`${config.apiBaseUrl}/setup/analyzeMenuUrl.cfm`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ html: htmlContent })
});
const result = await response.json();
console.log('Response status:', response.status, response.statusText);
const responseText = await response.text();
console.log('Raw response (first 500 chars):', responseText.substring(0, 500));
if (!responseText || responseText.trim().length === 0) {
throw new Error('Server returned empty response');
}
try {
result = JSON.parse(responseText);
} catch (parseErr) {
console.error('JSON parse error. Full response:', responseText);
throw new Error('Invalid JSON response from server. Check console for details.');
}
console.log('=== HTML FILE IMPORT RESPONSE ===');
console.log('File:', file.name);
console.log('Full response:', result);
console.log('=================================');
if (!result.OK) {
throw new Error(result.MESSAGE || 'Failed to analyze HTML file');
@ -1412,20 +1441,18 @@
// Store extracted data
config.extractedData = result.DATA;
// Log debug info
console.log('=== HTML FILE IMPORT RESPONSE ===');
console.log('File:', file.name);
console.log('Extracted data:', result.DATA);
if (result.steps) {
console.log('Steps:', result.steps);
}
console.log('=================================');
// Store image mappings for matching uploaded images to items
config.imageMappings = result.DATA.imageMappings || [];
console.log('Image mappings from HTML:', config.imageMappings.length);
// Remove loading message and start conversation flow
document.getElementById('conversation').innerHTML = '';
// In add-menu mode, skip business info and header
if (config.businessId && config.menuId) {
// If we have image mappings from the HTML, show image matching step first
if (config.imageMappings.length > 0) {
showImageMatchingStep();
} else if (config.businessId && config.menuId) {
// In add-menu mode, skip business info and header
showCategoriesStep();
} else {
showBusinessInfoStep();
@ -1433,10 +1460,16 @@
} catch (error) {
console.error('HTML analysis error:', error);
console.error('Full result object:', result);
document.getElementById('conversation').innerHTML = '';
let debugInfo = '';
if (result && result.debug) {
debugInfo = `<p style="font-size:12px;color:var(--gray-500);margin-top:8px;">Debug: hasHtml=${result.debug.hasHtmlKey}, htmlLen=${result.debug.htmlLength}, url="${result.debug.urlValue}"</p>`;
}
addMessage('ai', `
<p>Sorry, I encountered an error analyzing that file:</p>
<p style="color: var(--danger);">${error.message}</p>
${debugInfo}
<div class="action-buttons">
<button class="btn btn-primary" onclick="retryUrlAnalysis()">Try Again</button>
<button class="btn btn-secondary" onclick="switchToFileUpload()">Upload Images Instead</button>
@ -1455,6 +1488,107 @@
event.target.value = '';
}
// Show step to upload images from saved webpage subfolder and match to items
function showImageMatchingStep() {
const itemCount = config.extractedData.items ? config.extractedData.items.length : 0;
const mappingCount = config.imageMappings.length;
addMessage('ai', `
<p>I found <strong>${itemCount} menu items</strong> and detected <strong>${mappingCount} image references</strong> in the HTML.</p>
<p>To add images to your menu items, upload the images from the saved webpage folder (usually named something like <code>pagename_files</code>).</p>
<p style="font-size:13px;color:var(--gray-500);">I'll automatically match images to items based on filenames.</p>
<div style="margin-top:16px;">
<input type="file" id="matchImagesInput" accept="image/*" multiple style="display:none;" onchange="handleImageMatching(event)">
<button class="btn btn-primary" onclick="document.getElementById('matchImagesInput').click()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right:6px;">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/>
</svg>
Upload Images from Saved Folder
</button>
<button class="btn btn-secondary" onclick="skipImageMatching()" style="margin-left:8px;">
Skip - No Images
</button>
</div>
`);
}
// Handle image matching from uploaded files
function handleImageMatching(event) {
const files = event.target.files;
if (!files.length) return;
let matchedCount = 0;
const matchResults = [];
Array.from(files).forEach(file => {
const filename = file.name.toLowerCase();
// Try to find a matching mapping
for (const mapping of config.imageMappings) {
const mappingFilename = mapping.filename.toLowerCase();
// Check for exact filename match or close match
if (filename === mappingFilename ||
filename.includes(mappingFilename.replace(/\.[^.]+$/, '')) ||
mappingFilename.includes(filename.replace(/\.[^.]+$/, ''))) {
// Find matching item by alt text or name
const altText = (mapping.alt || '').toLowerCase();
const matchedItem = config.extractedData.items.find(item => {
const itemName = (item.name || '').toLowerCase();
const itemAlt = (item.imageAlt || '').toLowerCase();
return itemName === altText ||
itemAlt === altText ||
itemName.includes(altText) ||
altText.includes(itemName) ||
(itemAlt && (itemAlt.includes(altText) || altText.includes(itemAlt)));
});
if (matchedItem) {
config.itemImages[matchedItem.id] = file;
matchedCount++;
matchResults.push({ item: matchedItem.name, file: file.name });
break;
}
}
}
});
// Show results
const totalFiles = files.length;
const resultHtml = matchResults.length > 0
? `<ul style="font-size:13px;margin:8px 0;max-height:200px;overflow-y:auto;">${matchResults.map(r => `<li>${r.item} ← ${r.file}</li>`).join('')}</ul>`
: '';
addMessage('ai', `
<p>Matched <strong>${matchedCount}</strong> of ${totalFiles} uploaded images to menu items.</p>
${resultHtml}
<div class="action-buttons">
<button class="btn btn-primary" onclick="proceedAfterImageMatching()">Continue</button>
<button class="btn btn-secondary" onclick="document.getElementById('matchImagesInput').click()">Upload More Images</button>
</div>
`);
console.log('Image matching results:', matchResults);
console.log('Item images map:', config.itemImages);
// Reset input
event.target.value = '';
}
function skipImageMatching() {
proceedAfterImageMatching();
}
function proceedAfterImageMatching() {
// In add-menu mode, skip business info and header
if (config.businessId && config.menuId) {
showCategoriesStep();
} else {
showBusinessInfoStep();
}
}
async function startAnalysis() {
if (config.uploadedFiles.length === 0) {
showToast('Please upload at least one menu image', 'error');