Update AI prompts to intelligently assign modifiers to items
- Instructs Claude to look for modifiers in specific menu locations: category headers, item descriptions, asterisk notes, headers/footers - Assigns modifiers to items when confident about relationships - Only links obvious modifier-to-item connections - Leaves uncertain relationships unlinked for manual assignment Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
a4fb7e7503
commit
0d2634e6c4
1 changed files with 258 additions and 252 deletions
|
|
@ -1,33 +1,14 @@
|
||||||
<cfsetting showdebugoutput="false">
|
<cfsetting showdebugoutput="false">
|
||||||
<cfsetting enablecfoutputonly="true">
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
<cfsetting requesttimeout="900">
|
||||||
<cfcontent type="application/json; charset=utf-8" reset="true">
|
<cfcontent type="application/json; charset=utf-8" reset="true">
|
||||||
|
|
||||||
<!---
|
|
||||||
Analyze Menu Images using Claude Vision API
|
|
||||||
|
|
||||||
Accepts uploaded menu images (JPG, PNG, PDF) and sends them to Claude
|
|
||||||
to extract structured menu data including:
|
|
||||||
- Business info (name, address, phone, hours)
|
|
||||||
- Categories
|
|
||||||
- Modifier templates
|
|
||||||
- Menu items with prices and descriptions
|
|
||||||
|
|
||||||
Returns structured JSON for the setup wizard to display
|
|
||||||
--->
|
|
||||||
|
|
||||||
<cfset response = structNew()>
|
<cfset response = structNew()>
|
||||||
<cfset response["OK"] = false>
|
<cfset response["OK"] = false>
|
||||||
|
|
||||||
<!--- Claude API key - should be in environment or config --->
|
|
||||||
<cfset CLAUDE_API_KEY = "">
|
|
||||||
<cfif structKeyExists(application, "claudeApiKey") AND len(application.claudeApiKey)>
|
|
||||||
<cfset CLAUDE_API_KEY = application.claudeApiKey>
|
|
||||||
</cfif>
|
|
||||||
|
|
||||||
<!--- If not in application scope, try to read from config --->
|
|
||||||
<cfif NOT len(CLAUDE_API_KEY)>
|
|
||||||
<cftry>
|
<cftry>
|
||||||
<!--- Try relative path from current file location first --->
|
<!--- Load API Key --->
|
||||||
|
<cfset CLAUDE_API_KEY = "">
|
||||||
<cfset configPath = getDirectoryFromPath(getCurrentTemplatePath()) & "../../config/claude.json">
|
<cfset configPath = getDirectoryFromPath(getCurrentTemplatePath()) & "../../config/claude.json">
|
||||||
<cfif fileExists(configPath)>
|
<cfif fileExists(configPath)>
|
||||||
<cfset configData = deserializeJSON(fileRead(configPath))>
|
<cfset configData = deserializeJSON(fileRead(configPath))>
|
||||||
|
|
@ -35,33 +16,13 @@
|
||||||
<cfset CLAUDE_API_KEY = configData.apiKey>
|
<cfset CLAUDE_API_KEY = configData.apiKey>
|
||||||
</cfif>
|
</cfif>
|
||||||
</cfif>
|
</cfif>
|
||||||
<!--- Fallback to expandPath if relative path didn't work --->
|
|
||||||
<cfif NOT len(CLAUDE_API_KEY)>
|
|
||||||
<cfset configPath = expandPath("/biz.payfrit.com/config/claude.json")>
|
|
||||||
<cfif fileExists(configPath)>
|
|
||||||
<cfset configData = deserializeJSON(fileRead(configPath))>
|
|
||||||
<cfif structKeyExists(configData, "apiKey")>
|
|
||||||
<cfset CLAUDE_API_KEY = configData.apiKey>
|
|
||||||
</cfif>
|
|
||||||
</cfif>
|
|
||||||
</cfif>
|
|
||||||
<cfcatch type="any">
|
|
||||||
<!--- Config file doesn't exist or is invalid --->
|
|
||||||
</cfcatch>
|
|
||||||
</cftry>
|
|
||||||
</cfif>
|
|
||||||
|
|
||||||
<cfif NOT len(CLAUDE_API_KEY)>
|
<cfif NOT len(CLAUDE_API_KEY)>
|
||||||
<cfset response["MESSAGE"] = "Claude API key not configured">
|
<cfthrow message="Claude API key not configured">
|
||||||
<cfoutput>#serializeJSON(response)#</cfoutput>
|
|
||||||
<cfabort>
|
|
||||||
</cfif>
|
</cfif>
|
||||||
|
|
||||||
<cftry>
|
<!--- Find uploaded files --->
|
||||||
<!--- Check for uploaded files --->
|
|
||||||
<cfset uploadedFiles = arrayNew(1)>
|
<cfset uploadedFiles = arrayNew(1)>
|
||||||
|
|
||||||
<!--- Get form fields for file uploads (file0, file1, file2, etc.) --->
|
|
||||||
<cfset formFields = form.keyArray()>
|
<cfset formFields = form.keyArray()>
|
||||||
<cfloop array="#formFields#" index="fieldName">
|
<cfloop array="#formFields#" index="fieldName">
|
||||||
<cfif reFindNoCase("^file[0-9]+$", fieldName) AND len(form[fieldName])>
|
<cfif reFindNoCase("^file[0-9]+$", fieldName) AND len(form[fieldName])>
|
||||||
|
|
@ -73,82 +34,57 @@
|
||||||
<cfthrow message="No files uploaded">
|
<cfthrow message="No files uploaded">
|
||||||
</cfif>
|
</cfif>
|
||||||
|
|
||||||
<!--- Process uploaded files - convert to base64 --->
|
<!--- Upload and prepare all images first --->
|
||||||
<cfset imageDataArray = arrayNew(1)>
|
<cfset imageDataArray = arrayNew(1)>
|
||||||
|
|
||||||
<!--- Create temp directory for uploads if needed --->
|
|
||||||
<cfset uploadDir = getTempDirectory() & "payfrit_menu_uploads/">
|
<cfset uploadDir = getTempDirectory() & "payfrit_menu_uploads/">
|
||||||
<cfif NOT directoryExists(uploadDir)>
|
<cfif NOT directoryExists(uploadDir)>
|
||||||
<cfdirectory action="create" directory="#uploadDir#">
|
<cfdirectory action="create" directory="#uploadDir#">
|
||||||
</cfif>
|
</cfif>
|
||||||
|
|
||||||
<cfloop array="#uploadedFiles#" index="fieldName">
|
<cfloop array="#uploadedFiles#" index="fieldName">
|
||||||
<!--- Upload the file --->
|
<cffile action="upload" destination="#uploadDir#" filefield="#fieldName#" accept="image/jpeg,image/png,image/gif,image/webp,application/pdf" nameconflict="makeunique" result="uploadResult">
|
||||||
<cffile action="upload" destination="#uploadDir#" filefield="#fieldName#"
|
|
||||||
accept="image/jpeg,image/png,image/gif,image/webp,application/pdf"
|
|
||||||
nameconflict="makeunique" result="uploadResult">
|
|
||||||
|
|
||||||
<cfif uploadResult.fileWasSaved>
|
<cfif uploadResult.fileWasSaved>
|
||||||
<cfset filePath = uploadResult.serverDirectory & "/" & uploadResult.serverFile>
|
<cfset filePath = uploadResult.serverDirectory & "/" & uploadResult.serverFile>
|
||||||
<cfset fileExt = lCase(uploadResult.serverFileExt)>
|
<cfset fileExt = lCase(uploadResult.serverFileExt)>
|
||||||
|
|
||||||
<!--- For images, resize if too large (max 1600px on longest side) --->
|
<!--- Resize large images --->
|
||||||
<cfif listFindNoCase("jpg,jpeg,png,gif,webp", fileExt)>
|
<cfif listFindNoCase("jpg,jpeg,png,gif,webp", fileExt)>
|
||||||
<cfimage action="read" source="#filePath#" name="img">
|
<cfimage action="read" source="#filePath#" name="img">
|
||||||
<cfset imgWidth = img.width>
|
<cfif img.width GT 1600 OR img.height GT 1600>
|
||||||
<cfset imgHeight = img.height>
|
<cfif img.width GT img.height>
|
||||||
<cfset maxDimension = 1600>
|
<cfimage action="resize" source="#img#" width="1600" name="img">
|
||||||
|
|
||||||
<cfif imgWidth GT maxDimension OR imgHeight GT maxDimension>
|
|
||||||
<cfif imgWidth GT imgHeight>
|
|
||||||
<cfset newWidth = maxDimension>
|
|
||||||
<cfset newHeight = int(imgHeight * (maxDimension / imgWidth))>
|
|
||||||
<cfelse>
|
<cfelse>
|
||||||
<cfset newHeight = maxDimension>
|
<cfimage action="resize" source="#img#" height="1600" name="img">
|
||||||
<cfset newWidth = int(imgWidth * (maxDimension / imgHeight))>
|
|
||||||
</cfif>
|
</cfif>
|
||||||
<cfimage action="resize" source="#img#" width="#newWidth#" height="#newHeight#" name="img">
|
|
||||||
</cfif>
|
|
||||||
<!--- Re-save with good quality compression --->
|
|
||||||
<cfimage action="write" source="#img#" destination="#filePath#" quality="0.8" overwrite="true">
|
<cfimage action="write" source="#img#" destination="#filePath#" quality="0.8" overwrite="true">
|
||||||
</cfif>
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
<!--- Read file and convert to base64 --->
|
<!--- Read and encode --->
|
||||||
<cffile action="readbinary" file="#filePath#" variable="fileContent">
|
<cffile action="readbinary" file="#filePath#" variable="fileContent">
|
||||||
<cfset base64Content = toBase64(fileContent)>
|
<cfset base64Content = toBase64(fileContent)>
|
||||||
|
|
||||||
<!--- Determine media type --->
|
|
||||||
<cfset mediaType = "image/jpeg">
|
<cfset mediaType = "image/jpeg">
|
||||||
<cfif fileExt EQ "png">
|
<cfif fileExt EQ "png"><cfset mediaType = "image/png"></cfif>
|
||||||
<cfset mediaType = "image/png">
|
<cfif fileExt EQ "gif"><cfset mediaType = "image/gif"></cfif>
|
||||||
<cfelseif fileExt EQ "gif">
|
<cfif fileExt EQ "webp"><cfset mediaType = "image/webp"></cfif>
|
||||||
<cfset mediaType = "image/gif">
|
<cfif fileExt EQ "pdf"><cfset mediaType = "application/pdf"></cfif>
|
||||||
<cfelseif fileExt EQ "webp">
|
|
||||||
<cfset mediaType = "image/webp">
|
|
||||||
<cfelseif fileExt EQ "pdf">
|
|
||||||
<cfset mediaType = "application/pdf">
|
|
||||||
</cfif>
|
|
||||||
|
|
||||||
<!--- Claude API uses different structure for PDFs vs images --->
|
<cfset imgSource = structNew()>
|
||||||
|
<cfset imgSource["type"] = "base64">
|
||||||
|
<cfset imgSource["media_type"] = mediaType>
|
||||||
|
<cfset imgSource["data"] = base64Content>
|
||||||
|
|
||||||
|
<cfset imgStruct = structNew()>
|
||||||
<cfif fileExt EQ "pdf">
|
<cfif fileExt EQ "pdf">
|
||||||
<cfset imgStruct = structNew()>
|
|
||||||
<cfset imgStruct["type"] = "document">
|
<cfset imgStruct["type"] = "document">
|
||||||
<cfset imgStruct["source"] = structNew()>
|
|
||||||
<cfset imgStruct["source"]["type"] = "base64">
|
|
||||||
<cfset imgStruct["source"]["media_type"] = "application/pdf">
|
|
||||||
<cfset imgStruct["source"]["data"] = base64Content>
|
|
||||||
<cfset arrayAppend(imageDataArray, imgStruct)>
|
|
||||||
<cfelse>
|
<cfelse>
|
||||||
<cfset imgStruct = structNew()>
|
|
||||||
<cfset imgStruct["type"] = "image">
|
<cfset imgStruct["type"] = "image">
|
||||||
<cfset imgStruct["source"] = structNew()>
|
|
||||||
<cfset imgStruct["source"]["type"] = "base64">
|
|
||||||
<cfset imgStruct["source"]["media_type"] = mediaType>
|
|
||||||
<cfset imgStruct["source"]["data"] = base64Content>
|
|
||||||
<cfset arrayAppend(imageDataArray, imgStruct)>
|
|
||||||
</cfif>
|
</cfif>
|
||||||
|
<cfset imgStruct["source"] = imgSource>
|
||||||
|
<cfset arrayAppend(imageDataArray, imgStruct)>
|
||||||
|
|
||||||
<!--- Clean up temp file --->
|
|
||||||
<cffile action="delete" file="#filePath#">
|
<cffile action="delete" file="#filePath#">
|
||||||
</cfif>
|
</cfif>
|
||||||
</cfloop>
|
</cfloop>
|
||||||
|
|
@ -157,26 +93,24 @@
|
||||||
<cfthrow message="No valid images could be processed">
|
<cfthrow message="No valid images could be processed">
|
||||||
</cfif>
|
</cfif>
|
||||||
|
|
||||||
<!--- Build the prompt for Claude --->
|
<!--- System prompt for per-image analysis --->
|
||||||
<cfset systemPrompt = "You are an expert at extracting structured menu data from restaurant menu images. Your task is to analyze menu images and return the data in a specific JSON format. Be thorough and extract ALL items, categories, and any modifier patterns you can identify.">
|
<cfset systemPrompt = "You are an expert at extracting structured menu data from restaurant menu images. Extract ALL data visible on THIS image only. Return valid JSON with these keys: business (object with name, address, phone, hours - only if visible), categories (array of category names visible), modifiers (array of modifier templates with name, required boolean, and options array where each option is an object with 'name' and 'price' keys), items (array with name, description, price, category, and modifiers array). For modifier options, ALWAYS use format: {""name"": ""option name"", ""price"": 0}. IMPORTANT: Look for modifiers in these locations: (1) text under category headers (e.g., 'All burgers include...', 'Choose your size:'), (2) item descriptions (e.g., 'served with choice of side'), (3) asterisk notes, (4) header/footer text. For each item, include a 'modifiers' array with names of modifier templates that apply based on: category-level modifiers, item description mentions, or obvious context. Only include modifiers when confident - when unsure, omit them. Return ONLY valid JSON, no markdown, no explanation.">
|
||||||
|
|
||||||
<cfset userPrompt = "Please analyze these menu images and extract all the information into a JSON object with these keys: business (with name, address, phone, hours), categories (array with name and itemCount), modifiers (array with name, required boolean, and options array), and items (array with name, description, price, category, and modifiers array). Extract EVERY menu item with accurate prices. Group items by category. Look for modifier patterns like sizes, protein choices, bread choices, and add-ons. Create reusable modifier templates for patterns that appear on multiple items. Return ONLY valid JSON, no other text.">
|
<!--- Process each image individually --->
|
||||||
|
<cfset allResults = arrayNew(1)>
|
||||||
|
|
||||||
<!--- Build the messages array with images --->
|
<cfloop from="1" to="#arrayLen(imageDataArray)#" index="imgIndex">
|
||||||
|
<cfset imgData = imageDataArray[imgIndex]>
|
||||||
|
|
||||||
|
<!--- Build message for this single image --->
|
||||||
<cfset messagesContent = arrayNew(1)>
|
<cfset messagesContent = arrayNew(1)>
|
||||||
|
|
||||||
<!--- Add each image --->
|
|
||||||
<cfloop array="#imageDataArray#" index="imgData">
|
|
||||||
<cfset arrayAppend(messagesContent, imgData)>
|
<cfset arrayAppend(messagesContent, imgData)>
|
||||||
</cfloop>
|
|
||||||
|
|
||||||
<!--- Add the text prompt --->
|
|
||||||
<cfset textBlock = structNew()>
|
<cfset textBlock = structNew()>
|
||||||
<cfset textBlock["type"] = "text">
|
<cfset textBlock["type"] = "text">
|
||||||
<cfset textBlock["text"] = userPrompt>
|
<cfset textBlock["text"] = "Extract all menu data from this image. Return JSON with: business (if visible), categories, modifiers (with options as objects with name and price keys), items (with modifiers array). Look for modifiers under category headers, in item descriptions, in notes/asterisks, or in headers/footers. Assign modifiers to items based on: category-level rules (e.g., 'all burgers include...'), item descriptions, or obvious context. Example item: {""name"": ""Cheeseburger"", ""price"": 12.99, ""category"": ""Burgers"", ""modifiers"": [""Size"", ""Toppings""]}. Example modifier: {""name"": ""Size"", ""required"": true, ""options"": [{""name"": ""Small"", ""price"": 0}, {""name"": ""Large"", ""price"": 1.50}]}">
|
||||||
<cfset arrayAppend(messagesContent, textBlock)>
|
<cfset arrayAppend(messagesContent, textBlock)>
|
||||||
|
|
||||||
<!--- Build request body --->
|
|
||||||
<cfset userMessage = structNew()>
|
<cfset userMessage = structNew()>
|
||||||
<cfset userMessage["role"] = "user">
|
<cfset userMessage["role"] = "user">
|
||||||
<cfset userMessage["content"] = messagesContent>
|
<cfset userMessage["content"] = messagesContent>
|
||||||
|
|
@ -184,108 +118,47 @@
|
||||||
<cfset requestBody = structNew()>
|
<cfset requestBody = structNew()>
|
||||||
<cfset requestBody["model"] = "claude-sonnet-4-20250514">
|
<cfset requestBody["model"] = "claude-sonnet-4-20250514">
|
||||||
<cfset requestBody["max_tokens"] = 8192>
|
<cfset requestBody["max_tokens"] = 8192>
|
||||||
|
<cfset requestBody["temperature"] = 0>
|
||||||
<cfset requestBody["system"] = systemPrompt>
|
<cfset requestBody["system"] = systemPrompt>
|
||||||
<cfset requestBody["messages"] = arrayNew(1)>
|
<cfset requestBody["messages"] = arrayNew(1)>
|
||||||
<cfset arrayAppend(requestBody["messages"], userMessage)>
|
<cfset arrayAppend(requestBody["messages"], userMessage)>
|
||||||
|
|
||||||
<!--- Call Claude API using Java HttpURLConnection (bypasses Lucee's HTTP client issues) --->
|
<!--- Call Claude API for this image --->
|
||||||
<cfset requestJson = serializeJSON(requestBody)>
|
<cfhttp url="https://api.anthropic.com/v1/messages" method="POST" timeout="120" result="httpResult">
|
||||||
<cfset httpFileContent = "">
|
<cfhttpparam type="header" name="Content-Type" value="application/json">
|
||||||
<cfset httpStatusCode = 0>
|
<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>
|
||||||
|
|
||||||
<cftry>
|
<cfset httpStatusCode = httpResult.statusCode>
|
||||||
<cfset urlObj = createObject("java", "java.net.URL").init("https://api.anthropic.com/v1/messages")>
|
|
||||||
<cfset conn = urlObj.openConnection()>
|
|
||||||
<cfset conn.setRequestMethod("POST")>
|
|
||||||
<cfset conn.setDoOutput(true)>
|
|
||||||
<cfset conn.setConnectTimeout(30000)>
|
|
||||||
<cfset conn.setReadTimeout(300000)>
|
|
||||||
<cfset conn.setRequestProperty("Content-Type", "application/json")>
|
|
||||||
<cfset conn.setRequestProperty("x-api-key", CLAUDE_API_KEY)>
|
|
||||||
<cfset conn.setRequestProperty("anthropic-version", "2023-06-01")>
|
|
||||||
|
|
||||||
<!--- Write request body --->
|
|
||||||
<cfset outputStream = conn.getOutputStream()>
|
|
||||||
<cfset writer = createObject("java", "java.io.OutputStreamWriter").init(outputStream, "UTF-8")>
|
|
||||||
<cfset writer.write(requestJson)>
|
|
||||||
<cfset writer.flush()>
|
|
||||||
<cfset writer.close()>
|
|
||||||
|
|
||||||
<!--- Get response --->
|
|
||||||
<cfset httpStatusCode = conn.getResponseCode()>
|
|
||||||
|
|
||||||
<!--- Read response body --->
|
|
||||||
<cfif httpStatusCode GTE 200 AND httpStatusCode LT 300>
|
|
||||||
<cfset inputStream = conn.getInputStream()>
|
|
||||||
<cfelse>
|
|
||||||
<cfset inputStream = conn.getErrorStream()>
|
|
||||||
</cfif>
|
|
||||||
|
|
||||||
<cfif NOT isNull(inputStream)>
|
|
||||||
<cfset reader = createObject("java", "java.io.BufferedReader").init(
|
|
||||||
createObject("java", "java.io.InputStreamReader").init(inputStream, "UTF-8")
|
|
||||||
)>
|
|
||||||
<cfset sb = createObject("java", "java.lang.StringBuilder").init()>
|
|
||||||
<cfset line = reader.readLine()>
|
|
||||||
<cfloop condition="NOT isNull(line)">
|
|
||||||
<cfset sb.append(line)>
|
|
||||||
<cfset line = reader.readLine()>
|
|
||||||
</cfloop>
|
|
||||||
<cfset reader.close()>
|
|
||||||
<cfset httpFileContent = sb.toString()>
|
|
||||||
</cfif>
|
|
||||||
|
|
||||||
<cfset conn.disconnect()>
|
|
||||||
|
|
||||||
<cfcatch type="any">
|
|
||||||
<cfset httpStatusCode = 0>
|
|
||||||
<cfset errorStruct = structNew()>
|
|
||||||
<cfset errorStruct["error"] = structNew()>
|
|
||||||
<cfset errorStruct["error"]["message"] = cfcatch.message>
|
|
||||||
<cfset httpFileContent = serializeJSON(errorStruct)>
|
|
||||||
</cfcatch>
|
|
||||||
</cftry>
|
|
||||||
|
|
||||||
<!--- Normalize status code --->
|
|
||||||
<cfif isNumeric(httpStatusCode)>
|
<cfif isNumeric(httpStatusCode)>
|
||||||
<cfset httpStatusCode = int(httpStatusCode)>
|
<cfset httpStatusCode = int(httpStatusCode)>
|
||||||
<cfelseif findNoCase("200", httpStatusCode)>
|
<cfelseif findNoCase("200", httpStatusCode)>
|
||||||
<cfset httpStatusCode = 200>
|
<cfset httpStatusCode = 200>
|
||||||
|
<cfelse>
|
||||||
|
<cfset httpStatusCode = 0>
|
||||||
</cfif>
|
</cfif>
|
||||||
|
|
||||||
<cfif httpStatusCode NEQ 200>
|
<cfif httpStatusCode NEQ 200>
|
||||||
<!--- Try to parse error message from Claude's response --->
|
<cfthrow message="Claude API error on image #imgIndex#: #httpResult.statusCode#" detail="#httpResult.fileContent#">
|
||||||
<cfset errorMsg = "Claude API error: " & httpStatusCode>
|
|
||||||
<cftry>
|
|
||||||
<cfset errorData = deserializeJSON(httpFileContent)>
|
|
||||||
<cfif structKeyExists(errorData, "error") AND structKeyExists(errorData.error, "message")>
|
|
||||||
<cfset errorMsg = errorMsg & " - " & errorData.error.message>
|
|
||||||
</cfif>
|
|
||||||
<cfcatch type="any">
|
|
||||||
<!--- Use raw response if can't parse --->
|
|
||||||
</cfcatch>
|
|
||||||
</cftry>
|
|
||||||
<cfthrow message="#errorMsg#" detail="#httpFileContent#">
|
|
||||||
</cfif>
|
</cfif>
|
||||||
|
|
||||||
<!--- Parse Claude's response --->
|
<!--- Parse response --->
|
||||||
<cfset claudeResponse = deserializeJSON(httpFileContent)>
|
<cfset claudeResponse = deserializeJSON(httpResult.fileContent)>
|
||||||
|
|
||||||
<cfif NOT structKeyExists(claudeResponse, "content") OR NOT arrayLen(claudeResponse.content)>
|
<cfif NOT structKeyExists(claudeResponse, "content") OR NOT arrayLen(claudeResponse.content)>
|
||||||
<cfthrow message="Empty response from Claude">
|
<cfthrow message="Empty response from Claude for image #imgIndex#">
|
||||||
</cfif>
|
</cfif>
|
||||||
|
|
||||||
<!--- Extract the text content --->
|
|
||||||
<cfset responseText = "">
|
<cfset responseText = "">
|
||||||
<cfloop array="#claudeResponse.content#" index="block">
|
<cfloop array="#claudeResponse.content#" index="block">
|
||||||
<cfif block.type EQ "text">
|
<cfif structKeyExists(block, "type") AND block.type EQ "text">
|
||||||
<cfset responseText = block.text>
|
<cfset responseText = block.text>
|
||||||
<cfbreak>
|
<cfbreak>
|
||||||
</cfif>
|
</cfif>
|
||||||
</cfloop>
|
</cfloop>
|
||||||
|
|
||||||
<!--- Parse the JSON from Claude's response --->
|
<!--- Clean up JSON response --->
|
||||||
<!--- Claude might wrap it in markdown code blocks, so strip those --->
|
|
||||||
<cfset responseText = trim(responseText)>
|
<cfset responseText = trim(responseText)>
|
||||||
<cfif left(responseText, 7) EQ "```json">
|
<cfif left(responseText, 7) EQ "```json">
|
||||||
<cfset responseText = mid(responseText, 8, len(responseText) - 7)>
|
<cfset responseText = mid(responseText, 8, len(responseText) - 7)>
|
||||||
|
|
@ -297,33 +170,166 @@
|
||||||
<cfset responseText = left(responseText, len(responseText) - 3)>
|
<cfset responseText = left(responseText, len(responseText) - 3)>
|
||||||
</cfif>
|
</cfif>
|
||||||
<cfset responseText = trim(responseText)>
|
<cfset responseText = trim(responseText)>
|
||||||
|
<cfset responseText = reReplace(responseText, ",(\s*[\]\}])", "\1", "all")>
|
||||||
|
|
||||||
<cfset extractedData = deserializeJSON(responseText)>
|
<cfset imageResult = deserializeJSON(responseText)>
|
||||||
|
<cfset arrayAppend(allResults, imageResult)>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
<!--- Update category item counts --->
|
<!--- MERGE PHASE: Combine all results --->
|
||||||
<cfif structKeyExists(extractedData, "categories") AND structKeyExists(extractedData, "items")>
|
|
||||||
<cfloop from="1" to="#arrayLen(extractedData.categories)#" index="i">
|
<!--- 1. Extract business info (from first result that has it) --->
|
||||||
<cfset catName = extractedData.categories[i].name>
|
<cfset mergedBusiness = structNew()>
|
||||||
<cfset itemCount = 0>
|
<cfloop array="#allResults#" index="result">
|
||||||
<cfloop array="#extractedData.items#" index="item">
|
<cfif structKeyExists(result, "business") AND isStruct(result.business)>
|
||||||
<cfif structKeyExists(item, "category") AND item.category EQ catName>
|
<cfif structKeyExists(result.business, "name") AND len(result.business.name)>
|
||||||
<cfset itemCount = itemCount + 1>
|
<cfset mergedBusiness = result.business>
|
||||||
|
<cfbreak>
|
||||||
|
</cfif>
|
||||||
</cfif>
|
</cfif>
|
||||||
</cfloop>
|
</cfloop>
|
||||||
<cfset extractedData.categories[i]["itemCount"] = itemCount>
|
|
||||||
|
<!--- 2. Merge categories (dedupe by name) --->
|
||||||
|
<cfset categoryMap = structNew()>
|
||||||
|
<cfloop array="#allResults#" index="result">
|
||||||
|
<cfif structKeyExists(result, "categories") AND isArray(result.categories)>
|
||||||
|
<cfloop array="#result.categories#" index="cat">
|
||||||
|
<cfset catName = "">
|
||||||
|
<cfif isSimpleValue(cat) AND len(trim(cat))>
|
||||||
|
<cfset catName = trim(cat)>
|
||||||
|
<cfelseif isStruct(cat)>
|
||||||
|
<!--- Try common key names --->
|
||||||
|
<cfif structKeyExists(cat, "name") AND len(trim(cat.name))>
|
||||||
|
<cfset catName = trim(cat.name)>
|
||||||
|
<cfelseif structKeyExists(cat, "category") AND len(trim(cat.category))>
|
||||||
|
<cfset catName = trim(cat.category)>
|
||||||
|
<cfelseif structKeyExists(cat, "title") AND len(trim(cat.title))>
|
||||||
|
<cfset catName = trim(cat.title)>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
<cfif len(catName)>
|
||||||
|
<cfset categoryMap[lCase(catName)] = catName>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
<cfset mergedCategories = arrayNew(1)>
|
||||||
|
<cfloop collection="#categoryMap#" item="catKey">
|
||||||
|
<cfset catObj = structNew()>
|
||||||
|
<cfset catObj["name"] = categoryMap[catKey]>
|
||||||
|
<cfset catObj["itemCount"] = 0>
|
||||||
|
<cfset arrayAppend(mergedCategories, catObj)>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<!--- 3. Merge modifiers (dedupe by name) --->
|
||||||
|
<cfset modifierMap = structNew()>
|
||||||
|
<cfloop array="#allResults#" index="result">
|
||||||
|
<cfif structKeyExists(result, "modifiers") AND isArray(result.modifiers)>
|
||||||
|
<cfloop array="#result.modifiers#" index="mod">
|
||||||
|
<cfif isStruct(mod) AND structKeyExists(mod, "name") AND len(trim(mod.name))>
|
||||||
|
<cfset modKey = lCase(mod.name)>
|
||||||
|
<cfif NOT structKeyExists(modifierMap, modKey)>
|
||||||
|
<!--- Normalize the modifier structure --->
|
||||||
|
<cfset normalizedMod = structNew()>
|
||||||
|
<cfset normalizedMod["name"] = trim(mod.name)>
|
||||||
|
<cfset normalizedMod["required"] = structKeyExists(mod, "required") AND mod.required EQ true>
|
||||||
|
<cfset normalizedMod["options"] = arrayNew(1)>
|
||||||
|
|
||||||
|
<!--- Normalize options array --->
|
||||||
|
<cfif structKeyExists(mod, "options") AND isArray(mod.options)>
|
||||||
|
<cfloop array="#mod.options#" index="opt">
|
||||||
|
<cfset normalizedOpt = structNew()>
|
||||||
|
<cfif isSimpleValue(opt) AND len(trim(opt))>
|
||||||
|
<!--- Option is just a string --->
|
||||||
|
<cfset normalizedOpt["name"] = trim(opt)>
|
||||||
|
<cfset normalizedOpt["price"] = 0>
|
||||||
|
<cfelseif isStruct(opt)>
|
||||||
|
<!--- Option is an object - try different key names --->
|
||||||
|
<cfset optName = "">
|
||||||
|
<cfif structKeyExists(opt, "name") AND len(trim(opt.name))>
|
||||||
|
<cfset optName = trim(opt.name)>
|
||||||
|
<cfelseif structKeyExists(opt, "option") AND len(trim(opt.option))>
|
||||||
|
<cfset optName = trim(opt.option)>
|
||||||
|
<cfelseif structKeyExists(opt, "label") AND len(trim(opt.label))>
|
||||||
|
<cfset optName = trim(opt.label)>
|
||||||
|
</cfif>
|
||||||
|
<cfif len(optName)>
|
||||||
|
<cfset normalizedOpt["name"] = optName>
|
||||||
|
<cfset normalizedOpt["price"] = 0>
|
||||||
|
<cfif structKeyExists(opt, "price")>
|
||||||
|
<cfif isNumeric(opt.price)>
|
||||||
|
<cfset normalizedOpt["price"] = opt.price>
|
||||||
|
<cfelseif isSimpleValue(opt.price)>
|
||||||
|
<!--- Try to parse price string like "$1.50" or "+$1.50" --->
|
||||||
|
<cfset priceStr = reReplace(opt.price, "[^0-9.]", "", "all")>
|
||||||
|
<cfif isNumeric(priceStr)>
|
||||||
|
<cfset normalizedOpt["price"] = val(priceStr)>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
<cfif structKeyExists(normalizedOpt, "name") AND len(normalizedOpt.name)>
|
||||||
|
<cfset arrayAppend(normalizedMod["options"], normalizedOpt)>
|
||||||
|
</cfif>
|
||||||
</cfloop>
|
</cfloop>
|
||||||
</cfif>
|
</cfif>
|
||||||
|
|
||||||
<!--- Add unique IDs to items --->
|
<!--- Only add modifier if it has at least one valid option --->
|
||||||
<cfif structKeyExists(extractedData, "items")>
|
<cfif arrayLen(normalizedMod["options"]) GT 0>
|
||||||
<cfloop from="1" to="#arrayLen(extractedData.items)#" index="i">
|
<cfset modifierMap[modKey] = normalizedMod>
|
||||||
<cfset extractedData.items[i]["id"] = "item_" & i>
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
</cfloop>
|
</cfloop>
|
||||||
</cfif>
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
<cfset mergedModifiers = arrayNew(1)>
|
||||||
|
<cfloop collection="#modifierMap#" item="modKey">
|
||||||
|
<cfset arrayAppend(mergedModifiers, modifierMap[modKey])>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<!--- 4. Merge items (collect all, assign IDs) --->
|
||||||
|
<cfset mergedItems = arrayNew(1)>
|
||||||
|
<cfset itemIndex = 0>
|
||||||
|
<cfloop array="#allResults#" index="result">
|
||||||
|
<cfif structKeyExists(result, "items") AND isArray(result.items)>
|
||||||
|
<cfloop array="#result.items#" index="item">
|
||||||
|
<cfset itemIndex = itemIndex + 1>
|
||||||
|
<cfset item["id"] = "item_" & itemIndex>
|
||||||
|
<cfset arrayAppend(mergedItems, item)>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<!--- Build final response --->
|
||||||
|
<cfset finalData = structNew()>
|
||||||
|
<cfset finalData["business"] = mergedBusiness>
|
||||||
|
<cfset finalData["categories"] = mergedCategories>
|
||||||
|
<cfset finalData["modifiers"] = mergedModifiers>
|
||||||
|
<cfset finalData["items"] = mergedItems>
|
||||||
|
|
||||||
<cfset response["OK"] = true>
|
<cfset response["OK"] = true>
|
||||||
<cfset response["DATA"] = extractedData>
|
<cfset response["DATA"] = finalData>
|
||||||
<cfset response["imagesProcessed"] = arrayLen(imageDataArray)>
|
<cfset response["imagesProcessed"] = arrayLen(imageDataArray)>
|
||||||
|
<cfset response["DEBUG_RAW_RESULTS"] = allResults>
|
||||||
|
<!--- Debug: show first category structure --->
|
||||||
|
<cfif arrayLen(allResults) GT 0 AND structKeyExists(allResults[1], "categories") AND isArray(allResults[1].categories) AND arrayLen(allResults[1].categories) GT 0>
|
||||||
|
<cfset response["DEBUG_FIRST_CAT"] = allResults[1].categories[1]>
|
||||||
|
<cfset response["DEBUG_FIRST_CAT_KEYS"] = isStruct(allResults[1].categories[1]) ? structKeyList(allResults[1].categories[1]) : "NOT_A_STRUCT">
|
||||||
|
</cfif>
|
||||||
|
<!--- Debug: show first item structure --->
|
||||||
|
<cfif arrayLen(allResults) GT 0 AND structKeyExists(allResults[1], "items") AND isArray(allResults[1].items) AND arrayLen(allResults[1].items) GT 0>
|
||||||
|
<cfset response["DEBUG_FIRST_ITEM"] = allResults[1].items[1]>
|
||||||
|
</cfif>
|
||||||
|
<!--- Debug: show first modifier from raw results --->
|
||||||
|
<cfif arrayLen(allResults) GT 0 AND structKeyExists(allResults[1], "modifiers") AND isArray(allResults[1].modifiers) AND arrayLen(allResults[1].modifiers) GT 0>
|
||||||
|
<cfset response["DEBUG_FIRST_RAW_MODIFIER"] = allResults[1].modifiers[1]>
|
||||||
|
</cfif>
|
||||||
|
<!--- Debug: show first merged modifier --->
|
||||||
|
<cfif arrayLen(mergedModifiers) GT 0>
|
||||||
|
<cfset response["DEBUG_FIRST_MERGED_MODIFIER"] = mergedModifiers[1]>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
<cfcatch type="any">
|
<cfcatch type="any">
|
||||||
<cfset response["MESSAGE"] = cfcatch.message>
|
<cfset response["MESSAGE"] = cfcatch.message>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue