Add URL-based menu import to setup wizard

This commit is contained in:
John Mizerek 2026-02-12 16:43:37 -08:00
parent fccbc17fe3
commit f6518932db
2 changed files with 535 additions and 18 deletions

View file

@ -0,0 +1,368 @@
<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfsetting requesttimeout="300">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfset response = structNew()>
<cfset response["OK"] = false>
<cftry>
<!--- Load API Key --->
<cfset CLAUDE_API_KEY = "">
<cfset configPath = getDirectoryFromPath(getCurrentTemplatePath()) & "../../config/claude.json">
<cfif fileExists(configPath)>
<cfset configData = deserializeJSON(fileRead(configPath))>
<cfif structKeyExists(configData, "apiKey")>
<cfset CLAUDE_API_KEY = configData.apiKey>
</cfif>
</cfif>
<cfif NOT len(CLAUDE_API_KEY)>
<cfthrow message="Claude API key not configured">
</cfif>
<!--- Get URL from request --->
<cfset requestBody = toString(getHttpRequestData().content)>
<cfif NOT len(requestBody)>
<cfthrow message="No request body provided">
</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)>
<!--- 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>
<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>
<!--- Find menu links and fetch them too --->
<cfset menuPages = arrayNew(1)>
<cfset arrayAppend(menuPages, { url: targetUrl, html: pageHtml })>
<!--- Look for menu links in the page --->
<cfset menuLinkPatterns = 'href=["'']([^"'']*(?:menu|food|dishes|order)[^"'']*)["'']'>
<cfset menuLinks = reMatchNoCase(menuLinkPatterns, pageHtml)>
<cfloop array="#menuLinks#" index="linkMatch">
<cfset linkUrl = reReplaceNoCase(linkMatch, 'href=["'']([^"'']*)["'']', "\1")>
<!--- Resolve relative URLs --->
<cfif left(linkUrl, 1) EQ "/">
<cfset linkUrl = baseUrl & linkUrl>
<cfelseif NOT reFindNoCase("^https?://", linkUrl)>
<cfset linkUrl = basePath & linkUrl>
</cfif>
<!--- Skip if same as main page or external domain --->
<cfif linkUrl NEQ targetUrl AND findNoCase(baseUrl, linkUrl)>
<cftry>
<cfhttp url="#linkUrl#" method="GET" timeout="15" result="subPage" 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>
<cfif findNoCase("200", subPage.statusCode)>
<cfset arrayAppend(menuPages, { url: linkUrl, html: subPage.fileContent })>
<cfset arrayAppend(response.steps, "Found menu page: " & linkUrl)>
</cfif>
<cfcatch>
<!--- Skip failed requests --->
</cfcatch>
</cftry>
</cfif>
<!--- Limit to 5 pages max --->
<cfif arrayLen(menuPages) GTE 5>
<cfbreak>
</cfif>
</cfloop>
<!--- Extract images from all pages --->
<cfset allImages = arrayNew(1)>
<cfset imageUrls = structNew()>
<cfloop array="#menuPages#" index="menuPage">
<!--- Find all img tags --->
<cfset imgMatches = reMatchNoCase('<img[^>]+src=["'']([^"'']+)["''][^>]*>', menuPage.html)>
<cfloop array="#imgMatches#" index="imgTag">
<cfset imgSrc = reReplaceNoCase(imgTag, '.*src=["'']([^"'']+)["''].*', "\1")>
<!--- Resolve relative URLs --->
<cfif left(imgSrc, 1) EQ "/">
<cfset imgSrc = baseUrl & imgSrc>
<cfelseif NOT reFindNoCase("^https?://", imgSrc) AND NOT reFindNoCase("^data:", imgSrc)>
<cfset imgSrc = basePath & imgSrc>
</cfif>
<!--- Skip data URLs, icons, and already-processed images --->
<cfif reFindNoCase("^https?://", imgSrc) AND NOT structKeyExists(imageUrls, imgSrc)>
<!--- Skip common icon/logo patterns that are too small --->
<cfif NOT reFindNoCase("(icon|favicon|logo|sprite|pixel|tracking|badge|button)", imgSrc)>
<cfset imageUrls[imgSrc] = true>
</cfif>
</cfif>
</cfloop>
</cfloop>
<cfset arrayAppend(response.steps, "Found #structCount(imageUrls)# unique images")>
<!--- Download images (limit to 20) --->
<cfset imageDataArray = arrayNew(1)>
<cfset downloadedCount = 0>
<cfloop collection="#imageUrls#" item="imgUrl">
<cfif downloadedCount GTE 20>
<cfbreak>
</cfif>
<cftry>
<cfhttp url="#imgUrl#" method="GET" timeout="10" result="imgResult" getasbinary="yes">
</cfhttp>
<cfif findNoCase("200", imgResult.statusCode) AND isBinary(imgResult.fileContent)>
<!--- Check content type --->
<cfset contentType = structKeyExists(imgResult.responseHeader, "Content-Type") ? imgResult.responseHeader["Content-Type"] : "">
<cfif reFindNoCase("image/(jpeg|jpg|png|gif|webp)", contentType)>
<!--- Check image size (skip tiny images) --->
<cfset imgBytes = len(imgResult.fileContent)>
<cfif imgBytes GT 5000>
<cfset base64Content = toBase64(imgResult.fileContent)>
<cfset mediaType = "image/jpeg">
<cfif findNoCase("png", contentType)><cfset mediaType = "image/png"></cfif>
<cfif findNoCase("gif", contentType)><cfset mediaType = "image/gif"></cfif>
<cfif findNoCase("webp", contentType)><cfset mediaType = "image/webp"></cfif>
<cfset imgSource = structNew()>
<cfset imgSource["type"] = "base64">
<cfset imgSource["media_type"] = mediaType>
<cfset imgSource["data"] = base64Content>
<cfset imgStruct = structNew()>
<cfset imgStruct["type"] = "image">
<cfset imgStruct["source"] = imgSource>
<cfset imgStruct["url"] = imgUrl>
<cfset arrayAppend(imageDataArray, imgStruct)>
<cfset downloadedCount = downloadedCount + 1>
</cfif>
</cfif>
</cfif>
<cfcatch>
<!--- Skip failed downloads --->
</cfcatch>
</cftry>
</cfloop>
<cfset arrayAppend(response.steps, "Downloaded #arrayLen(imageDataArray)# valid images")>
<!--- Combine all page HTML into one text block --->
<cfset combinedHtml = "">
<cfloop array="#menuPages#" index="menuPage">
<!--- Strip scripts, styles, and extract text content --->
<cfset cleanHtml = menuPage.html>
<cfset cleanHtml = reReplaceNoCase(cleanHtml, "<script[^>]*>.*?</script>", "", "all")>
<cfset cleanHtml = reReplaceNoCase(cleanHtml, "<style[^>]*>.*?</style>", "", "all")>
<cfset cleanHtml = reReplaceNoCase(cleanHtml, "<!--.*?-->", "", "all")>
<cfset combinedHtml = combinedHtml & chr(10) & "--- PAGE: " & menuPage.url & " ---" & chr(10) & cleanHtml>
</cfloop>
<!--- Limit HTML size for Claude --->
<cfif len(combinedHtml) GT 100000>
<cfset combinedHtml = left(combinedHtml, 100000)>
</cfif>
<!--- System prompt for URL analysis --->
<cfset systemPrompt = "You are an expert at extracting structured menu data from restaurant website HTML. Extract ALL menu data visible in the HTML. Return valid JSON with these keys: business (object with name, address, phone, hours, brandColor), categories (array of category names), modifiers (array of modifier templates with name, required boolean, appliesTo, categoryName if applicable, and options array), items (array with name, description, price, category, modifiers array, and imageUrl if found). For brandColor: suggest a vibrant hex color (6 digits, no ##) based on the restaurant style. For hours: format as ""Mon-Fri 10:30am-10pm, Sat 11am-10pm, Sun 11am-9pm"". Include ALL days visible. For prices: extract as numbers (e.g., 12.99). For modifier options: use format {""name"": ""option"", ""price"": 0}. Return ONLY valid JSON, no markdown, no explanation.">
<!--- Build message content --->
<cfset messagesContent = arrayNew(1)>
<!--- Add images first (up to 10 for analysis) --->
<cfset imgLimit = min(arrayLen(imageDataArray), 10)>
<cfloop from="1" to="#imgLimit#" index="i">
<cfset imgData = imageDataArray[i]>
<cfset imgContent = structNew()>
<cfset imgContent["type"] = "image">
<cfset imgContent["source"] = imgData.source>
<cfset arrayAppend(messagesContent, imgContent)>
</cfloop>
<!--- Add HTML text --->
<cfset textBlock = structNew()>
<cfset textBlock["type"] = "text">
<cfset textBlock["text"] = "Extract menu data from this restaurant website HTML. The images above are from the same website - identify which ones are food photos that could be used as item images, and which could be header/banner images. Here is the HTML content:" & chr(10) & chr(10) & combinedHtml>
<cfset arrayAppend(messagesContent, textBlock)>
<cfset userMessage = structNew()>
<cfset userMessage["role"] = "user">
<cfset userMessage["content"] = messagesContent>
<cfset requestBody = structNew()>
<cfset requestBody["model"] = "claude-sonnet-4-20250514">
<cfset requestBody["max_tokens"] = 8192>
<cfset requestBody["temperature"] = 0>
<cfset requestBody["system"] = systemPrompt>
<cfset requestBody["messages"] = arrayNew(1)>
<cfset arrayAppend(requestBody["messages"], userMessage)>
<cfset arrayAppend(response.steps, "Sending to Claude API...")>
<!--- Call Claude API --->
<cfhttp url="https://api.anthropic.com/v1/messages" method="POST" timeout="120" result="httpResult">
<cfhttpparam type="header" name="Content-Type" value="application/json">
<cfhttpparam type="header" name="x-api-key" value="#CLAUDE_API_KEY#">
<cfhttpparam type="header" name="anthropic-version" value="2023-06-01">
<cfhttpparam type="body" value="#serializeJSON(requestBody)#">
</cfhttp>
<cfset httpStatusCode = httpResult.statusCode>
<cfif isNumeric(httpStatusCode)>
<cfset httpStatusCode = int(httpStatusCode)>
<cfelseif findNoCase("200", httpStatusCode)>
<cfset httpStatusCode = 200>
<cfelse>
<cfset httpStatusCode = 0>
</cfif>
<cfif httpStatusCode NEQ 200>
<cfset errorDetail = "">
<cftry>
<cfset errorResponse = deserializeJSON(httpResult.fileContent)>
<cfif structKeyExists(errorResponse, "error") AND structKeyExists(errorResponse.error, "message")>
<cfset errorDetail = errorResponse.error.message>
<cfelse>
<cfset errorDetail = httpResult.fileContent>
</cfif>
<cfcatch>
<cfset errorDetail = httpResult.fileContent>
</cfcatch>
</cftry>
<cfthrow message="Claude API error: #httpResult.statusCode# - #errorDetail#">
</cfif>
<!--- Parse response --->
<cfset claudeResponse = deserializeJSON(httpResult.fileContent)>
<cfif NOT structKeyExists(claudeResponse, "content") OR NOT arrayLen(claudeResponse.content)>
<cfthrow message="Empty response from Claude">
</cfif>
<cfset responseText = "">
<cfloop array="#claudeResponse.content#" index="block">
<cfif structKeyExists(block, "type") AND block.type EQ "text">
<cfset responseText = block.text>
<cfbreak>
</cfif>
</cfloop>
<!--- Clean up JSON response --->
<cfset responseText = trim(responseText)>
<cfif left(responseText, 7) EQ "```json">
<cfset responseText = mid(responseText, 8, len(responseText) - 7)>
</cfif>
<cfif left(responseText, 3) EQ "```">
<cfset responseText = mid(responseText, 4, len(responseText) - 3)>
</cfif>
<cfif right(responseText, 3) EQ "```">
<cfset responseText = left(responseText, len(responseText) - 3)>
</cfif>
<cfset responseText = trim(responseText)>
<cfset responseText = reReplace(responseText, ",(\s*[\]\}])", "\1", "all")>
<cfset menuData = deserializeJSON(responseText)>
<!--- Build image URL list for the wizard to use --->
<cfset imageUrlList = arrayNew(1)>
<cfloop array="#imageDataArray#" index="imgData">
<cfif structKeyExists(imgData, "url")>
<cfset arrayAppend(imageUrlList, imgData.url)>
</cfif>
</cfloop>
<!--- Ensure expected structure --->
<cfif NOT structKeyExists(menuData, "business")>
<cfset menuData["business"] = structNew()>
</cfif>
<cfif NOT structKeyExists(menuData, "categories")>
<cfset menuData["categories"] = arrayNew(1)>
</cfif>
<cfif NOT structKeyExists(menuData, "modifiers")>
<cfset menuData["modifiers"] = arrayNew(1)>
</cfif>
<cfif NOT structKeyExists(menuData, "items")>
<cfset menuData["items"] = arrayNew(1)>
</cfif>
<!--- Convert categories to expected format if needed --->
<cfset formattedCategories = arrayNew(1)>
<cfloop array="#menuData.categories#" index="cat">
<cfif isSimpleValue(cat)>
<cfset catObj = structNew()>
<cfset catObj["name"] = cat>
<cfset catObj["itemCount"] = 0>
<cfset arrayAppend(formattedCategories, catObj)>
<cfelseif isStruct(cat)>
<cfif NOT structKeyExists(cat, "itemCount")>
<cfset cat["itemCount"] = 0>
</cfif>
<cfset arrayAppend(formattedCategories, cat)>
</cfif>
</cfloop>
<cfset menuData["categories"] = formattedCategories>
<!--- Add item IDs --->
<cfloop from="1" to="#arrayLen(menuData.items)#" index="i">
<cfset menuData.items[i]["id"] = "item_" & i>
</cfloop>
<!--- Add image URLs to response --->
<cfset menuData["imageUrls"] = imageUrlList>
<cfset menuData["headerCandidateIndices"] = arrayNew(1)>
<cfset response["OK"] = true>
<cfset response["DATA"] = menuData>
<cfset response["sourceUrl"] = targetUrl>
<cfset response["pagesProcessed"] = arrayLen(menuPages)>
<cfset response["imagesFound"] = arrayLen(imageDataArray)>
<cfcatch type="any">
<cfset response["MESSAGE"] = cfcatch.message>
<cfif len(cfcatch.detail)>
<cfset response["DETAIL"] = cfcatch.detail>
</cfif>
<cfif structKeyExists(cfcatch, "tagContext") AND arrayLen(cfcatch.tagContext) GT 0>
<cfset response["DEBUG_LINE"] = cfcatch.tagContext[1].line>
<cfset response["DEBUG_TEMPLATE"] = cfcatch.tagContext[1].template>
</cfif>
</cfcatch>
</cftry>
<cfoutput>#serializeJSON(response)#</cfoutput>

View file

@ -769,31 +769,71 @@
<!-- Wizard Header --> <!-- Wizard Header -->
<div class="wizard-header"> <div class="wizard-header">
<h1>Let's Setup Your Menu</h1> <h1>Let's Setup Your Menu</h1>
<p>Upload your menu images or PDFs and I'll extract then input all the information for you to preview!</p> <p>Import your menu from a website URL or upload images/PDFs</p>
</div> </div>
<!-- Upload Section --> <!-- Upload Section -->
<div id="uploadSection"> <div id="uploadSection">
<div class="upload-zone" id="uploadZone"> <!-- Import Method Tabs -->
<svg class="upload-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <div class="import-tabs" style="display:flex;gap:0;margin-bottom:20px;border-radius:8px;overflow:hidden;border:1px solid var(--gray-300);">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> <button class="import-tab active" id="tabUrl" onclick="switchImportTab('url')" style="flex:1;padding:12px 16px;border:none;background:var(--primary);color:white;font-weight:500;cursor:pointer;transition:all 0.2s;">
<polyline points="17 8 12 3 7 8"/> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align:middle;margin-right:6px;">
<line x1="12" y1="3" x2="12" y2="15"/> <circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>
</svg> </svg>
<h3>Drop your menu images here</h3> Import from URL
<p>or click to browse (JPG, PNG, PDF supported)</p> </button>
<input type="file" id="fileInput" multiple accept="image/*,.pdf"> <button class="import-tab" id="tabUpload" onclick="switchImportTab('upload')" style="flex:1;padding:12px 16px;border:none;background:var(--gray-100);color:var(--gray-700);font-weight:500;cursor:pointer;transition:all 0.2s;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align:middle;margin-right:6px;">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/>
</svg>
Upload Files
</button>
</div> </div>
<div class="file-preview-grid" id="filePreviewGrid"></div> <!-- URL Import Panel -->
<div id="urlImportPanel">
<div class="action-buttons" id="uploadActions" style="display: none;"> <div style="background:var(--gray-50);border:2px dashed var(--gray-300);border-radius:12px;padding:32px;text-align:center;">
<button class="btn btn-primary" onclick="startAnalysis()"> <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="var(--gray-400)" stroke-width="1.5" style="margin-bottom:16px;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>
<polygon points="5 3 19 12 5 21 5 3"/>
</svg> </svg>
Analyze Menu <h3 style="margin:0 0 8px;color:var(--gray-700);">Enter Restaurant Website URL</h3>
</button> <p style="margin:0 0 16px;color:var(--gray-500);font-size:14px;">We'll crawl the site to extract menu items, prices, images, and business info</p>
<input type="url" id="menuUrlInput" placeholder="https://restaurant-website.com" style="width:100%;max-width:400px;padding:12px 16px;border:1px solid var(--gray-300);border-radius:8px;font-size:16px;margin-bottom:16px;">
<div>
<button class="btn btn-primary" onclick="startUrlAnalysis()" 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;">
<circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>
</svg>
Import Menu
</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>
</div>
</div>
<!-- File Upload Panel (hidden by default) -->
<div id="fileUploadPanel" style="display:none;">
<div class="upload-zone" id="uploadZone">
<svg class="upload-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="17 8 12 3 7 8"/>
<line x1="12" y1="3" x2="12" y2="15"/>
</svg>
<h3>Drop your menu images here</h3>
<p>or click to browse (JPG, PNG, PDF supported)</p>
<input type="file" id="fileInput" multiple accept="image/*,.pdf">
</div>
<div class="file-preview-grid" id="filePreviewGrid"></div>
<div class="action-buttons" id="uploadActions" style="display: none;">
<button class="btn btn-primary" onclick="startAnalysis()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polygon points="5 3 19 12 5 21 5 3"/>
</svg>
Analyze Menu
</button>
</div>
</div> </div>
</div> </div>
@ -1209,6 +1249,115 @@
} }
} }
// Switch between URL and file upload tabs
function switchImportTab(tab) {
const tabUrl = document.getElementById('tabUrl');
const tabUpload = document.getElementById('tabUpload');
const urlPanel = document.getElementById('urlImportPanel');
const filePanel = document.getElementById('fileUploadPanel');
if (tab === 'url') {
tabUrl.style.background = 'var(--primary)';
tabUrl.style.color = 'white';
tabUpload.style.background = 'var(--gray-100)';
tabUpload.style.color = 'var(--gray-700)';
urlPanel.style.display = 'block';
filePanel.style.display = 'none';
} else {
tabUpload.style.background = 'var(--primary)';
tabUpload.style.color = 'white';
tabUrl.style.background = 'var(--gray-100)';
tabUrl.style.color = 'var(--gray-700)';
urlPanel.style.display = 'none';
filePanel.style.display = 'block';
}
}
// URL-based menu import
async function startUrlAnalysis() {
const urlInput = document.getElementById('menuUrlInput');
const url = urlInput.value.trim();
if (!url) {
showToast('Please enter a website URL', 'error');
return;
}
// 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>Crawling website and extracting menu data...</span>
</div>
<p style="font-size:13px;color:var(--gray-500);margin-top:8px;">This may take 30-60 seconds while I fetch pages, download images, and analyze everything.</p>
`);
try {
const response = await fetch(`${config.apiBaseUrl}/setup/analyzeMenuUrl.cfm`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url })
});
const result = await response.json();
if (!result.OK) {
throw new Error(result.MESSAGE || 'Failed to analyze URL');
}
// Store extracted data
config.extractedData = result.DATA;
config.sourceUrl = result.sourceUrl;
// 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('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('URL analysis error:', error);
document.getElementById('conversation').innerHTML = '';
addMessage('ai', `
<p>Sorry, I encountered an error importing from that URL:</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 Files Instead</button>
</div>
`);
}
}
function retryUrlAnalysis() {
document.getElementById('conversation').innerHTML = '';
document.getElementById('uploadSection').style.display = 'block';
switchImportTab('url');
}
function switchToFileUpload() {
document.getElementById('conversation').innerHTML = '';
document.getElementById('uploadSection').style.display = 'block';
switchImportTab('upload');
}
async function startAnalysis() { async function startAnalysis() {
if (config.uploadedFiles.length === 0) { if (config.uploadedFiles.length === 0) {
showToast('Please upload at least one menu image', 'error'); showToast('Please upload at least one menu image', 'error');