Update app.py
Browse files
app.py
CHANGED
@@ -4,6 +4,9 @@ import torch
|
|
4 |
import numpy as np
|
5 |
import random
|
6 |
import json
|
|
|
|
|
|
|
7 |
|
8 |
# Lade RecipeBERT Modell (für semantische Zutat-Kombination)
|
9 |
bert_model_name = "alexdseo/RecipeBERT"
|
@@ -89,15 +92,6 @@ def get_combined_scores(query_vector, embedding_list, all_good_embeddings, avg_w
|
|
89 |
def find_best_ingredients(required_ingredients, available_ingredients, max_ingredients=6, avg_weight=0.6):
|
90 |
"""
|
91 |
Findet die besten Zutaten basierend auf RecipeBERT Embeddings.
|
92 |
-
|
93 |
-
Args:
|
94 |
-
required_ingredients (list): Benötigte Zutaten, die verwendet werden müssen
|
95 |
-
available_ingredients (list): Verfügbare Zutaten zur Auswahl
|
96 |
-
max_ingredients (int): Maximale Anzahl von Zutaten für das Rezept
|
97 |
-
avg_weight (float): Gewicht für den Durchschnittsvektor
|
98 |
-
|
99 |
-
Returns:
|
100 |
-
list: Die optimale Kombination von Zutaten
|
101 |
"""
|
102 |
# Stelle sicher, dass keine Duplikate in den Listen sind
|
103 |
required_ingredients = list(set(required_ingredients))
|
@@ -282,32 +276,16 @@ def generate_recipe_with_t5(ingredients_list, max_retries=5):
|
|
282 |
"directions": ["Fehler beim Generieren der Rezeptanweisungen"]
|
283 |
}
|
284 |
|
285 |
-
|
|
|
286 |
"""
|
287 |
-
|
288 |
-
|
289 |
"""
|
290 |
-
|
291 |
-
|
292 |
-
if isinstance(ingredients_data, str):
|
293 |
-
data = json.loads(ingredients_data)
|
294 |
-
else:
|
295 |
-
data = ingredients_data # Ist bereits ein Dict (z.B. von Gradio UI)
|
296 |
-
|
297 |
-
# Parameter extrahieren (wie deine ursprüngliche Flask-API)
|
298 |
-
required_ingredients = data.get('required_ingredients', [])
|
299 |
-
available_ingredients = data.get('available_ingredients', [])
|
300 |
-
|
301 |
-
# Abwärtskompatibilität: Wenn nur 'ingredients' angegeben ist, behandle es als required_ingredients
|
302 |
-
if data.get('ingredients') and not required_ingredients:
|
303 |
-
required_ingredients = data.get('ingredients', [])
|
304 |
-
|
305 |
-
max_ingredients = data.get('max_ingredients', 7)
|
306 |
-
max_retries = data.get('max_retries', 5)
|
307 |
-
|
308 |
-
if not required_ingredients and not available_ingredients:
|
309 |
-
return json.dumps({"error": "Keine Zutaten angegeben"})
|
310 |
|
|
|
311 |
# Optimale Zutaten finden
|
312 |
optimized_ingredients = find_best_ingredients(
|
313 |
required_ingredients,
|
@@ -318,15 +296,36 @@ def flutter_api_generate_recipe(ingredients_data):
|
|
318 |
# Rezept mit optimierten Zutaten generieren
|
319 |
recipe = generate_recipe_with_t5(optimized_ingredients, max_retries)
|
320 |
|
321 |
-
#
|
322 |
result = {
|
323 |
'title': recipe['title'],
|
324 |
'ingredients': recipe['ingredients'],
|
325 |
'directions': recipe['directions'],
|
326 |
'used_ingredients': optimized_ingredients
|
327 |
}
|
|
|
328 |
|
329 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
330 |
|
331 |
except Exception as e:
|
332 |
return json.dumps({"error": f"Fehler bei der Rezeptgenerierung: {str(e)}"})
|
@@ -334,34 +333,17 @@ def flutter_api_generate_recipe(ingredients_data):
|
|
334 |
def gradio_ui_generate_recipe(required_ingredients_text, available_ingredients_text, max_ingredients_val, max_retries_val):
|
335 |
"""Gradio UI Funktion für die Web-Oberfläche"""
|
336 |
try:
|
337 |
-
# Text-Eingaben parsen
|
338 |
required_ingredients = [ing.strip() for ing in required_ingredients_text.split(',') if ing.strip()]
|
339 |
available_ingredients = [ing.strip() for ing in available_ingredients_text.split(',') if ing.strip()]
|
340 |
|
341 |
-
#
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
'max_ingredients': max_ingredients_val, # Verwende den Parameter aus dem Slider
|
346 |
-
'max_retries': max_retries_val # Verwende den Parameter aus dem Slider
|
347 |
-
}
|
348 |
-
|
349 |
-
# --- WICHTIG: Wandle das Python-Dictionary in einen JSON-String um,
|
350 |
-
# da flutter_api_generate_recipe intern dieses Format erwartet,
|
351 |
-
# wenn es über einen Gradio-Input aufgerufen wird, der einen String liefert.
|
352 |
-
# Dies simuliert den JSON-String, den die Flutter-App senden würde.
|
353 |
-
data_json_string = json.dumps(data_for_flutter_api)
|
354 |
-
# ---------------------------------------------------------------------
|
355 |
-
|
356 |
-
# Verwende dieselbe Funktion wie die Flutter API
|
357 |
-
result_json = flutter_api_generate_recipe(data_json_string) # <-- Hier die Änderung
|
358 |
-
|
359 |
-
result = json.loads(result_json)
|
360 |
|
361 |
if 'error' in result:
|
362 |
return result['error'], "", "", ""
|
363 |
|
364 |
-
# Für die Gradio-Anzeige formatieren
|
365 |
ingredients_list = '\n'.join([f"• {ing}" for ing in result['ingredients']])
|
366 |
directions_list = '\n'.join([f"{i+1}. {dir}" for i, dir in enumerate(result['directions'])])
|
367 |
used_ingredients = ', '.join(result['used_ingredients'])
|
@@ -374,7 +356,6 @@ def gradio_ui_generate_recipe(required_ingredients_text, available_ingredients_t
|
|
374 |
)
|
375 |
|
376 |
except Exception as e:
|
377 |
-
# Fehlermeldung für die Gradio UI
|
378 |
return f"Fehler: {str(e)}", "", "", ""
|
379 |
|
380 |
# Erstelle die Gradio Oberfläche
|
@@ -395,7 +376,6 @@ with gr.Blocks(title="AI Rezept Generator") as demo:
|
|
395 |
placeholder="Knoblauch, Tomate, Pfeffer, Kräuter",
|
396 |
lines=2
|
397 |
)
|
398 |
-
# Die Parameter-Namen für Slider müssen mit den Argumenten der Gradio UI Funktion übereinstimmen
|
399 |
max_ing = gr.Slider(3, 10, value=7, step=1, label="Maximale Zutaten")
|
400 |
max_retries = gr.Slider(1, 10, value=5, step=1, label="Max. Wiederholungsversuche")
|
401 |
|
@@ -409,13 +389,13 @@ with gr.Blocks(title="AI Rezept Generator") as demo:
|
|
409 |
|
410 |
generate_btn.click(
|
411 |
fn=gradio_ui_generate_recipe,
|
412 |
-
inputs=[required_ing, available_ing, max_ing, max_retries],
|
413 |
outputs=[title_output, ingredients_output, directions_output, used_ingredients_output]
|
414 |
)
|
415 |
|
416 |
with gr.Tab("API-Test"):
|
417 |
-
gr.Markdown("### Teste die
|
418 |
-
gr.Markdown("Dieser Tab verwendet
|
419 |
|
420 |
api_input = gr.Textbox(
|
421 |
label="JSON-Eingabe (Flutter API-Format)",
|
@@ -425,11 +405,12 @@ with gr.Blocks(title="AI Rezept Generator") as demo:
|
|
425 |
api_output = gr.Textbox(label="JSON-Ausgabe", lines=15, interactive=False)
|
426 |
api_test_btn = gr.Button("API testen", variant="secondary")
|
427 |
|
|
|
428 |
api_test_btn.click(
|
429 |
fn=flutter_api_generate_recipe,
|
430 |
inputs=[api_input],
|
431 |
outputs=[api_output],
|
432 |
-
api_name="generate_recipe_for_flutter" #
|
433 |
)
|
434 |
|
435 |
gr.Examples(
|
@@ -440,5 +421,46 @@ with gr.Blocks(title="AI Rezept Generator") as demo:
|
|
440 |
inputs=[api_input]
|
441 |
)
|
442 |
|
443 |
-
|
444 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
import numpy as np
|
5 |
import random
|
6 |
import json
|
7 |
+
from fastapi import FastAPI, Request
|
8 |
+
from fastapi.responses import JSONResponse
|
9 |
+
from pydantic import BaseModel
|
10 |
|
11 |
# Lade RecipeBERT Modell (für semantische Zutat-Kombination)
|
12 |
bert_model_name = "alexdseo/RecipeBERT"
|
|
|
92 |
def find_best_ingredients(required_ingredients, available_ingredients, max_ingredients=6, avg_weight=0.6):
|
93 |
"""
|
94 |
Findet die besten Zutaten basierend auf RecipeBERT Embeddings.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
95 |
"""
|
96 |
# Stelle sicher, dass keine Duplikate in den Listen sind
|
97 |
required_ingredients = list(set(required_ingredients))
|
|
|
276 |
"directions": ["Fehler beim Generieren der Rezeptanweisungen"]
|
277 |
}
|
278 |
|
279 |
+
# Diese Funktion wird von der Gradio-UI und der neuen FastAPI-Route aufgerufen.
|
280 |
+
def process_recipe_request_logic(required_ingredients, available_ingredients, max_ingredients, max_retries):
|
281 |
"""
|
282 |
+
Kernlogik zur Verarbeitung einer Rezeptgenerierungsanfrage.
|
283 |
+
Ausgelagert, um von verschiedenen Endpunkten aufgerufen zu werden.
|
284 |
"""
|
285 |
+
if not required_ingredients and not available_ingredients:
|
286 |
+
return {"error": "Keine Zutaten angegeben"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
287 |
|
288 |
+
try:
|
289 |
# Optimale Zutaten finden
|
290 |
optimized_ingredients = find_best_ingredients(
|
291 |
required_ingredients,
|
|
|
296 |
# Rezept mit optimierten Zutaten generieren
|
297 |
recipe = generate_recipe_with_t5(optimized_ingredients, max_retries)
|
298 |
|
299 |
+
# Ergebnis formatieren
|
300 |
result = {
|
301 |
'title': recipe['title'],
|
302 |
'ingredients': recipe['ingredients'],
|
303 |
'directions': recipe['directions'],
|
304 |
'used_ingredients': optimized_ingredients
|
305 |
}
|
306 |
+
return result
|
307 |
|
308 |
+
except Exception as e:
|
309 |
+
return {"error": f"Fehler bei der Rezeptgenerierung: {str(e)}"}
|
310 |
+
|
311 |
+
def flutter_api_generate_recipe(ingredients_data):
|
312 |
+
"""
|
313 |
+
Flutter-freundliche API-Funktion für den Gradio-API-Test-Tab.
|
314 |
+
Verarbeitet JSON-String-Eingabe und gibt JSON-String-Ausgabe zurück.
|
315 |
+
"""
|
316 |
+
try:
|
317 |
+
data = json.loads(ingredients_data) # Muss ein JSON-String sein
|
318 |
+
|
319 |
+
required_ingredients = data.get('required_ingredients', [])
|
320 |
+
available_ingredients = data.get('available_ingredients', [])
|
321 |
+
max_ingredients = data.get('max_ingredients', 7)
|
322 |
+
max_retries = data.get('max_retries', 5)
|
323 |
+
|
324 |
+
# Rufe die Kernlogik auf
|
325 |
+
result_dict = process_recipe_request_logic(
|
326 |
+
required_ingredients, available_ingredients, max_ingredients, max_retries
|
327 |
+
)
|
328 |
+
return json.dumps(result_dict) # Gibt einen JSON-String zurück
|
329 |
|
330 |
except Exception as e:
|
331 |
return json.dumps({"error": f"Fehler bei der Rezeptgenerierung: {str(e)}"})
|
|
|
333 |
def gradio_ui_generate_recipe(required_ingredients_text, available_ingredients_text, max_ingredients_val, max_retries_val):
|
334 |
"""Gradio UI Funktion für die Web-Oberfläche"""
|
335 |
try:
|
|
|
336 |
required_ingredients = [ing.strip() for ing in required_ingredients_text.split(',') if ing.strip()]
|
337 |
available_ingredients = [ing.strip() for ing in available_ingredients_text.split(',') if ing.strip()]
|
338 |
|
339 |
+
# Rufe die Kernlogik auf
|
340 |
+
result = process_recipe_request_logic(
|
341 |
+
required_ingredients, available_ingredients, max_ingredients_val, max_retries_val
|
342 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
343 |
|
344 |
if 'error' in result:
|
345 |
return result['error'], "", "", ""
|
346 |
|
|
|
347 |
ingredients_list = '\n'.join([f"• {ing}" for ing in result['ingredients']])
|
348 |
directions_list = '\n'.join([f"{i+1}. {dir}" for i, dir in enumerate(result['directions'])])
|
349 |
used_ingredients = ', '.join(result['used_ingredients'])
|
|
|
356 |
)
|
357 |
|
358 |
except Exception as e:
|
|
|
359 |
return f"Fehler: {str(e)}", "", "", ""
|
360 |
|
361 |
# Erstelle die Gradio Oberfläche
|
|
|
376 |
placeholder="Knoblauch, Tomate, Pfeffer, Kräuter",
|
377 |
lines=2
|
378 |
)
|
|
|
379 |
max_ing = gr.Slider(3, 10, value=7, step=1, label="Maximale Zutaten")
|
380 |
max_retries = gr.Slider(1, 10, value=5, step=1, label="Max. Wiederholungsversuche")
|
381 |
|
|
|
389 |
|
390 |
generate_btn.click(
|
391 |
fn=gradio_ui_generate_recipe,
|
392 |
+
inputs=[required_ing, available_ing, max_ing, max_retries],
|
393 |
outputs=[title_output, ingredients_output, directions_output, used_ingredients_output]
|
394 |
)
|
395 |
|
396 |
with gr.Tab("API-Test"):
|
397 |
+
gr.Markdown("### Teste die Gradio API (für interne Tests)")
|
398 |
+
gr.Markdown("Dieser Tab verwendet die interne Gradio API für Testzwecke.")
|
399 |
|
400 |
api_input = gr.Textbox(
|
401 |
label="JSON-Eingabe (Flutter API-Format)",
|
|
|
405 |
api_output = gr.Textbox(label="JSON-Ausgabe", lines=15, interactive=False)
|
406 |
api_test_btn = gr.Button("API testen", variant="secondary")
|
407 |
|
408 |
+
# Hier wird die Funktion weiterhin für den Gradio-eigenen API-Test-Tab verwendet.
|
409 |
api_test_btn.click(
|
410 |
fn=flutter_api_generate_recipe,
|
411 |
inputs=[api_input],
|
412 |
outputs=[api_output],
|
413 |
+
api_name="generate_recipe_for_flutter" # Gradio-interner API-Name
|
414 |
)
|
415 |
|
416 |
gr.Examples(
|
|
|
421 |
inputs=[api_input]
|
422 |
)
|
423 |
|
424 |
+
# --- FastAPI-Integration ---
|
425 |
+
app = FastAPI()
|
426 |
+
|
427 |
+
class RecipeRequest(BaseModel):
|
428 |
+
required_ingredients: list[str] = []
|
429 |
+
available_ingredients: list[str] = []
|
430 |
+
max_ingredients: int = 7
|
431 |
+
max_retries: int = 5
|
432 |
+
|
433 |
+
@app.post("/api/generate_recipe_rest")
|
434 |
+
async def generate_recipe_rest_api(request_data: RecipeRequest):
|
435 |
+
"""
|
436 |
+
Standard-REST-API-Endpunkt für die Flutter-App.
|
437 |
+
Nimmt direkt JSON-Daten an und gibt direkt JSON zurück.
|
438 |
+
"""
|
439 |
+
required_ingredients = request_data.required_ingredients
|
440 |
+
available_ingredients = request_data.available_ingredients
|
441 |
+
max_ingredients = request_data.max_ingredients
|
442 |
+
max_retries = request_data.max_retries
|
443 |
+
|
444 |
+
# Abwärtskompatibilität, falls 'ingredients' statt 'required_ingredients' gesendet wird
|
445 |
+
# Dies ist in der FastAPI-Pydantic-Modelldefinition nicht direkt abbildbar,
|
446 |
+
# aber du könntest es manuell hinzufügen, falls nötig, wenn das Pydantic-Modell flexibler wäre.
|
447 |
+
# Für den Einfachheit halber gehen wir davon aus, dass Flutter die korrekten Felder sendet.
|
448 |
+
|
449 |
+
result_dict = process_recipe_request_logic(
|
450 |
+
required_ingredients, available_ingredients, max_ingredients, max_retries
|
451 |
+
)
|
452 |
+
|
453 |
+
return JSONResponse(content=result_dict)
|
454 |
+
|
455 |
+
# Gradio-App als Sub-App in die FastAPI-App mounten
|
456 |
+
# Dies ist der Standardweg, um Gradio in eine FastAPI-Anwendung einzubetten.
|
457 |
+
# Der Gradio-Teil wird dann unter dem Wurzelpfad '/'.
|
458 |
+
app = gr.mount_gradio_app(app, demo, path="/") # Gradio unter dem Wurzelpfad mounten
|
459 |
+
|
460 |
+
# Wenn du deine App lokal ausführst, kannst du FastAPI mit Uvicorn starten:
|
461 |
+
# if __name__ == "__main__":
|
462 |
+
# import uvicorn
|
463 |
+
# uvicorn.run(app, host="0.0.0.0", port=8000)
|
464 |
+
|
465 |
+
# Für Hugging Face Spaces ist der if __name__ == "__main__": Block nicht nötig,
|
466 |
+
# da Spaces Uvicorn automatisch startet und die "app"-Variable sucht.
|