| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  | <template> | 
					
						
							| 
									
										
										
										
											2021-10-16 16:06:13 -08:00
										 |  |  |   <v-container class="pa-0"> | 
					
						
							| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  |     <v-container> | 
					
						
							|  |  |  |       <BaseCardSectionTitle title="Ingredients Natural Language Processor"> | 
					
						
							| 
									
										
										
										
											2021-10-16 16:06:13 -08:00
										 |  |  |         Mealie uses Conditional Random Fields (CRFs) for parsing and processing ingredients. The model used for | 
					
						
							|  |  |  |         ingredients is based off a data set of over 100,000 ingredients from a dataset compiled by the New York Times. | 
					
						
							|  |  |  |         Note that as the model is trained in English only, you may have varied results when using the model in other | 
					
						
							|  |  |  |         languages. This page is a playground for testing the model. | 
					
						
							| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         <p class="pt-3"> | 
					
						
							|  |  |  |           It's not perfect, but it yields great results in general and is a good starting point for manually parsing | 
					
						
							| 
									
										
										
										
											2021-10-16 16:06:13 -08:00
										 |  |  |           ingredients into individual fields. Alternatively, you can also use the "Brute" processor that uses a pattern | 
					
						
							|  |  |  |           matching technique to identify ingredients. | 
					
						
							| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  |         </p> | 
					
						
							|  |  |  |       </BaseCardSectionTitle> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-16 16:06:13 -08:00
										 |  |  |       <div class="d-flex align-center justify-center justify-md-start flex-wrap"> | 
					
						
							|  |  |  |         <v-btn-toggle v-model="parser" dense mandatory @change="processIngredient"> | 
					
						
							|  |  |  |           <v-btn value="nlp"> NLP </v-btn> | 
					
						
							|  |  |  |           <v-btn value="brute"> Brute </v-btn> | 
					
						
							|  |  |  |         </v-btn-toggle> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         <v-checkbox v-model="showConfidence" class="ml-5" label="Show individual confidence"></v-checkbox> | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  |       <v-card flat> | 
					
						
							|  |  |  |         <v-card-text> | 
					
						
							|  |  |  |           <v-text-field v-model="ingredient" label="Ingredient Text"> </v-text-field> | 
					
						
							|  |  |  |         </v-card-text> | 
					
						
							|  |  |  |         <v-card-actions> | 
					
						
							|  |  |  |           <BaseButton class="ml-auto" @click="processIngredient"> | 
					
						
							|  |  |  |             <template #icon> {{ $globals.icons.check }}</template> | 
					
						
							|  |  |  |             {{ $t("general.submit") }} | 
					
						
							|  |  |  |           </BaseButton> | 
					
						
							|  |  |  |         </v-card-actions> | 
					
						
							|  |  |  |       </v-card> | 
					
						
							|  |  |  |     </v-container> | 
					
						
							|  |  |  |     <v-container v-if="results"> | 
					
						
							| 
									
										
										
										
											2021-10-16 16:06:13 -08:00
										 |  |  |       <div v-if="parser !== 'brute' && getConfidence('average')" class="d-flex"> | 
					
						
							|  |  |  |         <v-chip dark :color="getColor('average')" class="mx-auto mb-2"> | 
					
						
							|  |  |  |           {{ getConfidence("average") }} Confident | 
					
						
							|  |  |  |         </v-chip> | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |       <div class="d-flex justify-center flex-wrap" style="gap: 1.5rem"> | 
					
						
							| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  |         <template v-for="(prop, index) in properties"> | 
					
						
							| 
									
										
										
										
											2021-10-16 16:06:13 -08:00
										 |  |  |           <div v-if="prop.value" :key="index" class="flex-grow-1"> | 
					
						
							|  |  |  |             <v-card min-width="200px"> | 
					
						
							| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  |               <v-card-title> {{ prop.value }} </v-card-title> | 
					
						
							|  |  |  |               <v-card-text> | 
					
						
							|  |  |  |                 {{ prop.subtitle }} | 
					
						
							|  |  |  |               </v-card-text> | 
					
						
							|  |  |  |             </v-card> | 
					
						
							| 
									
										
										
										
											2021-10-16 16:06:13 -08:00
										 |  |  |             <v-chip v-if="prop.confidence && showConfidence" dark :color="prop.color" class="mt-2"> | 
					
						
							|  |  |  |               {{ prop.confidence }} Confident | 
					
						
							|  |  |  |             </v-chip> | 
					
						
							|  |  |  |           </div> | 
					
						
							| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  |         </template> | 
					
						
							| 
									
										
										
										
											2021-10-16 16:06:13 -08:00
										 |  |  |       </div> | 
					
						
							| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  |     </v-container> | 
					
						
							|  |  |  |     <v-container class="narrow-container"> | 
					
						
							|  |  |  |       <v-card-title> Try an example </v-card-title> | 
					
						
							|  |  |  |       <v-card v-for="(text, idx) in tryText" :key="idx" class="my-2" hover @click="processTryText(text)"> | 
					
						
							|  |  |  |         <v-card-text> {{ text }} </v-card-text> | 
					
						
							|  |  |  |       </v-card> | 
					
						
							|  |  |  |     </v-container> | 
					
						
							|  |  |  |   </v-container> | 
					
						
							|  |  |  | </template> | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  | <script lang="ts"> | 
					
						
							| 
									
										
										
										
											2021-10-16 16:06:13 -08:00
										 |  |  | import { defineComponent, reactive, ref, toRefs } from "@nuxtjs/composition-api"; | 
					
						
							|  |  |  | import { Confidence, Parser } from "~/api/class-interfaces/recipes"; | 
					
						
							| 
									
										
										
										
											2021-11-06 11:28:47 -08:00
										 |  |  | import { useUserApi } from "~/composables/api"; | 
					
						
							| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | export default defineComponent({ | 
					
						
							|  |  |  |   layout: "admin", | 
					
						
							|  |  |  |   setup() { | 
					
						
							| 
									
										
										
										
											2021-11-06 11:28:47 -08:00
										 |  |  |     const api = useUserApi(); | 
					
						
							| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     const state = reactive({ | 
					
						
							|  |  |  |       loading: false, | 
					
						
							|  |  |  |       ingredient: "", | 
					
						
							|  |  |  |       results: false, | 
					
						
							| 
									
										
										
										
											2021-10-16 16:06:13 -08:00
										 |  |  |       parser: "nlp" as Parser, | 
					
						
							| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-16 16:06:13 -08:00
										 |  |  |     const confidence = ref<Confidence>({}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function getColor(attribute: string) { | 
					
						
							|  |  |  |       const percentage = getConfidence(attribute); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // @ts-ignore
 | 
					
						
							|  |  |  |       const p_as_num = parseFloat(percentage?.replace("%", "")); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Set color based off range
 | 
					
						
							|  |  |  |       if (p_as_num > 75) { | 
					
						
							|  |  |  |         return "success"; | 
					
						
							|  |  |  |       } else if (p_as_num > 60) { | 
					
						
							|  |  |  |         return "warning"; | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         return "error"; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function getConfidence(attribute: string) { | 
					
						
							|  |  |  |       attribute = attribute.toLowerCase(); | 
					
						
							|  |  |  |       if (!confidence.value) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // @ts-ignore
 | 
					
						
							|  |  |  |       const property: number = confidence.value[attribute]; | 
					
						
							|  |  |  |       if (property) { | 
					
						
							|  |  |  |         return `${(property * 100).toFixed(0)}%`; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  |     const tryText = [ | 
					
						
							|  |  |  |       "2 tbsp minced cilantro, leaves and stems", | 
					
						
							|  |  |  |       "1 large yellow onion, coarsely chopped", | 
					
						
							|  |  |  |       "1 1/2 tsp garam masala", | 
					
						
							|  |  |  |       "1 inch piece fresh ginger, (peeled and minced)", | 
					
						
							|  |  |  |       "2 cups mango chunks, (2 large mangoes) (fresh or frozen)", | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function processTryText(str: string) { | 
					
						
							|  |  |  |       state.ingredient = str; | 
					
						
							|  |  |  |       processIngredient(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     async function processIngredient() { | 
					
						
							| 
									
										
										
										
											2021-10-16 16:06:13 -08:00
										 |  |  |       if (state.ingredient === "") { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  |       state.loading = true; | 
					
						
							| 
									
										
										
										
											2021-10-16 16:06:13 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |       const { data } = await api.recipes.parseIngredient(state.parser, state.ingredient); | 
					
						
							| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |       if (data) { | 
					
						
							|  |  |  |         state.results = true; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-16 16:06:13 -08:00
										 |  |  |         confidence.value = data.confidence; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  |         // TODO: Remove ts-ignore
 | 
					
						
							|  |  |  |         // ts-ignore because data will likely change significantly once I figure out how to return results
 | 
					
						
							|  |  |  |         // for the parser. For now we'll leave it like this
 | 
					
						
							| 
									
										
										
										
											2021-10-16 16:06:13 -08:00
										 |  |  |         properties.comment.value = data.ingredient.note || ""; | 
					
						
							|  |  |  |         properties.quantity.value = data.ingredient.quantity || ""; | 
					
						
							| 
									
										
										
										
											2021-11-06 15:03:12 -08:00
										 |  |  |         properties.unit.value = data.ingredient?.unit?.name || ""; | 
					
						
							|  |  |  |         properties.food.value = data.ingredient?.food?.name || ""; | 
					
						
							| 
									
										
										
										
											2021-10-16 16:06:13 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         for (const property in properties) { | 
					
						
							|  |  |  |           const color = getColor(property); | 
					
						
							|  |  |  |           const confidence = getConfidence(property); | 
					
						
							|  |  |  |           if (color) { | 
					
						
							|  |  |  |             // @ts-ignore
 | 
					
						
							|  |  |  |             properties[property].color = color; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           if (confidence) { | 
					
						
							|  |  |  |             // @ts-ignore
 | 
					
						
							|  |  |  |             properties[property].confidence = confidence; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  |       } | 
					
						
							|  |  |  |       state.loading = false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const properties = reactive({ | 
					
						
							|  |  |  |       quantity: { | 
					
						
							|  |  |  |         subtitle: "Quantity", | 
					
						
							| 
									
										
										
										
											2021-10-16 16:06:13 -08:00
										 |  |  |         value: "" as any, | 
					
						
							|  |  |  |         color: null, | 
					
						
							|  |  |  |         confidence: null, | 
					
						
							| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  |       }, | 
					
						
							|  |  |  |       unit: { | 
					
						
							|  |  |  |         subtitle: "Unit", | 
					
						
							| 
									
										
										
										
											2021-10-16 16:06:13 -08:00
										 |  |  |         value: "", | 
					
						
							|  |  |  |         color: null, | 
					
						
							|  |  |  |         confidence: null, | 
					
						
							| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  |       }, | 
					
						
							|  |  |  |       food: { | 
					
						
							|  |  |  |         subtitle: "Food", | 
					
						
							| 
									
										
										
										
											2021-10-16 16:06:13 -08:00
										 |  |  |         value: "", | 
					
						
							|  |  |  |         color: null, | 
					
						
							|  |  |  |         confidence: null, | 
					
						
							| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  |       }, | 
					
						
							| 
									
										
										
										
											2021-10-16 16:06:13 -08:00
										 |  |  |       comment: { | 
					
						
							|  |  |  |         subtitle: "Comment", | 
					
						
							|  |  |  |         value: "", | 
					
						
							|  |  |  |         color: null, | 
					
						
							|  |  |  |         confidence: null, | 
					
						
							| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  |       }, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-16 16:06:13 -08:00
										 |  |  |     const showConfidence = ref(false); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  |     return { | 
					
						
							| 
									
										
										
										
											2021-10-16 16:06:13 -08:00
										 |  |  |       showConfidence, | 
					
						
							|  |  |  |       getColor, | 
					
						
							|  |  |  |       confidence, | 
					
						
							|  |  |  |       getConfidence, | 
					
						
							| 
									
										
										
										
											2021-10-09 13:08:23 -08:00
										 |  |  |       ...toRefs(state), | 
					
						
							|  |  |  |       tryText, | 
					
						
							|  |  |  |       properties, | 
					
						
							|  |  |  |       processTryText, | 
					
						
							|  |  |  |       processIngredient, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  |   head() { | 
					
						
							|  |  |  |     return { | 
					
						
							|  |  |  |       title: "Parser", | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | </script> | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  | <style scoped> | 
					
						
							|  |  |  | </style> |