196 lines
6.7 KiB
Python
196 lines
6.7 KiB
Python
"""JustVitamin × QuikCue — AI Content Engine
|
||
Live proposal site with real Gemini-powered demos."""
|
||
|
||
import os, json, time, traceback
|
||
from pathlib import Path
|
||
from flask import (Flask, render_template, jsonify, request,
|
||
send_from_directory, redirect)
|
||
|
||
from scraper import scrape_product, scrape_competitor
|
||
from ai_engine import (generate_asset_pack, competitor_xray, pdp_surgeon,
|
||
optimise_pdp_copy, generate_product_images)
|
||
|
||
app = Flask(__name__,
|
||
static_folder="static",
|
||
template_folder="templates")
|
||
|
||
GEN_DIR = Path(__file__).parent / "generated"
|
||
GEN_DIR.mkdir(exist_ok=True)
|
||
|
||
# ── Page routes ──────────────────────────────────────────────
|
||
|
||
@app.route("/")
|
||
def index():
|
||
return render_template("index.html")
|
||
|
||
@app.route("/dashboard")
|
||
@app.route("/dashboard/")
|
||
def dashboard():
|
||
return redirect("/dashboard/index.html")
|
||
|
||
@app.route("/dashboard/<path:filename>")
|
||
def dashboard_files(filename):
|
||
return send_from_directory("static/dashboard", filename)
|
||
|
||
@app.route("/proposal")
|
||
def proposal():
|
||
return send_from_directory("static/proposal", "index.html")
|
||
|
||
@app.route("/offer")
|
||
def offer():
|
||
return send_from_directory("static/offer", "index.html")
|
||
|
||
@app.route("/generated/<path:filename>")
|
||
def serve_generated(filename):
|
||
return send_from_directory(GEN_DIR, filename)
|
||
|
||
|
||
# ── API: Scrape ──────────────────────────────────────────────
|
||
|
||
@app.route("/api/scrape", methods=["POST"])
|
||
def api_scrape():
|
||
"""Scrape a JustVitamins product page."""
|
||
url = request.json.get("url", "").strip()
|
||
if not url:
|
||
return jsonify({"error": "No URL provided"}), 400
|
||
try:
|
||
data = scrape_product(url)
|
||
if not data.get("title"):
|
||
return jsonify({"error": "Could not find product. Check URL."}), 400
|
||
return jsonify(data)
|
||
except Exception as e:
|
||
traceback.print_exc()
|
||
return jsonify({"error": str(e)}), 500
|
||
|
||
|
||
@app.route("/api/scrape-competitor", methods=["POST"])
|
||
def api_scrape_competitor():
|
||
"""Scrape any competitor product page."""
|
||
url = request.json.get("url", "").strip()
|
||
if not url:
|
||
return jsonify({"error": "No URL provided"}), 400
|
||
try:
|
||
data = scrape_competitor(url)
|
||
if not data.get("title"):
|
||
return jsonify({"error": "Could not extract product data."}), 400
|
||
return jsonify(data)
|
||
except Exception as e:
|
||
traceback.print_exc()
|
||
return jsonify({"error": str(e)}), 500
|
||
|
||
|
||
# ── API: Demo A — 12-Asset Pack ──────────────────────────────
|
||
|
||
@app.route("/api/generate-pack", methods=["POST"])
|
||
def api_generate_pack():
|
||
"""Generate a full 12-asset marketing pack from product data."""
|
||
product = request.json
|
||
if not product:
|
||
return jsonify({"error": "No product data"}), 400
|
||
try:
|
||
t0 = time.time()
|
||
pack = generate_asset_pack(product)
|
||
pack["_generation_time"] = f"{time.time()-t0:.1f}s"
|
||
pack["_product_title"] = product.get("title", "")
|
||
return jsonify(pack)
|
||
except Exception as e:
|
||
traceback.print_exc()
|
||
return jsonify({"error": str(e)}), 500
|
||
|
||
|
||
# ── API: Demo B — Competitor X-Ray ───────────────────────────
|
||
|
||
@app.route("/api/competitor-xray", methods=["POST"])
|
||
def api_competitor_xray():
|
||
"""Scrape competitor + run AI analysis."""
|
||
url = request.json.get("url", "").strip()
|
||
if not url:
|
||
return jsonify({"error": "No URL provided"}), 400
|
||
try:
|
||
t0 = time.time()
|
||
comp_data = scrape_competitor(url)
|
||
if not comp_data.get("title"):
|
||
return jsonify({"error": "Could not scrape competitor page."}), 400
|
||
analysis = competitor_xray(comp_data)
|
||
analysis["_scrape_data"] = {
|
||
"title": comp_data.get("title"),
|
||
"price": comp_data.get("price"),
|
||
"brand": comp_data.get("brand"),
|
||
"url": url,
|
||
}
|
||
analysis["_generation_time"] = f"{time.time()-t0:.1f}s"
|
||
return jsonify(analysis)
|
||
except Exception as e:
|
||
traceback.print_exc()
|
||
return jsonify({"error": str(e)}), 500
|
||
|
||
|
||
# ── API: Demo C — PDP Surgeon ────────────────────────────────
|
||
|
||
@app.route("/api/pdp-surgeon", methods=["POST"])
|
||
def api_pdp_surgeon():
|
||
"""Rewrite PDP copy in a given style."""
|
||
data = request.json or {}
|
||
product = data.get("product")
|
||
style = data.get("style", "balanced")
|
||
if not product:
|
||
return jsonify({"error": "No product data"}), 400
|
||
try:
|
||
t0 = time.time()
|
||
result = pdp_surgeon(product, style)
|
||
result["_generation_time"] = f"{time.time()-t0:.1f}s"
|
||
return jsonify(result)
|
||
except Exception as e:
|
||
traceback.print_exc()
|
||
return jsonify({"error": str(e)}), 500
|
||
|
||
|
||
# ── API: Full PDP Optimise ───────────────────────────────────
|
||
|
||
@app.route("/api/optimise", methods=["POST"])
|
||
def api_optimise():
|
||
"""Full PDP rewrite — scrape + AI copy."""
|
||
product = request.json
|
||
if not product:
|
||
return jsonify({"error": "No product data"}), 400
|
||
try:
|
||
t0 = time.time()
|
||
copy = optimise_pdp_copy(product)
|
||
copy["_generation_time"] = f"{time.time()-t0:.1f}s"
|
||
return jsonify(copy)
|
||
except Exception as e:
|
||
traceback.print_exc()
|
||
return jsonify({"error": str(e)}), 500
|
||
|
||
|
||
# ── API: Image Generation ────────────────────────────────────
|
||
|
||
@app.route("/api/generate-images", methods=["POST"])
|
||
def api_generate_images():
|
||
"""Generate AI product images via Nano Banana / Pro."""
|
||
product = request.json
|
||
if not product:
|
||
return jsonify({"error": "No product data"}), 400
|
||
try:
|
||
t0 = time.time()
|
||
images = generate_product_images(product)
|
||
images["_generation_time"] = f"{time.time()-t0:.1f}s"
|
||
return jsonify(images)
|
||
except Exception as e:
|
||
traceback.print_exc()
|
||
return jsonify({"error": str(e)}), 500
|
||
|
||
|
||
# ── Health check ─────────────────────────────────────────────
|
||
|
||
@app.route("/api/health")
|
||
def health():
|
||
return jsonify({
|
||
"status": "ok",
|
||
"gemini_key_set": bool(os.environ.get("GEMINI_API_KEY")),
|
||
})
|
||
|
||
|
||
if __name__ == "__main__":
|
||
app.run(host="0.0.0.0", port=5050, debug=True)
|