Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions front-flask/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 5000

CMD ["python", "app.py"]
75 changes: 75 additions & 0 deletions front-flask/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import os

import requests
from flask import Flask, render_template, request, url_for

app = Flask(__name__)

API_HOST = os.environ.get("API_HOST", "http://localhost:8888").rstrip("/")

CUT_OPTIONS = ["Ideal", "Premium", "Good", "Very Good", "Fair"]
COLOR_OPTIONS = ["E", "I", "J", "H", "F", "G", "D"]
CLARITY_OPTIONS = ["SI2", "SI1", "VS1", "VS2", "VVS2", "VVS1", "I1", "IF"]


@app.route("/")
def home():
return render_template("home.html")


@app.route("/predict", methods=["GET", "POST"])
def predict():
prediction = None
error = None
form_data = {}

if request.method == "POST":
form_data = request.form.to_dict()
try:
payload = {
"carat": float(form_data.get("carat", 0.5)),
"cut": form_data.get("cut", "Ideal"),
"color": form_data.get("color", "E"),
"clarity": form_data.get("clarity", "SI1"),
"depth": float(form_data.get("depth", 61.0)),
"table": float(form_data.get("table", 55.0)),
"x": float(form_data.get("x", 0.0)),
"y": float(form_data.get("y", 0.0)),
"z": float(form_data.get("z", 0.0)),
}
response = requests.post(
f"{API_HOST}/predict_one", json=payload, timeout=10
)
response.raise_for_status()
result = response.json()
prediction = result.get("price")
if prediction is None:
error = f"API response is missing 'price'. Response: {result}"
except requests.exceptions.ConnectionError:
error = "Prediction API is unreachable. Please make sure the API is running."
except requests.exceptions.Timeout:
error = "Prediction API timed out. Please try again."
except requests.exceptions.HTTPError as exc:
error = f"API returned an error: {exc}"
except (ValueError, KeyError) as exc:
error = f"Failed to parse API response: {exc}"

return render_template(
"predict.html",
prediction=prediction,
error=error,
form_data=form_data,
cut_options=CUT_OPTIONS,
color_options=COLOR_OPTIONS,
clarity_options=CLARITY_OPTIONS,
)


@app.route("/visualize")
def visualize():
return render_template("visualize.html", api_host=API_HOST)


if __name__ == "__main__":
debug = os.environ.get("FLASK_DEBUG", "false").lower() == "true"
app.run(debug=debug, host="0.0.0.0", port=5000)
2 changes: 2 additions & 0 deletions front-flask/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
flask==3.1.1
requests==2.32.3
200 changes: 200 additions & 0 deletions front-flask/static/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/* ─── Reset & base ─────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }

body {
margin: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f8f9fc;
color: #333;
}

/* ─── Navbar ────────────────────────────────────────────────────── */
.navbar {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
padding: 0 2rem;
display: flex;
align-items: center;
justify-content: space-between;
height: 60px;
box-shadow: 0 2px 8px rgba(0,0,0,.35);
}

.navbar-brand {
color: #e0e0ff;
font-size: 1.3rem;
font-weight: 700;
text-decoration: none;
letter-spacing: .5px;
}

.navbar-links {
list-style: none;
margin: 0;
padding: 0;
display: flex;
gap: 1.5rem;
}

.navbar-links a {
color: #b0b8e0;
text-decoration: none;
font-size: .95rem;
transition: color .2s;
}

.navbar-links a:hover,
.navbar-links a.active {
color: #ffffff;
}

/* ─── Main container ────────────────────────────────────────────── */
.container {
max-width: 860px;
margin: 2.5rem auto;
padding: 0 1.5rem;
}

/* ─── Cards ─────────────────────────────────────────────────────── */
.card {
background: #ffffff;
border-radius: 12px;
padding: 2rem;
box-shadow: 0 2px 12px rgba(0,0,0,.08);
margin-bottom: 1.5rem;
}

/* ─── Form elements ─────────────────────────────────────────────── */
.form-group {
margin-bottom: 1.1rem;
}

.form-group label {
display: block;
font-weight: 600;
margin-bottom: .35rem;
font-size: .9rem;
color: #444;
}

.form-control {
width: 100%;
padding: .5rem .75rem;
border: 1px solid #ced4da;
border-radius: 6px;
font-size: .95rem;
transition: border-color .2s, box-shadow .2s;
background: #fff;
}

.form-control:focus {
outline: none;
border-color: #6c63ff;
box-shadow: 0 0 0 3px rgba(108,99,255,.15);
}

.form-row {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 1rem;
}

/* ─── Buttons ───────────────────────────────────────────────────── */
.btn {
display: inline-block;
padding: .55rem 1.4rem;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: transform .1s, opacity .2s;
}

.btn:active { transform: scale(.97); }

.btn-primary {
background: linear-gradient(135deg, #6c63ff, #48dbfb);
color: #fff;
}

.btn-primary:hover { opacity: .9; }

/* ─── Alerts ────────────────────────────────────────────────────── */
.alert {
border-radius: 8px;
padding: .85rem 1.2rem;
margin-bottom: 1.2rem;
font-size: .95rem;
}

.alert-success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}

.alert-error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}

/* ─── Prediction result ─────────────────────────────────────────── */
.price-tag {
font-size: 2rem;
font-weight: 700;
color: #6c63ff;
}

/* ─── Logo on home page ─────────────────────────────────────────── */
.home-logo {
width: 120px;
margin: 1.5rem 0;
}

/* ─── Rotating cats ─────────────────────────────────────────────── */
/*
Clockwise rotation (sens horaire):
transform: rotate(360deg) — positive degrees = clockwise in CSS
*/
@keyframes spin-cw {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}

.cat-container {
display: flex;
gap: 1.5rem;
justify-content: center;
align-items: center;
margin: 1.5rem 0;
flex-wrap: wrap;
}

.cat {
font-size: 2.5rem;
display: inline-block;
animation: spin-cw linear infinite;
transform-origin: center;
user-select: none;
}

/* Slightly different durations for variety */
.cat:nth-child(1) { animation-duration: 3s; }
.cat:nth-child(2) { animation-duration: 4s; }
.cat:nth-child(3) { animation-duration: 2.5s; }
.cat:nth-child(4) { animation-duration: 5s; }
.cat:nth-child(5) { animation-duration: 3.5s; }

/* ─── Visualize page ─────────────────────────────────────────────── */
.plot-container {
width: 100%;
min-height: 420px;
}

/* ─── Slider display ─────────────────────────────────────────────── */
.slider-value {
font-weight: 700;
color: #6c63ff;
margin-left: .4rem;
}
28 changes: 28 additions & 0 deletions front-flask/templates/base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{% block title %}Diamonds Price Prediction{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" />
</head>
<body>

<nav class="navbar">
<a class="navbar-brand" href="{{ url_for('home') }}">💎 Diamonds</a>
<ul class="navbar-links">
<li><a href="{{ url_for('home') }}"
class="{{ 'active' if request.endpoint == 'home' }}">Home</a></li>
<li><a href="{{ url_for('predict') }}"
class="{{ 'active' if request.endpoint == 'predict' }}">Predict</a></li>
<li><a href="{{ url_for('visualize') }}"
class="{{ 'active' if request.endpoint == 'visualize' }}">Visualize</a></li>
</ul>
</nav>

<div class="container">
{% block content %}{% endblock %}
</div>

</body>
</html>
32 changes: 32 additions & 0 deletions front-flask/templates/home.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{% extends "base.html" %}
{% block title %}Home – Diamonds Price Prediction{% endblock %}

{% block content %}
<div class="card">
<h1>Diamonds Price Prediction App</h1>

<p>
This is a simple Flask application that allows you to predict the price of a diamond
based on its characteristics. You can input the features of the diamond and get an
estimated price using a machine learning model deployed as an API.
</p>

<!-- Logo -->
<img
class="home-logo"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAMAAABF0y+mAAAAP1BMVEVHcEwKESgKESgRESsKESgOESoTES0XEi4fEjItEzkhEzNDEUEhEzRjDUppDEyGBlSNB1cLESgXES4KESgKESiXRhehAAAAFXRSTlMANcsn/8T/wv//xP/C/5EIAr+GtXOG2sbdAAAAtUlEQVR4AX3LURZEQAwF0dARCQD2v9Z5DUB66rfOpUdZRunyPP0Cc0jOgrn4A9O0EJSgQdZ8Wupa6UJVUzNVj1ZmVhuqHIgVw//SBq/tuha38WDb9kPbOrSJsyMCtTcNGmdPNMSpT1rGCQgaZ/mAomo1IGhtqnKno4jaRGtTnOMNMqYOtIUnHC6IKYA7FczxgoxLZ3jM4YQI8KSMxhMiusWx4MCLevCiDryoCy86M1rec2E0/wCazgih6xhvCQAAAABJRU5ErkJggg=="
alt="Diamonds Logo"
/>

<!-- Clockwise-rotating cats -->
<div class="cat-container">
<span class="cat" title="Clockwise cat 1">🐱</span>
<span class="cat" title="Clockwise cat 2">🐈</span>
<span class="cat" title="Clockwise cat 3">😺</span>
<span class="cat" title="Clockwise cat 4">🐱</span>
<span class="cat" title="Clockwise cat 5">🐈‍⬛</span>
</div>

<a class="btn btn-primary" href="{{ url_for('predict') }}">Try a Prediction →</a>
</div>
{% endblock %}
Loading