Changement de Streamlit vers Flask

This commit is contained in:
François Pelletier 2024-12-30 22:36:06 -05:00
parent 761f3d10aa
commit 6a4cff83f2
21 changed files with 361 additions and 133 deletions

View file

@ -1 +1,2 @@
BACKEND_URL=
BACKEND_URL=
FLASK_DEBUG=

View file

@ -1,40 +0,0 @@
# .gitlab-ci.yml
stages:
- dockerize
- deployment
build-push-docker-image-job:
stage: dockerize
# Specify a Docker image to run the job in.
image: docker:20-dind
# Specify an additional image 'docker:dind' ("Docker-in-Docker") that
# will start up the Docker daemon when it is brought up by a runner.
before_script:
- docker login -u "$DOCKER_REGISTRY_USER" -p "$DOCKER_REGISTRY_PASSWORD" $DOCKER_REGISTRY_URL # Instructs GitLab to login to its registry
services:
- name: docker:20-dind
alias: docker
command: ["--tls=false"]
script:
- echo "Building..." # MAKE SURE NO SPACE ON EITHER SIDE OF = IN THE FOLLOWING LINE
- export CONTAINER_FULL_IMAGE_NAME_WITH_TAG=$IMAGE_NAME_WITH_REGISTRY_PREFIX/my-build-image:$COMMIT_HASH
- docker build --network=host -f ./Dockerfile --pull -t built-image-name .
- docker tag built-image-name "$CONTAINER_FULL_IMAGE_NAME_WITH_TAG"
- docker push "$CONTAINER_FULL_IMAGE_NAME_WITH_TAG"
- echo "$CONTAINER_FULL_IMAGE_NAME_WITH_TAG"
- echo "Deploying on CapRover..."
- docker run --network=host caprover/cli-caprover:2.2.3 caprover deploy --caproverUrl "$CAPROVER_URL" --caproverPassword "$CAPROVER_PASSWORD" -a "$CAPROVER_APP" -i "$CONTAINER_FULL_IMAGE_NAME_WITH_TAG"
only:
- main
variables:
DOCKER_DRIVER: overlay2
DOCKER_REGISTRY_USER: ${CI_REGISTRY_USER}
DOCKER_REGISTRY_PASSWORD: ${CI_REGISTRY_PASSWORD}
DOCKER_REGISTRY_URL: ${CI_REGISTRY}
IMAGE_NAME_WITH_REGISTRY_PREFIX: ${CI_REGISTRY_IMAGE}
COMMIT_HASH: ${CI_COMMIT_SHA}
CAPROVER_URL: ${CAPROVER_URL}
CAPROVER_PASSWORD: ${CAPROVER_PASSWORD}
CAPROVER_APP: ${CAPROVER_APP}

View file

@ -1,19 +0,0 @@
steps:
- name: docker
image: plugins/docker
commands:
- docker login docker.io -u $${DOCKERHUB_USERNAME} -p $${DOCKERHUB_PASSWORD}
- docker build --rm=true -f Dockerfile -t $${CI_COMMIT_REF} .
- docker tag $${CI_COMMIT_REF} $${DOCKERHUB_USERNAME}/$${CI_REPO_NAME}:latest
- docker push $${DOCKERHUB_USERNAME}/$${CI_REPO_NAME}:latest
secrets: [ dockerhub_username, dockerhub_password ]
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- name: caprover
image: plugins/docker
commands:
- docker login docker.io -u $${DOCKERHUB_USERNAME} -p $${DOCKERHUB_PASSWORD}
- docker run --network=host caprover/cli-caprover:2.2.3 caprover deploy --caproverUrl "$${CAPROVER_URL}" --caproverPassword "$${CAPROVER_PASSWORD}" -a "$${CI_REPO_NAME}" -i docker.io/$${DOCKERHUB_USERNAME}/$${CI_REPO_NAME}
secrets: [ dockerhub_username, dockerhub_password, caprover_url, caprover_password ]
volumes:
- /var/run/docker.sock:/var/run/docker.sock

View file

@ -1,6 +1,6 @@
FROM python:3.10-slim
FROM python:3.13-slim-bookworm
EXPOSE 8051
EXPOSE 5000
WORKDIR /app
@ -10,9 +10,14 @@ RUN pip install -r requirements.txt
# Copy the app's code
COPY config.py ./
COPY main.py ./
COPY app.py ./
COPY ressources ./ressources
COPY templates ./templates
COPY static ./static
# Set the environment variable for Flask
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
# Set the entrypoint to run the app
ENTRYPOINT [ "streamlit", "run" ]
CMD [ "main.py", "--server.port=8051", "--server.headless", "true", "--server.fileWatcherType", "none", "--browser.gatherUsageStats", "false"]
CMD ["flask", "run", "--port=5000"]

35
app.py Normal file
View file

@ -0,0 +1,35 @@
from flask import Flask, render_template, request, jsonify
from flaskext.markdown import Markdown
from markupsafe import Markup
import requests
import os
from config import settings
app = Flask(__name__)
Markdown(app)
def correct_text(text):
url = f"{settings.BACKEND_URL}/corriger"
response = requests.post(url, json={"text": text})
if response.status_code != 200:
return {"error": f"Erreur lors de la requête au serveur: {response.status_code}"}
else:
return {"text": response.json()["text"]}
@app.route('/')
def index():
with open("ressources/header.md", "r") as f:
header = f.read()
with open("ressources/footer.md", "r") as f:
footer = f.read()
return render_template('index.html', header=header, footer=footer)
@app.route('/correct', methods=['POST'])
def correct():
text = request.form['text']
result = correct_text(text)
return jsonify(result)
if __name__ == '__main__':
debug_mode = os.getenv('FLASK_DEBUG', 'False').lower() == 'true'
app.run(host='localhost', debug=debug_mode)

View file

@ -1,17 +1,9 @@
from typing import Optional
import os
from dotenv import load_dotenv
from pydantic import BaseModel
from pydantic_settings import BaseSettings
from dotenv import find_dotenv
LOGGER_NAME = "point-median-frontend"
class Settings(BaseSettings):
BACKEND_URL: str = "http://localhost:8000"
class Config:
env_file = find_dotenv()
load_dotenv()
class Settings:
BACKEND_URL = os.getenv("BACKEND_URL")
settings = Settings()

View file

@ -1,4 +1,27 @@
docker stop point-median-frontend
docker rm point-median-frontend
# Ce programme sert à lancer le job_dispatcher dans un docker localement pour tester
docker run -p 8051:8051 --name point-median-frontend --env-file .env --network host local/point-median-frontend
#!/bin/bash
# Name of the Docker network and application
NETWORK_NAME="point-median-network"
APP_NAME="point-median-frontend"
# Create the Docker network if it doesn't exist
if ! docker network inspect $NETWORK_NAME >/dev/null 2>&1; then
echo "Creating Docker network: $NETWORK_NAME"
docker network create $NETWORK_NAME
else
echo "Docker network $NETWORK_NAME already exists"
fi
# Stop and remove the existing container if it exists
docker stop $APP_NAME >/dev/null 2>&1
docker rm $APP_NAME >/dev/null 2>&1
# Run the new container
echo "Starting $APP_NAME container"
docker run -d \
-p 5000:5000 \
--name $APP_NAME \
--env-file .env \
--network $NETWORK_NAME \
local/$APP_NAME
echo "Container started. You can access the application at http://localhost:5000"

34
main.py
View file

@ -1,34 +0,0 @@
import streamlit as st
import requests
from config import settings
import streamlit.components.v1 as components
def correct_text(text):
url = f"{settings.BACKEND_URL}/corriger"
response = requests.post(url, json={"text": text})
if response.status_code != 200:
st.error("Erreur lors de la requête au serveur: {response.status_code}")
return ""
else:
return response.json()["text"]
def main():
st.title("Application Point Médian")
with open("ressources/header.md", "r") as f:
st.markdown(f.read(), unsafe_allow_html=False)
text = st.text_area("Entre le texte à corriger", placeholder="Écris ton texte ici", height=200)
if st.button("Corriger"):
corrected_text = correct_text(text)
st.text_area("Texte corrigé:", value=corrected_text, height=200)
with open("ressources/formulaire_courriel.html", "r") as f:
components.html(f.read(), height=350, scrolling=True)
with open("ressources/footer.md", "r") as f:
st.markdown(f.read(), unsafe_allow_html=False)
if __name__ == "__main__":
main()

View file

@ -1,5 +1,5 @@
pydantic~=2.3.0
pydantic-settings==2.0.3
streamlit~=1.26.0
requests~=2.31.0
python-dotenv~=1.0.0
Flask==2.3.2
Flask-Markdown==0.3
MarkupSafe==2.1.3
requests
python-dotenv

View file

@ -3,15 +3,10 @@
<div>
<h3>Abonne-toi pour ne rien manquer</h3>
<input type="hidden" name="nonce"/>
<p><label for="emaillistmonk">Courriel: </label> <input type="email" id="emaillistmonk" name="email"
required placeholder="E-mail"/></p>
<p><label for="nomlistmonk">Nom: </label> <input type="text" id="nomlistmonk" name="name"
placeholder="Nom (facultatif)"/></p>
<p>
<input id="4acf1" type="checkbox" name="l" checked value="4acf17dd-b2d6-4970-9e5b-25cacd9b3f31"/>
<label for="4acf1">Les mises à jour de l'application Point médian</label>
</p>
<p><label for="emaillistmonk">Courriel : </label> <input type="email" id="emaillistmonk" name="email"
required placeholder="E-mail"/></p>
<p><label for="nomlistmonk">Nom : </label> <input type="text" id="nomlistmonk" name="name"
placeholder="Nom (facultatif)"/></p>
<p>
<input id="a74b6" type="checkbox" name="l" checked value="a74b62d0-14e0-410c-aa86-517ee1e2e7bd"/>
<label for="a74b6">💌 La cyberlettre 💌</label>
@ -20,7 +15,6 @@
<input id="32641" type="checkbox" name="l" checked value="32641cf3-06ab-41bb-87b5-6fcd0886906a"/>
<label for="32641">Mon podcast Aires Communes</label>
</p>
<p><input type="submit" value="S'abonner"/></p>
</div>
</form>

View file

@ -1,2 +1,3 @@
Cette application convertit le texte entré dans la boîte en forme inclusive abrégée avec le point régulier . vers le point médian ·.
Les séparateurs / et - et les formes avec les parenthèses sont aussi convertis.
Cette application convertit le texte entré dans la boîte en forme inclusive abrégée avec le point régulier `.` vers le point médian `·`.
Les séparateurs `/` et `-` et les formes avec les parenthèses sont aussi convertis.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

25
static/script.js Normal file
View file

@ -0,0 +1,25 @@
$(document).ready(function() {
$('#correct-button').click(function() {
var text = $('#input-text').val();
$.ajax({
url: '/correct',
method: 'POST',
data: { text: text },
success: function(response) {
if (response.error) {
alert(response.error);
} else {
$('#output-text').val(response.text);
}
},
error: function() {
alert('Une erreur est survenue lors de la correction du texte.');
}
});
});
// Load email form
$.get('ressources/formulaire_courriel.html', function(data) {
$('#email-form').html(data);
});
});

214
static/style.css Normal file
View file

@ -0,0 +1,214 @@
:root {
--bg-color: #001233;
--text-color: #c5c5c5;
--accent-color: #daa520;
--secondary-color: #791cf8;
--tertiary-color: #daa520;
--link-color: #954df9;
--link-hover-color: #daa520;
--code-bg-color: #1d1d1d;
--code-text-color: #c1bdbd;
}
@font-face {
font-family: 'Fira Sans';
src: url(/static/fonts/FiraSans/FiraSans-Light.ttf) format("truetype");
font-weight: 300;
font-style: normal
}
@font-face {
font-family: 'Fira Sans';
src: url(/static/fonts/FiraSans/FiraSans-Regular.ttf) format("truetype");
font-weight: 400;
font-style: normal
}
@font-face {
font-family: 'Fira Sans';
src: url(/static/fonts/FiraSans/FiraSans-Medium.ttf) format("truetype");
font-weight: 500;
font-style: normal
}
@font-face {
font-family: 'Fira Sans';
src: url(/static/fonts/FiraSans/FiraSans-SemiBold.ttf) format("truetype");
font-weight: 600;
font-style: normal
}
@font-face {
font-family: 'Cinzel';
src: url(/static/fonts/Cinzel/Cinzel-Regular.ttf) format("truetype");
font-weight: 400;
font-style: normal
}
@font-face {
font-family: 'Cinzel';
src: url(/static/fonts/Cinzel/Cinzel-SemiBold.ttf) format("truetype");
font-weight: 600;
font-style: normal
}
@font-face {
font-family: 'Cinzel';
src: url(/static/fonts/Cinzel/Cinzel-Bold.ttf) format("truetype");
font-weight: 700;
font-style: normal
}
.container {
width: 100%;
max-width: 100%;
margin: 0 auto;
padding: 0 20px;
box-sizing: border-box;
}
@media screen and (min-width: 768px) {
.container {
width: 80%;
max-width: 1200px;
}
}
body {
margin: 0;
padding: 0;
}
/* Adjust the existing body styles */
body {
background-color: var(--bg-color);
color: var(--text-color);
font-family: 'Fira Sans', sans-serif;
font-weight: 300;
font-size: 18px;
line-height: 1.6;
background-image: radial-gradient(circle at 10% 20%, #8a2be20d 0%, transparent 20%), radial-gradient(circle at 90% 80%, #32cd320d 0%, transparent 20%);
}
/* Adjust padding for elements inside the container */
.container > * {
padding-left: 0;
padding-right: 0;
}
strong {
font-weight: 600
}
h1 {
font-family: 'Cinzel', serif;
font-weight: 800;
color: var(--accent-color);
text-shadow: 2px 2px 4px #00000080;
letter-spacing: 1px;
font-size: 2.5em
}
h2, h3 {
font-family: 'Cinzel', serif;
font-weight: 600;
color: var(--accent-color);
text-shadow: 1px 1px 3px #0000004d;
letter-spacing: .5px
}
h4, h5 {
font-family: 'Cinzel', serif;
font-weight: 400;
color: var(--accent-color);
text-shadow: 1px 1px 3px #0000004d;
letter-spacing: .5px
}
h2 {
font-size: 2em
}
h3 {
font-size: 1.75em
}
h4 {
font-size: 1.5em
}
h5 {
font-size: 1.25em
}
a {
color: var(--link-color);
text-decoration: none;
transition: all .3s ease
}
a:hover {
color: var(--link-hover-color);
text-decoration: underline
}
textarea {
width: 100%;
padding: 15px 20px;
margin: 15px;
background-color: var(--code-bg-color);
color: var(--code-text-color);
border: 1px solid var(--accent-color);
border-radius: 5px;
font-family: 'Fira Sans', monospace;
font-size: 16px;
resize: vertical;
line-height: 1.5;
box-sizing: border-box;
}
textarea:focus {
outline: none;
border-color: var(--secondary-color);
box-shadow: 0 0 0 2px rgba(121, 28, 248, 0.2);
}
button {
padding: 12px 24px;
background-color: var(--secondary-color);
color: var(--text-color);
border: none;
border-radius: 30px;
cursor: pointer;
font-family: 'Fira Sans', sans-serif;
font-size: 18px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 1px;
transition: all 0.3s ease;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
margin: 10px 0;
display: inline-block;
min-width: 150px;
}
button:hover {
background-color: var(--tertiary-color);
color: var(--bg-color);
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2);
}
button:active {
transform: translateY(1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
button:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(218, 165, 32, 0.4);
}
.markdown-content {
margin-bottom: 20px;
}

31
templates/index.html Normal file
View file

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Application Point Médian</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<div class="container">
<h1>Application Point Médian</h1>
<div class="markdown-content">
{{ header | markdown }}
</div>
<textarea id="input-text" placeholder="Écris ton texte ici" rows="10"></textarea>
<button id="correct-button">Corriger</button>
<textarea id="output-text" readonly rows="10"></textarea>
<div id="email-form"></div>
<div class="markdown-content">
{{ footer | markdown }}
</div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="{{ url_for('static', filename='script.js') }}"></script>
</body>
</html>