import os from dotenv import load_dotenv from flask import Flask, render_template, redirect, url_for, request, flash from flask_sqlalchemy import SQLAlchemy from flask_login import ( LoginManager, UserMixin, login_user, login_required, logout_user, current_user, ) from werkzeug.security import generate_password_hash, check_password_hash from openai import OpenAI load_dotenv() OPENROUTER_API_KEY=os.environ.get("OPENROUTER_API_KEY") OPENROUTER_MODEL=os.environ.get("OPENROUTER_MODEL") app = Flask(__name__) app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY") app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///mealplan.db" db = SQLAlchemy(app) login_manager = LoginManager(app) login_manager.login_view = "login" # Anvender Open-router client = OpenAI( base_url="https://openrouter.ai/api/v1", api_key=OPENROUTER_API_KEY, default_headers={ "HTTP-Referer": "http://localhost:5000", "X-Title": "ChefGPT Meal Planner", }, ) # --- Models --- class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(150), unique=True, nullable=False) password = db.Column(db.String(150), nullable=False) plans = db.relationship("MealPlan", backref="creator", lazy=True) class MealPlan(db.Model): id = db.Column(db.Integer, primary_key=True) summary = db.Column(db.Text, nullable=False) shopping = db.Column(db.Text, nullable=False) recipes = db.Column(db.Text, nullable=False) description = db.Column(db.Text, nullable=False) user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False) @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id)) @app.route("/") def index(): return render_template("index.html",source_link=os.environ.get("SOURCECODE_LINK")) @app.route("/about") def about(): return render_template("about.html") @app.route("/logout") @login_required def logout(): logout_user() return redirect(url_for("index")) @app.route("/dashboard") @login_required def dashboard(): # Show the user's previous plans plans = ( MealPlan.query.filter_by(user_id=current_user.id) .order_by(MealPlan.id.desc()) .limit(3) .all() ) return render_template("dashboard.html", plans=plans) @app.route("/plan/") @login_required def view_plan(plan_id): plan = MealPlan.query.get_or_404(plan_id) if plan.user_id != current_user.id: flash("You do not have permission to view this plan.") return redirect(url_for("dashboard")) return render_template("plan_result.html", plan=plan) @app.route("/generate", methods=["POST"]) @login_required def generate(): data = { "people_count": request.form.get("people_count"), "budget_level": request.form.get("budget_level"), "max_cooking_time": request.form.get("max_cooking_time"), "skill_level": request.form.get("skill_level"), "dietary_focus": request.form.get("dietary_focus"), "dietary_preference": request.form.get("dietary_preference"), "meal_strategy": request.form.get("meal_strategy"), "fridge_items": request.form.get("fridge_items") or "None (start from scratch)", "output_language": request.form.get("output_language", "English"), } try: with open("system_prompt.txt", "r", encoding="utf-8") as f: prompt_template = f.read() # indsætter formular parametre i systemprompt final_prompt = prompt_template.format(**data) except FileNotFoundError: flash("System prompt file missing. Please contact admin.", "error") return redirect(url_for("dashboard")) # Anvender open-router free modeller try: response = client.chat.completions.create( model=OPENROUTER_MODEL, messages=[{"role": "system", "content": final_prompt}], temperature=0.7, ) ai_output = response.choices[0].message.content except Exception as e: flash(f"AI Service Error: {str(e)}", "error") return redirect(url_for("dashboard")) # begynder parsing af AI svaret. Svaret skal leve op til formatkravet for at det kan indlæses pænt def extract_section(text, start_tag, end_tag): start = text.find(start_tag) end = text.find(end_tag) if start == -1 or end == -1 or end <= start: return None return text[start + len(start_tag) : end].strip() desc = extract_section(ai_output, "[[DESCRIPTION_START]]", "[[DESCRIPTION_END]]") summ = extract_section(ai_output, "[[SUMMARY_START]]", "[[SUMMARY_END]]") shop = extract_section(ai_output, "[[SHOPPING_START]]", "[[SHOPPING_END]]") reci = extract_section(ai_output, "[[RECIPES_START]]", "[[RECIPES_END]]") if not all([desc, summ, shop, reci]): flash("The plan could not be generated. Please try generating again.", "error") return redirect(url_for("dashboard")) # hvis AI svaret levede op til format kravet, gemmes det i db try: new_plan = MealPlan( description=desc, summary=summ, shopping=shop, recipes=reci, user_id=current_user.id, ) db.session.add(new_plan) db.session.commit() return redirect(url_for("view_plan", plan_id=new_plan.id)) except Exception as e: db.session.rollback() flash(f"Database Error: {str(e)}", "error") return redirect(url_for("dashboard")) @app.route("/login", methods=["GET", "POST"]) def login(): if request.method == "POST": user = User.query.filter_by(username=request.form.get("username")).first() if user and check_password_hash(user.password, request.form.get("password")): login_user(user) return redirect(url_for("dashboard")) flash("Invalid credentials") return render_template("login.html") @app.route("/register", methods=["GET", "POST"]) def register(): if request.method == "POST": new_user = User( username=request.form.get("username"), password=generate_password_hash( request.form.get("password"), method="pbkdf2:sha256" ), ) db.session.add(new_user) db.session.commit() return redirect(url_for("/")) return render_template("register.html") if __name__ == "__main__": with app.app_context(): db.create_all() app.run()