Add HTML file upload option for menu import
- Backend now accepts either url or html content in request body - Frontend adds HTML file upload option below URL input - Useful when websites block the crawler (403 errors) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
31773b0acf
commit
813628cecb
2 changed files with 142 additions and 25 deletions
|
|
@ -28,37 +28,57 @@
|
|||
</cfif>
|
||||
|
||||
<cfset requestData = deserializeJSON(requestBody)>
|
||||
<cfif NOT structKeyExists(requestData, "url") OR NOT len(trim(requestData.url))>
|
||||
<cfthrow message="URL is required">
|
||||
</cfif>
|
||||
|
||||
<cfset targetUrl = trim(requestData.url)>
|
||||
|
||||
<!--- Validate URL format --->
|
||||
<cfif NOT reFindNoCase("^https?://", targetUrl)>
|
||||
<cfset targetUrl = "https://" & targetUrl>
|
||||
</cfif>
|
||||
|
||||
<cfset response["steps"] = arrayNew(1)>
|
||||
<cfset arrayAppend(response.steps, "Fetching URL: " & targetUrl)>
|
||||
<cfset pageHtml = "">
|
||||
<cfset baseUrl = "">
|
||||
<cfset basePath = "">
|
||||
<cfset targetUrl = "">
|
||||
|
||||
<!--- Fetch the main page --->
|
||||
<cfhttp url="#targetUrl#" method="GET" timeout="30" result="mainPage" useragent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36">
|
||||
<cfhttpparam type="header" name="Accept" value="text/html,application/xhtml+xml">
|
||||
</cfhttp>
|
||||
<!--- Check if HTML content was provided directly (uploaded file or pasted) --->
|
||||
<cfif structKeyExists(requestData, "html") AND len(trim(requestData.html))>
|
||||
<cfset pageHtml = trim(requestData.html)>
|
||||
<cfset arrayAppend(response.steps, "Using provided HTML content: " & len(pageHtml) & " bytes")>
|
||||
<!--- No base URL for local content - images won't be fetched --->
|
||||
<cfset baseUrl = "">
|
||||
<cfset basePath = "">
|
||||
<cfelseif structKeyExists(requestData, "url") AND len(trim(requestData.url))>
|
||||
<cfset targetUrl = trim(requestData.url)>
|
||||
|
||||
<cfif mainPage.statusCode NEQ "200 OK" AND NOT findNoCase("200", mainPage.statusCode)>
|
||||
<cfthrow message="Failed to fetch URL: #mainPage.statusCode#">
|
||||
</cfif>
|
||||
<!--- Validate URL format --->
|
||||
<cfif NOT reFindNoCase("^https?://", targetUrl)>
|
||||
<cfset targetUrl = "https://" & targetUrl>
|
||||
</cfif>
|
||||
|
||||
<cfset pageHtml = mainPage.fileContent>
|
||||
<cfset arrayAppend(response.steps, "Fetched #len(pageHtml)# bytes")>
|
||||
<cfset arrayAppend(response.steps, "Fetching URL: " & targetUrl)>
|
||||
|
||||
<!--- Extract base URL for resolving relative links --->
|
||||
<cfset baseUrl = reReplace(targetUrl, "(https?://[^/]+).*", "\1")>
|
||||
<cfset basePath = reReplace(targetUrl, "(https?://[^/]+/[^?]*/?).*", "\1")>
|
||||
<cfif NOT reFindNoCase("/$", basePath)>
|
||||
<cfset basePath = reReplace(basePath, "/[^/]*$", "/")>
|
||||
<!--- Fetch the main page with browser-like headers --->
|
||||
<cfhttp url="#targetUrl#" method="GET" timeout="30" result="mainPage" useragent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36">
|
||||
<cfhttpparam type="header" name="Accept" value="text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8">
|
||||
<cfhttpparam type="header" name="Accept-Language" value="en-US,en;q=0.9">
|
||||
<cfhttpparam type="header" name="Accept-Encoding" value="gzip, deflate, br">
|
||||
<cfhttpparam type="header" name="Sec-Fetch-Dest" value="document">
|
||||
<cfhttpparam type="header" name="Sec-Fetch-Mode" value="navigate">
|
||||
<cfhttpparam type="header" name="Sec-Fetch-Site" value="none">
|
||||
<cfhttpparam type="header" name="Sec-Fetch-User" value="?1">
|
||||
<cfhttpparam type="header" name="Upgrade-Insecure-Requests" value="1">
|
||||
</cfhttp>
|
||||
|
||||
<cfif mainPage.statusCode NEQ "200 OK" AND NOT findNoCase("200", mainPage.statusCode)>
|
||||
<cfthrow message="Failed to fetch URL: #mainPage.statusCode#">
|
||||
</cfif>
|
||||
|
||||
<cfset pageHtml = mainPage.fileContent>
|
||||
<cfset arrayAppend(response.steps, "Fetched " & len(pageHtml) & " bytes")>
|
||||
|
||||
<!--- Extract base URL for resolving relative links --->
|
||||
<cfset baseUrl = reReplace(targetUrl, "(https?://[^/]+).*", "\1")>
|
||||
<cfset basePath = reReplace(targetUrl, "(https?://[^/]+/[^?]*/?).*", "\1")>
|
||||
<cfif NOT reFindNoCase("/$", basePath)>
|
||||
<cfset basePath = reReplace(basePath, "/[^/]*$", "/")>
|
||||
</cfif>
|
||||
<cfelse>
|
||||
<cfthrow message="Either 'url' or 'html' content is required">
|
||||
</cfif>
|
||||
|
||||
<!--- Find menu links and fetch them too --->
|
||||
|
|
|
|||
|
|
@ -808,6 +808,23 @@
|
|||
</button>
|
||||
</div>
|
||||
<p style="margin:16px 0 0;color:var(--gray-400);font-size:12px;">Works with most restaurant websites, DoorDash, Yelp, Toast, Square, and more</p>
|
||||
|
||||
<!-- Divider -->
|
||||
<div style="display:flex;align-items:center;margin:24px 0;gap:16px;">
|
||||
<div style="flex:1;height:1px;background:var(--gray-300);"></div>
|
||||
<span style="color:var(--gray-400);font-size:12px;text-transform:uppercase;">or upload saved page</span>
|
||||
<div style="flex:1;height:1px;background:var(--gray-300);"></div>
|
||||
</div>
|
||||
|
||||
<!-- HTML File Upload -->
|
||||
<p style="margin:0 0 12px;color:var(--gray-500);font-size:14px;">If the website blocks our crawler, save the menu page and upload it here</p>
|
||||
<input type="file" id="htmlFileInput" accept=".html,.htm,.mhtml,.txt" style="display:none;" onchange="handleHtmlFileUpload(event)">
|
||||
<button class="btn btn-secondary" onclick="document.getElementById('htmlFileInput').click()" style="min-width:160px;">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right:6px;">
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/>
|
||||
</svg>
|
||||
Upload HTML File
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -1358,6 +1375,86 @@
|
|||
switchImportTab('upload');
|
||||
}
|
||||
|
||||
// Handle uploaded HTML file
|
||||
async function handleHtmlFileUpload(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
// Read the file content
|
||||
const reader = new FileReader();
|
||||
reader.onload = async function(e) {
|
||||
const htmlContent = e.target.result;
|
||||
|
||||
// Hide upload section, show conversation
|
||||
document.getElementById('uploadSection').style.display = 'none';
|
||||
|
||||
addMessage('ai', `
|
||||
<div style="display:flex;align-items:center;gap:12px;">
|
||||
<div class="loading-spinner"></div>
|
||||
<span>Analyzing saved page: ${file.name}...</span>
|
||||
</div>
|
||||
<p style="font-size:13px;color:var(--gray-500);margin-top:8px;">Extracting menu data from HTML content.</p>
|
||||
`);
|
||||
|
||||
try {
|
||||
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();
|
||||
|
||||
if (!result.OK) {
|
||||
throw new Error(result.MESSAGE || 'Failed to analyze HTML file');
|
||||
}
|
||||
|
||||
// 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('=================================');
|
||||
|
||||
// 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) {
|
||||
showCategoriesStep();
|
||||
} else {
|
||||
showBusinessInfoStep();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('HTML analysis error:', error);
|
||||
document.getElementById('conversation').innerHTML = '';
|
||||
addMessage('ai', `
|
||||
<p>Sorry, I encountered an error analyzing that file:</p>
|
||||
<p style="color: var(--danger);">${error.message}</p>
|
||||
<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>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = function() {
|
||||
showToast('Failed to read file', 'error');
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
|
||||
// Reset the input so the same file can be selected again
|
||||
event.target.value = '';
|
||||
}
|
||||
|
||||
async function startAnalysis() {
|
||||
if (config.uploadedFiles.length === 0) {
|
||||
showToast('Please upload at least one menu image', 'error');
|
||||
|
|
|
|||
Reference in a new issue