212 lines
6.6 KiB
Python
212 lines
6.6 KiB
Python
import os
|
|
from dotenv import load_dotenv
|
|
from flask import Flask, render_template, redirect, url_for, request, flash, send_from_directory
|
|
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))
|
|
|
|
# PWA service worker
|
|
|
|
@app.route('/sw.js')
|
|
def serve_sw():
|
|
return send_from_directory('static', 'sw.js', mimetype='application/javascript')
|
|
|
|
@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/<int:plan_id>")
|
|
@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()
|