diff --git a/cookbook/helper/recipe_url_import.py b/cookbook/helper/recipe_url_import.py index 767945937..32560fafc 100644 --- a/cookbook/helper/recipe_url_import.py +++ b/cookbook/helper/recipe_url_import.py @@ -205,6 +205,7 @@ def get_from_scraper(scrape, request): except Exception: pass + recipe_json['properties'] = [] try: recipe_json['properties'] = get_recipe_properties(request.space, scrape.schema.nutrients()) print(recipe_json['properties']) @@ -227,6 +228,13 @@ def get_recipe_properties(space, property_data): "property-proteins": "proteinContent", "property-fats": "fatContent", } + + serving_size = 1 + try: + serving_size = parse_servings(property_data['servingSize']) + except KeyError: + pass + recipe_properties = [] for pt in PropertyType.objects.filter(space=space, open_data_slug__in=list(properties.keys())).all(): for p in list(properties.keys()): @@ -237,7 +245,7 @@ def get_recipe_properties(space, property_data): 'id': pt.id, 'name': pt.name, }, - 'property_amount': parse_servings(property_data[properties[p]]) / parse_servings(property_data['servingSize']), + 'property_amount': parse_servings(property_data[properties[p]]) / serving_size, }) return recipe_properties diff --git a/cookbook/views/api.py b/cookbook/views/api.py index 67641364b..2d34015ba 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -1,3 +1,4 @@ +import base64 import datetime import io import json @@ -13,6 +14,7 @@ from urllib.parse import unquote from zipfile import ZipFile import PIL.Image +import litellm import redis import requests from PIL import UnidentifiedImageError @@ -34,13 +36,12 @@ from django.urls import reverse from django.utils import timezone from django.utils.dateparse import parse_datetime from django.utils.datetime_safe import date -from django.utils.timezone import make_aware from django.utils.translation import gettext as _ from django_scopes import scopes_disabled from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter, extend_schema, extend_schema_view, OpenApiExample, inline_serializer -from google import generativeai from icalendar import Calendar, Event +from litellm import completion from oauth2_provider.models import AccessToken from recipe_scrapers import scrape_html from recipe_scrapers._exceptions import NoSchemaFoundInWildMode @@ -1829,22 +1830,37 @@ class ImageToRecipeView(APIView): print('method called') serializer = ImportImageSerializer(data=request.data, partial=True) if serializer.is_valid(): - generativeai.configure(api_key=GOOGLE_AI_API_KEY) - - model = generativeai.GenerativeModel('gemini-1.5-flash-latest') + # generativeai.configure(api_key=GOOGLE_AI_API_KEY) + # + # model = generativeai.GenerativeModel('gemini-1.5-flash-latest') img = PIL.Image.open(serializer.validated_data['image']) - # response = model.generate_content([ - # "The Image given is a photograph or screenshot taken of a cooking recipe. The data contained in the image should be converted to a structured json format adhering to the specification given in the schema.org/recipes schema. It is not allowed to make any data up, the response must only contain data given in the original image. The response must be a plain json object without any formatting characters or other unnecessary information. If unsure please return an empty json object {}. Do not wrap the response in a markdown codeblock (like ```json ```)", - # img], stream=True) - # response.resolve() + buffer = io.BytesIO() + img.save(buffer, format="PNG") # or PNG if needed + image_bytes = buffer.getvalue() - # response_text = response.text - # response_text.replace('```json', '') - # response_text.replace('```', '') + # TODO cant use ingredient splitting because scraper gets upset "Please separate the ingredients into amount, unit, food and if required a note. " + # TODO maybe not use scraper? + messages = [ + { + "role": "user", + "content": [ + { + "type": "text", + "text": "Please look at the file and return the contained recipe as a structured JSON in the same language as given in the file. For the JSON use the format given in the schema.org/recipe schema. Do not make anything up and leave everything blank you do not know. If shown in the file please also return the nutrition in the format specified in the schema.org/recipe schema." - response_text = '{"@context": "https://schema.org", "@type": "Recipe", "name": "Pennettine mit Nüssen und Zitrone", "prepTime": "PT25M", "recipeYield": "4", "ingredients": [{"@type": "Quantity", "name": "BARILLA Pennettine", "amount": 500, "unitCode": "G"}, {"@type": "Quantity", "name": "Walnußkerne", "amount": 100, "unitCode": "G"}, {"@type": "Quantity", "name": "Sahne", "amount": "1/2-1", "unitCode": "CUP"}, {"@type": "Quantity", "name": "Butter", "amount": 20, "unitCode": "G"}, {"@type": "Quantity", "name": "Zucker", "amount": 10, "unitCode": "G"}, {"@type": "Quantity", "name": "gemahlener Zimt", "amount": 1, "unitCode": "UNIT"}, {"@type": "Quantity", "name": "Zitrone, unbehandelt", "amount": "1/2-1", "unitCode": "UNIT"}, {"@type": "Quantity", "name": "Salz", "amount": null, "unitCode": null}, {"@type": "Quantity", "name": "frisch gemahlener Pfeffer", "amount": null, "unitCode": null}], "recipeInstructions": [{"@type": "HowToStep", "text": "Walnußkerne hacken, in eine große Schüssel geben, Zimt und Zucker mit einer Prise Salz und Pfeffer untermischen."}, {"@type": "HowToStep", "text": "Die Zitrone waschen, Schale einer 1/2 Zitrone abreiben, dann zusammen mit den anderen Zutaten in die Schüssel geben und zuletzt in Stückchen geschnittene Butter untermischen."}, {"@type": "HowToStep", "text": "Die Mischung schmelzen, indem Sie die Schüssel auf den Topf mit dem kochenden Pasta-Wasser stellen und hin und wieder umrühren."}, {"@type": "HowToStep", "text": "Sahne dazugeben und die Zutaten mit einem Schneebesen verrühren."}, {"@type": "HowToStep", "text": "BARILLA Pennettine in reichlich Salzwasser al dente kochen, abgießen und mit der Sauce anrichten."}]}' + }, + { + "type": "image_url", + "image_url": f'data:image/png;base64,{base64.b64encode(image_bytes).decode("utf-8")}' + }, - print(response_text) + ] + }, + ] + + response = completion(api_key=GOOGLE_AI_API_KEY, model='gemini/gemini-2.0-flash', response_format={"type": "json_object"}, messages=messages, ) + + response_text = response.choices[0].message.content try: data_json = json.loads(response_text) diff --git a/requirements.txt b/requirements.txt index e9fb1ed24..b0ac6defb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -48,7 +48,7 @@ aiohttp==3.10.11 inflection==0.5.1 redis==5.2.1 django-vite==3.0.3 -google-generativeai==0.5.3 +litellm==1.60.8 # Development pytest==8.0.0 diff --git a/vue3/src/components/model_editors/RecipeBookEditor.vue b/vue3/src/components/model_editors/RecipeBookEditor.vue new file mode 100644 index 000000000..68606b15a --- /dev/null +++ b/vue3/src/components/model_editors/RecipeBookEditor.vue @@ -0,0 +1,65 @@ + + + + + \ No newline at end of file diff --git a/vue3/src/pages/BooksPage.vue b/vue3/src/pages/BooksPage.vue new file mode 100644 index 000000000..7b591642f --- /dev/null +++ b/vue3/src/pages/BooksPage.vue @@ -0,0 +1,42 @@ + + + + +