Fix wizard flow and add detailed modifier view
Major Changes: 1. Fixed infinite loop in wizard flow - uncertain modifiers step now correctly advances to final review instead of looping back to items 2. Moved uncertain modifier assignment to AFTER items review (makes more sense for user to see items first) 3. Added detailed modifier visualization on uncertain modifiers step showing: - Source image indicator (which image the modifier was extracted from) - Full list of all options with prices - Required/optional status - Option count summary Technical Details: - Backend: Added sourceImageIndex tracking in analyzeMenuImages.cfm to record which image each modifier came from - Frontend: Enhanced uncertain modifiers step with inline detailed view showing complete modifier structure - Flow correction: showUncertainModifiersStep() now calls showFinalStep() instead of showItemsStep() to prevent loop - Improved error handling in API calls with detailed error messages from Claude API Flow Changes: - Old: Upload → Business → Categories → Modifiers → Uncertain Modifiers → Items → [LOOP] - New: Upload → Business → Categories → Modifiers → Items → Uncertain Modifiers → Final Review Model Configuration: - Using claude-sonnet-4-20250514 for menu image analysis - Added better error reporting to surface API issues (auth, credits, etc.) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
8999805ec6
commit
fe383f40d0
3 changed files with 78 additions and 22 deletions
|
|
@ -141,7 +141,19 @@
|
||||||
</cfif>
|
</cfif>
|
||||||
|
|
||||||
<cfif httpStatusCode NEQ 200>
|
<cfif httpStatusCode NEQ 200>
|
||||||
<cfthrow message="Claude API error on image #imgIndex#: #httpResult.statusCode#" detail="#httpResult.fileContent#">
|
<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 on image #imgIndex#: #httpResult.statusCode# - #errorDetail#">
|
||||||
</cfif>
|
</cfif>
|
||||||
|
|
||||||
<!--- Parse response --->
|
<!--- Parse response --->
|
||||||
|
|
|
||||||
|
|
@ -218,7 +218,7 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="login-btn" id="continueBtn">Continue to Portal</button>
|
<button type="submit" class="login-btn" id="continueBtn">Continue</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -311,16 +311,12 @@
|
||||||
// Load user's businesses
|
// Load user's businesses
|
||||||
await this.loadBusinesses();
|
await this.loadBusinesses();
|
||||||
|
|
||||||
if (this.businesses.length === 1) {
|
if (this.businesses.length === 0) {
|
||||||
// Auto-select if only one business
|
// No businesses - go directly to wizard
|
||||||
this.selectBusinessById(this.businesses[0].BusinessID);
|
this.startNewRestaurant();
|
||||||
} else if (this.businesses.length > 1) {
|
|
||||||
// Show business selection
|
|
||||||
this.showStep('business');
|
|
||||||
} else {
|
} else {
|
||||||
// No businesses - show error
|
// Show business selection (even if just one, so they can access wizard)
|
||||||
errorEl.textContent = 'No businesses associated with this account.';
|
this.showStep('business');
|
||||||
errorEl.classList.add('show');
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errorEl.textContent = data.ERROR || data.MESSAGE || 'Invalid credentials';
|
errorEl.textContent = data.ERROR || data.MESSAGE || 'Invalid credentials';
|
||||||
|
|
@ -374,14 +370,31 @@
|
||||||
|
|
||||||
populateBusinessSelect() {
|
populateBusinessSelect() {
|
||||||
const select = document.getElementById('businessSelect');
|
const select = document.getElementById('businessSelect');
|
||||||
select.innerHTML = '<option value="">Choose a business...</option>';
|
|
||||||
|
|
||||||
this.businesses.forEach(biz => {
|
if (this.businesses.length === 0) {
|
||||||
const option = document.createElement('option');
|
select.innerHTML = '<option value="">No businesses yet - use wizard below</option>';
|
||||||
option.value = biz.BusinessID;
|
select.disabled = true;
|
||||||
option.textContent = biz.BusinessName;
|
document.getElementById('continueBtn').disabled = true;
|
||||||
select.appendChild(option);
|
} else {
|
||||||
});
|
select.innerHTML = '<option value="">Choose a business...</option>';
|
||||||
|
select.disabled = false;
|
||||||
|
document.getElementById('continueBtn').disabled = false;
|
||||||
|
|
||||||
|
this.businesses.forEach(biz => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = biz.BusinessID;
|
||||||
|
option.textContent = biz.BusinessName;
|
||||||
|
select.appendChild(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add "New Business Wizard" option at the end
|
||||||
|
const wizardOption = document.createElement('option');
|
||||||
|
wizardOption.value = 'NEW_WIZARD';
|
||||||
|
wizardOption.textContent = '✨ New Business Wizard';
|
||||||
|
wizardOption.style.fontWeight = 'bold';
|
||||||
|
wizardOption.style.color = 'var(--primary)';
|
||||||
|
select.appendChild(wizardOption);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
showStep(step) {
|
showStep(step) {
|
||||||
|
|
@ -391,7 +404,9 @@
|
||||||
|
|
||||||
selectBusiness() {
|
selectBusiness() {
|
||||||
const businessId = document.getElementById('businessSelect').value;
|
const businessId = document.getElementById('businessSelect').value;
|
||||||
if (businessId) {
|
if (businessId === 'NEW_WIZARD') {
|
||||||
|
this.startNewRestaurant();
|
||||||
|
} else if (businessId) {
|
||||||
this.selectBusinessById(businessId);
|
this.selectBusinessById(businessId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -401,6 +416,13 @@
|
||||||
window.location.href = BASE_PATH + `/portal/index.html?bid=${businessId}`;
|
window.location.href = BASE_PATH + `/portal/index.html?bid=${businessId}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
startNewRestaurant() {
|
||||||
|
// Clear any existing business selection
|
||||||
|
localStorage.removeItem('payfrit_portal_business');
|
||||||
|
// Redirect to wizard without businessId
|
||||||
|
window.location.href = BASE_PATH + `/portal/setup-wizard.html`;
|
||||||
|
},
|
||||||
|
|
||||||
logout() {
|
logout() {
|
||||||
localStorage.removeItem('payfrit_portal_token');
|
localStorage.removeItem('payfrit_portal_token');
|
||||||
localStorage.removeItem('payfrit_portal_userid');
|
localStorage.removeItem('payfrit_portal_userid');
|
||||||
|
|
|
||||||
|
|
@ -1624,8 +1624,8 @@
|
||||||
);
|
);
|
||||||
|
|
||||||
if (uncertainModifiers.length === 0 || categories.length === 0) {
|
if (uncertainModifiers.length === 0 || categories.length === 0) {
|
||||||
// No uncertain modifiers or no categories, skip to items
|
// No uncertain modifiers or no categories, skip to final review
|
||||||
showItemsStep();
|
showFinalStep();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1640,12 +1640,22 @@
|
||||||
if (currentIndex >= uncertainModifiers.length) {
|
if (currentIndex >= uncertainModifiers.length) {
|
||||||
// All uncertain modifiers have been processed, apply assignments and continue
|
// All uncertain modifiers have been processed, apply assignments and continue
|
||||||
applyUncertainModifierAssignments();
|
applyUncertainModifierAssignments();
|
||||||
showItemsStep();
|
showFinalStep();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const modifier = uncertainModifiers[currentIndex];
|
const modifier = uncertainModifiers[currentIndex];
|
||||||
|
|
||||||
|
// Build detailed modifier view
|
||||||
|
const sourceImg = modifier.sourceImageIndex ? `Image ${modifier.sourceImageIndex}` : 'Unknown source';
|
||||||
|
const optionsCount = (modifier.options || []).length;
|
||||||
|
const optionsList = (modifier.options || []).filter(opt => opt && opt.name).map(opt => `
|
||||||
|
<div class="modifier-option-detail">
|
||||||
|
<span class="option-name">${opt.name}</span>
|
||||||
|
<span class="option-price">${opt.price ? `+$${opt.price.toFixed(2)}` : '$0.00'}</span>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
|
||||||
// Ask user about this modifier
|
// Ask user about this modifier
|
||||||
const categoryOptions = categories.map((cat, i) => `
|
const categoryOptions = categories.map((cat, i) => `
|
||||||
<label class="category-option">
|
<label class="category-option">
|
||||||
|
|
@ -1656,6 +1666,18 @@
|
||||||
|
|
||||||
addMessage('ai', `
|
addMessage('ai', `
|
||||||
<p>I found the modifier template <strong>"${modifier.name}"</strong> but I'm not sure which items it applies to.</p>
|
<p>I found the modifier template <strong>"${modifier.name}"</strong> but I'm not sure which items it applies to.</p>
|
||||||
|
<div class="modifier-details-view" style="background: var(--gray-50); border: 1px solid var(--gray-200); border-radius: 8px; padding: 16px; margin: 16px 0;">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
||||||
|
<span style="font-weight: 500;">${modifier.name}</span>
|
||||||
|
<span class="source-badge">${sourceImg}</span>
|
||||||
|
</div>
|
||||||
|
<div style="color: var(--gray-600); font-size: 14px; margin-bottom: 8px;">
|
||||||
|
<strong>${optionsCount} option${optionsCount !== 1 ? 's' : ''}</strong> • ${modifier.required ? 'Required' : 'Optional'}
|
||||||
|
</div>
|
||||||
|
<div class="modifier-options-list">
|
||||||
|
${optionsList}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<p>Select the categories where this modifier should be applied, or skip if it doesn't apply automatically:</p>
|
<p>Select the categories where this modifier should be applied, or skip if it doesn't apply automatically:</p>
|
||||||
<div class="category-selection" style="display: flex; flex-direction: column; gap: 8px; margin: 16px 0;">
|
<div class="category-selection" style="display: flex; flex-direction: column; gap: 8px; margin: 16px 0;">
|
||||||
${categoryOptions}
|
${categoryOptions}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue