From 7946e5dce55600504aa20ad2e45e8fa5b5161858 Mon Sep 17 00:00:00 2001 From: Karl Date: Tue, 24 Jun 2025 13:01:39 +0100 Subject: [PATCH] split out routes and add settings page --- ai_frame_image_server.py | 196 +++++++------------------------------- config.cfg | 6 ++ routes/__init__.py | 11 +++ routes/auth_routes.py | 29 ++++++ routes/create_routes.py | 41 ++++++++ routes/gallery_routes.py | 15 +++ routes/image_routes.py | 22 +++++ routes/index_routes.py | 22 +++++ routes/job_routes.py | 8 ++ routes/settings_routes.py | 40 ++++++++ templates/index.html | 2 +- templates/login.html | 1 + templates/settings.html | 157 ++++++++++++++++++++++++++++++ 13 files changed, 385 insertions(+), 165 deletions(-) create mode 100644 config.cfg create mode 100644 routes/__init__.py create mode 100644 routes/auth_routes.py create mode 100644 routes/create_routes.py create mode 100644 routes/gallery_routes.py create mode 100644 routes/image_routes.py create mode 100644 routes/index_routes.py create mode 100644 routes/job_routes.py create mode 100644 routes/settings_routes.py create mode 100644 templates/settings.html diff --git a/ai_frame_image_server.py b/ai_frame_image_server.py index 90e2184..e9dcae2 100644 --- a/ai_frame_image_server.py +++ b/ai_frame_image_server.py @@ -1,182 +1,50 @@ -from flask import ( - Flask, - render_template, - send_from_directory, - request, - jsonify, - redirect, - url_for, - session, - render_template_string, -) +from flask import Flask +from libs.generic import load_config import os -import time -import threading -from apscheduler.schedulers.background import BackgroundScheduler -from libs.generic import ( - load_config, - load_recent_prompts, - get_details_from_png, - get_current_version, - load_models_from_config, - load_topics_from_config -) -from libs.comfyui import cancel_current_job, create_image, select_model -from libs.ollama import create_prompt_on_openwebui -# workflow test commit +from routes import ( + auth_routes, + gallery_routes, + image_routes, + index_routes, + job_routes, + create_routes, + settings_routes +) user_config = load_config() app = Flask(__name__) -app.secret_key = os.environ.get('SECRET_KEY') +app.secret_key = os.environ.get("SECRET_KEY") -image_folder = "./output" +# Inject config into routes that need it +create_routes.init_app(user_config) +auth_routes.init_app(user_config) +# Register blueprints +app.register_blueprint(index_routes.bp) +app.register_blueprint(auth_routes.bp) +app.register_blueprint(gallery_routes.bp) +app.register_blueprint(image_routes.bp) +app.register_blueprint(job_routes.bp) +app.register_blueprint(create_routes.bp) +app.register_blueprint(settings_routes.bp) -@app.route("/", methods=["GET"]) -def index() -> str: - """ - Renders the main HTML template with image and prompt. - """ - image_filename = "./image.png" - image_path = os.path.join(image_folder, image_filename) +# Optional: scheduler setup +from apscheduler.schedulers.background import BackgroundScheduler +import time +from libs.comfyui import create_image - prompt = get_details_from_png(image_path)["p"] - - version = get_current_version() - - return render_template( - "index.html", - image=image_filename, - prompt=prompt, - reload_interval=user_config["frame"]["reload_interval"], - version=version, - ) - - -@app.route('/login', methods=['GET', 'POST']) -def login(): - if request.method == 'POST': - if request.form['password'] == user_config["frame"]["password_for_auth"]: - session['authenticated'] = True - return render_template("create_image.html", models=load_models_from_config(), topics=load_topics_from_config()) - else: - return redirect(url_for('login')) - return render_template('login.html') - - -@app.route("/images", methods=["GET"]) -def gallery() -> str: - images = [] - for f in os.listdir(image_folder): - if f.lower().endswith(("png", "jpg", "jpeg", "gif")): - images.append({"filename": f}) - images = sorted( - images, - key=lambda x: os.path.getmtime(os.path.join(image_folder, x["filename"])), - reverse=True, - ) - return render_template("gallery.html", images=images) - - -@app.route("/image-details/", methods=["GET"]) -def image_details(filename): - path = os.path.join(image_folder, filename) - if not os.path.exists(path): - return {"error": "File not found"}, 404 - details = get_details_from_png(path) - return {"prompt": details["p"], "model": details["m"], "date": details["d"]} - - -@app.route("/images/thumbnails/") -def serve_thumbnail(filename): - return send_from_directory("output/thumbnails", filename) - - -@app.route("/images/", methods=["GET"]) -def images(filename: str) -> None: - """ - Serves the requested image file. - Args: - filename (str): The name of the image file. - Returns: - None: Sends the image file. - """ - return send_from_directory(image_folder, filename) - - -@app.route("/cancel", methods=["GET"]) -def cancel_job() -> None: - """ - Serves the requested image file. - Args: - filename (str): The name of the image file. - Returns: - None: Sends the image file. - """ - return cancel_current_job() - - -@app.route("/create", methods=["GET", "POST"]) -def create(): - if request.method == "POST": - prompt = request.form.get("prompt") - selected_workflow, model = select_model(request.form.get("model") or "Random") - topic = request.form.get("topic") - - if not prompt: - prompt = create_prompt_on_openwebui(user_config["comfyui"]["prompt"], topic) - - # Start generation in background - threading.Thread(target=lambda: create_image(prompt, model)).start() - - return redirect( - url_for("image_queued", prompt=prompt, model=model.split(".")[0]) - ) - - # For GET requests, just show the form to enter prompt - return render_template("create_image.html", models=load_models_from_config()) - - -@app.route("/image_queued") -def image_queued(): - prompt = request.args.get("prompt", "No prompt provided.") - model = request.args.get("model", "No model selected.").split(".")[0] - return render_template("image_queued.html", prompt=prompt, model=model) - - -def scheduled_task() -> None: - """Executes the scheduled image generation task.""" +def scheduled_task(): print(f"Executing scheduled task at {time.strftime('%Y-%m-%d %H:%M:%S')}") create_image(None) - -@app.route("/create_image", methods=["GET"]) -def create_image_endpoint() -> str: - """ - Renders the create image template with image and prompt. - """ - if user_config["frame"]["create_requires_auth"] == "True" and not session.get('authenticated'): - return redirect(url_for("login")) - - return render_template("create_image.html", models=load_models_from_config(), topics=load_topics_from_config()) - - if user_config["frame"]["auto_regen"] == "True": if os.environ.get("WERKZEUG_RUN_MAIN") == "true": scheduler = BackgroundScheduler() - regen_time = user_config["frame"]["regen_time"].split(":") - scheduler.add_job( - scheduled_task, - "cron", - hour=regen_time[0], - minute=regen_time[1], - id="scheduled_task", - max_instances=1, # prevent overlapping - replace_existing=True, # don't double-schedule - ) + h, m = user_config["frame"]["regen_time"].split(":") + scheduler.add_job(scheduled_task, "cron", hour=h, minute=m, id="scheduled_task", max_instances=1, replace_existing=True) scheduler.start() - os.makedirs(image_folder, exist_ok=True) - app.run(host="0.0.0.0", port=user_config["frame"]["port"], debug=True) +os.makedirs("./output", exist_ok=True) +app.run(host="0.0.0.0", port=user_config["frame"]["port"], debug=True) diff --git a/config.cfg b/config.cfg new file mode 100644 index 0000000..b6658b0 --- /dev/null +++ b/config.cfg @@ -0,0 +1,6 @@ +[settings] +my_items = chelsea fc, stamford bridge, charpi dog, soccer, IT, computing, cybernetic insects, alien, predator, the simpsons, south park, nintendo, sega, xbox, playstation, pixelart, bmw e90, test + +[comfyui] +topics = chelsea fc, stamford bridge, charpi dog, soccer, IT, computing, cybernetic insects, alien, predator, the simpsons, south park, nintendo, sega, xbox, playstation, pixelart, bmw e90, gg + diff --git a/routes/__init__.py b/routes/__init__.py new file mode 100644 index 0000000..808a9b3 --- /dev/null +++ b/routes/__init__.py @@ -0,0 +1,11 @@ +from . import auth_routes, create_routes, gallery_routes, image_routes, index_routes, job_routes, settings_routes + +__all__ = [ + "auth_routes", + "create_routes", + "gallery_routes", + "image_routes", + "index_routes", + "job_routes", + "settings_routes" +] diff --git a/routes/auth_routes.py b/routes/auth_routes.py new file mode 100644 index 0000000..7314a32 --- /dev/null +++ b/routes/auth_routes.py @@ -0,0 +1,29 @@ +from flask import Blueprint, render_template, request, redirect, url_for, session +from libs.generic import load_models_from_config, load_topics_from_config +from urllib.parse import urlparse, urljoin + +bp = Blueprint("auth_routes", __name__) +user_config = None + +def init_app(config): + global user_config + user_config = config + +def is_safe_url(target): + ref_url = urlparse(request.host_url) + test_url = urlparse(urljoin(request.host_url, target)) + return test_url.scheme in ("http", "https") and ref_url.netloc == test_url.netloc + +@bp.route("/login", methods=["GET", "POST"]) +def login(): + next_url = request.args.get("next") if request.method == "GET" else request.form.get("next") + + if request.method == "POST": + if request.form["password"] == user_config["frame"]["password_for_auth"]: + session["authenticated"] = True + if next_url and is_safe_url(next_url): + return redirect(next_url) + return redirect(url_for("create_image")) # fallback + return redirect(url_for("auth_routes.login", next=next_url)) # retry with `next` + + return render_template("login.html", next=next_url) \ No newline at end of file diff --git a/routes/create_routes.py b/routes/create_routes.py new file mode 100644 index 0000000..a2d813a --- /dev/null +++ b/routes/create_routes.py @@ -0,0 +1,41 @@ +from flask import Blueprint, request, render_template, redirect, url_for, session +import threading +from libs.comfyui import create_image, select_model +from libs.ollama import create_prompt_on_openwebui +from libs.generic import load_models_from_config, load_topics_from_config +import os + +bp = Blueprint("create_routes", __name__) +user_config = None # will be set in init_app + +@bp.route("/create", methods=["GET", "POST"]) +def create(): + if request.method == "POST": + prompt = request.form.get("prompt") + selected_workflow, model = select_model(request.form.get("model") or "Random") + topic = request.form.get("topic") + + if not prompt: + prompt = create_prompt_on_openwebui(user_config["comfyui"]["prompt"], topic) + + threading.Thread(target=lambda: create_image(prompt, model)).start() + return redirect(url_for("create_routes.image_queued", prompt=prompt, model=model.split(".")[0])) + + return render_template("create_image.html", models=load_models_from_config(), topics=load_topics_from_config()) + +@bp.route("/image_queued") +def image_queued(): + prompt = request.args.get("prompt", "No prompt provided.") + model = request.args.get("model", "No model selected.").split(".")[0] + return render_template("image_queued.html", prompt=prompt, model=model) + +@bp.route("/create_image", methods=["GET"]) +def create_image_page(): + if user_config["frame"]["create_requires_auth"] == "True" and not session.get("authenticated"): + return redirect(url_for("auth_routes.login", next=request.path)) + return render_template("create_image.html", models=load_models_from_config(), topics=load_topics_from_config()) + + +def init_app(config): + global user_config + user_config = config diff --git a/routes/gallery_routes.py b/routes/gallery_routes.py new file mode 100644 index 0000000..891c1d6 --- /dev/null +++ b/routes/gallery_routes.py @@ -0,0 +1,15 @@ +from flask import Blueprint, render_template +import os + +bp = Blueprint("gallery_routes", __name__) +image_folder = "./output" + +@bp.route("/images", methods=["GET"]) +def gallery(): + images = [ + {"filename": f} + for f in os.listdir(image_folder) + if f.lower().endswith(("png", "jpg", "jpeg", "gif")) + ] + images = sorted(images, key=lambda x: os.path.getmtime(os.path.join(image_folder, x["filename"])), reverse=True) + return render_template("gallery.html", images=images) diff --git a/routes/image_routes.py b/routes/image_routes.py new file mode 100644 index 0000000..ba7cc65 --- /dev/null +++ b/routes/image_routes.py @@ -0,0 +1,22 @@ +from flask import Blueprint, send_from_directory, jsonify +import os +from libs.generic import get_details_from_png + +bp = Blueprint("image_routes", __name__) +image_folder = "./output" + +@bp.route("/images/", methods=["GET"]) +def serve_image(filename): + return send_from_directory(image_folder, filename) + +@bp.route("/images/thumbnails/") +def serve_thumbnail(filename): + return send_from_directory("output/thumbnails", filename) + +@bp.route("/image-details/", methods=["GET"]) +def image_details(filename): + path = os.path.join(image_folder, filename) + if not os.path.exists(path): + return jsonify({"error": "File not found"}), 404 + details = get_details_from_png(path) + return jsonify({"prompt": details["p"], "model": details["m"], "date": details["d"]}) diff --git a/routes/index_routes.py b/routes/index_routes.py new file mode 100644 index 0000000..c13ebff --- /dev/null +++ b/routes/index_routes.py @@ -0,0 +1,22 @@ +from flask import Blueprint, render_template +import os +from libs.generic import get_details_from_png, get_current_version, load_config + +bp = Blueprint("index_routes", __name__) +image_folder = "./output" +user_config = load_config() + +@bp.route("/", methods=["GET"]) +def index(): + image_filename = "./image.png" + image_path = os.path.join(image_folder, image_filename) + prompt = get_details_from_png(image_path)["p"] + version = get_current_version() + + return render_template( + "index.html", + image=image_filename, + prompt=prompt, + reload_interval=user_config["frame"]["reload_interval"], + version=version, + ) diff --git a/routes/job_routes.py b/routes/job_routes.py new file mode 100644 index 0000000..6fec8d6 --- /dev/null +++ b/routes/job_routes.py @@ -0,0 +1,8 @@ +from flask import Blueprint +from libs.comfyui import cancel_current_job + +bp = Blueprint("job_routes", __name__) + +@bp.route("/cancel", methods=["GET"]) +def cancel_job(): + return cancel_current_job() diff --git a/routes/settings_routes.py b/routes/settings_routes.py new file mode 100644 index 0000000..d95a4da --- /dev/null +++ b/routes/settings_routes.py @@ -0,0 +1,40 @@ +from flask import Blueprint, render_template, request, redirect, url_for, session +import configparser +from libs.generic import load_topics_from_config + +bp = Blueprint('settings_route', __name__) +CONFIG_PATH = "./user_config.cfg" + +def save_items(items): + config = configparser.ConfigParser() + config.read(CONFIG_PATH) + + # Make sure the section exists + if not config.has_section('comfyui'): + config.add_section('comfyui') + + # Save updated list to the 'topics' key + config.set('comfyui', 'topics', ', '.join(items)) + + with open(CONFIG_PATH, 'w') as configfile: + config.write(configfile) + +@bp.route('/settings', methods=['GET', 'POST']) +def config_editor(): + if not session.get("authenticated"): + return redirect(url_for("auth_routes.login", next=request.path)) + items = load_topics_from_config() # should return list[str] + + if request.method == 'POST': + if 'new_topic' in request.form: + new_item = request.form.get('new_topic', '').strip() + if new_item and new_item not in items: + items.append(new_item) + elif 'delete_topic' in request.form: + to_delete = request.form.getlist('delete_topic') + items = [item for item in items if item not in to_delete] + + save_items(items) + return redirect(url_for('settings_route.config_editor')) + + return render_template('settings.html', topics=items) diff --git a/templates/index.html b/templates/index.html index 7c18187..c23b42e 100644 --- a/templates/index.html +++ b/templates/index.html @@ -134,7 +134,7 @@ {% if image %}
- Latest Image + Latest Image
{% if prompt %}
{{ prompt }}
diff --git a/templates/login.html b/templates/login.html index cee2868..9a52559 100644 --- a/templates/login.html +++ b/templates/login.html @@ -65,6 +65,7 @@
+
diff --git a/templates/settings.html b/templates/settings.html new file mode 100644 index 0000000..2fb4ffc --- /dev/null +++ b/templates/settings.html @@ -0,0 +1,157 @@ + + + + + + + Config Editor + + + + + +
+

Topics

+
+ + +
+ +
+ + +
+
+ +
+

Models

+
+ + +
+ +
+ + +
+
+ + + + + +