feat(frontend): 🚧 groundwork for user interactive ingredient parsing

This commit is contained in:
hay-kot 2021-08-28 14:14:21 -08:00
parent f8c2f760bd
commit 0f6f81eb27
5 changed files with 129 additions and 9 deletions

View File

@ -10,6 +10,7 @@ const routes = {
recipesCreateUrl: `${prefix}/recipes/create-url`,
recipesCreateFromZip: `${prefix}/recipes/create-from-zip`,
recipesCategory: `${prefix}/recipes/category`,
recipesParseIngredients: `${prefix}/parse/ingredient`,
recipesRecipeSlug: (recipe_slug: string) => `${prefix}/recipes/${recipe_slug}`,
recipesRecipeSlugZip: (recipe_slug: string) => `${prefix}/recipes/${recipe_slug}/zip`,
@ -81,4 +82,8 @@ export class RecipeAPI extends BaseCRUDAPI<Recipe, CreateRecipe> {
async deleteComment(slug: string, id: number) {
return await this.requests.delete(routes.recipesSlugCommentsId(slug, id));
}
async parseIngredients(ingredients: Array<string>) {
return await this.requests.post(routes.recipesParseIngredients, { ingredients });
}
}

View File

@ -59,6 +59,9 @@
<RecipeIngredientFoodDialog class="mx-2" block small />
</template>
</v-autocomplete>
<template v-if="!checkForFood(value.food)">
'{{ value.food && value.food !== "" ? value.food.name : null }}' does not exists. Would you like to create it?
</template>
</v-col>
<v-col sm="12" md="" cols="12">
<v-text-field v-model="value.note" hide-details dense solo class="mx-1" placeholder="Notes">
@ -92,6 +95,7 @@ import RecipeIngredientFoodDialog from "./RecipeIngredientFoodDialog.vue";
import { useFoods } from "~/composables/use-recipe-foods";
import { useUnits } from "~/composables/use-recipe-units";
import { validators } from "~/composables/use-validators";
import { RecipeIngredientFood } from "~/types/api-types/recipe";
export default defineComponent({
components: { RecipeIngredientUnitDialog, RecipeIngredientFoodDialog },
@ -115,6 +119,20 @@ export default defineComponent({
showTitle: false,
});
function checkForUnit(unit: RecipeIngredientFood) {
if (units.value && unit?.name) {
return units.value.some((u) => u.name === unit.name);
}
return false;
}
function checkForFood(food: RecipeIngredientFood) {
if (foods.value && food?.name) {
return foods.value.some((f) => f.name === food.name);
}
return false;
}
function toggleTitle() {
if (value.title) {
state.showTitle = false;
@ -125,7 +143,17 @@ export default defineComponent({
}
}
return { workingUnitData, unitActions, validators, foods, units, ...toRefs(state), toggleTitle };
return {
workingUnitData,
unitActions,
validators,
foods,
units,
...toRefs(state),
toggleTitle,
checkForUnit,
checkForFood,
};
},
});
</script>

View File

@ -0,0 +1,84 @@
<template>
<div>
<v-menu offset-y offset-overflow left top nudge-top="6" :close-on-content-click="false">
<template #activator="{ on, attrs }">
<v-btn color="accent" dark v-bind="attrs" v-on="on">
<v-icon left>
{{ $globals.icons.foods }}
</v-icon>
Parse
</v-btn>
</template>
<v-card width="400">
<v-card-title class="mb-1 pb-0"> Warning Experimental </v-card-title>
<v-card-text>
Mealie can use natural language processing to attempt to parse and create units, and foods for your Recipe
ingredients. This is experimental and may not work as expected. If you choose to not use the parsed results
you can click the close button at the top of the page and your changes will not be saved.
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<BaseButton small color="accent" @click="parseIngredients">
<template #icon>
{{ $globals.icons.check }}
</template>
{{ $t("general.confirm") }}
</BaseButton>
</v-card-actions>
</v-card>
</v-menu>
<BaseDialog ref="domParsedDataDialog" width="100%">
<v-card-text>
<div v-for="(ing, index) in parsedData.ingredient" :key="index">
<div class="ml-10 text-body-1" :class="index > 0 ? 'mt-4' : null">{{ ingredients[index].note }}</div>
<RecipeIngredientEditor :value="ing" />
</div>
</v-card-text>
</BaseDialog>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from "@nuxtjs/composition-api";
import RecipeIngredientEditor from "./RecipeIngredientEditor.vue";
import { useApiSingleton } from "~/composables/use-api";
import { RecipeIngredient } from "~/types/api-types/recipe";
export default defineComponent({
components: {
RecipeIngredientEditor,
},
props: {
ingredients: {
type: Array,
required: true,
},
},
setup(props) {
const ingredients = props.ingredients;
const api = useApiSingleton();
const parsedData = ref<any>([]);
const domParsedDataDialog = ref(null);
async function parseIngredients() {
// @ts-ignore -> No idea what it's talking about
const ingredientNotes = ingredients.map((ing: RecipeIngredient) => ing.note);
const { data } = await api.recipes.parseIngredients(ingredientNotes);
if (data) {
// @ts-ignore
domParsedDataDialog.value.open();
console.log(data);
parsedData.value = data;
}
console.log("ingredientNotes", ingredientNotes);
}
return { api, parseIngredients, parsedData, domParsedDataDialog };
},
});
</script>

View File

@ -110,8 +110,9 @@
/>
</draggable>
<div class="d-flex justify-end mt-2">
<RecipeDialogBulkAdd class="mr-2" @bulk-data="addIngredient" />
<BaseButton @click="addIngredient"> {{ $t("general.new") }} </BaseButton>
<RecipeDialogBulkAdd class="mr-1" @bulk-data="addIngredient" />
<BaseButton class="mr-1" @click="addIngredient"> {{ $t("general.new") }} </BaseButton>
<RecipeIngredientParserMenu :ingredients="recipe.recipeIngredient" />
</div>
</div>
@ -252,6 +253,7 @@ import RecipeNotes from "~/components/Domain/Recipe/RecipeNotes.vue";
import RecipeImageUploadBtn from "~/components/Domain/Recipe/RecipeImageUploadBtn.vue";
import RecipeSettingsMenu from "~/components/Domain/Recipe/RecipeSettingsMenu.vue";
import RecipeIngredientEditor from "~/components/Domain/Recipe/RecipeIngredientEditor.vue";
import RecipeIngredientParserMenu from "~/components/Domain/Recipe/RecipeIngredientParserMenu.vue";
import { Recipe } from "~/types/api-types/recipe";
import { useStaticRoutes } from "~/composables/api";
@ -271,6 +273,7 @@ export default defineComponent({
RecipeSettingsMenu,
RecipeIngredientEditor,
RecipeTimeCard,
RecipeIngredientParserMenu,
VueMarkdown,
draggable,
},

View File

@ -80,12 +80,12 @@ export interface Recipe {
comments?: CommentOut[];
}
export interface RecipeIngredient {
title?: string;
note?: string;
unit?: RecipeIngredientUnit;
food?: RecipeIngredientFood;
disableAmount?: boolean;
quantity?: number;
title: string;
note: string;
unit: RecipeIngredientUnit;
food: RecipeIngredientFood;
disableAmount: boolean;
quantity: number;
}
export interface RecipeIngredientUnit {
name?: string;