Update app.py
Browse files
app.py
CHANGED
@@ -5,7 +5,11 @@ import os
|
|
5 |
from PIL import Image
|
6 |
import tempfile
|
7 |
import json
|
8 |
-
import logging
|
|
|
|
|
|
|
|
|
9 |
|
10 |
app = Flask(__name__)
|
11 |
|
@@ -39,6 +43,76 @@ SAFETY_SETTINGS = [
|
|
39 |
)
|
40 |
]
|
41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
# --- Prompts (Keep them as they are) ---
|
43 |
prompt_tableau = """
|
44 |
Traite selon cette méthodologie :
|
@@ -120,20 +194,20 @@ Voici un exercice a trous présentant la rédaction.. référence textuelle= rep
|
|
120 |
|
121 |
EXERCICE À TROUS
|
122 |
|
123 |
-
Le thème de ... (thème du texte) a souvent fait l'objet de nombreuses préoccupations dans le monde littéraire (mais pas que). C
|
124 |
|
125 |
-
Dans son extrait (poème), l
|
126 |
|
127 |
-
S'agissant de ... (sous-axe 1), l
|
128 |
De plus, l'homme de lettres emploie ... (outil d'analyse 1 + référence textuelle) pour ... (interprétation). Il se sert aussi de ...(outil d'analyse 2 + référence textuelle) afin de ... (interprétation). Pour continuer sa (description, représentation), le ...(nationalité de l'auteur) se manque pas de faire recours à ... (outil d'analyse 3 référence textuelle) Ici, il s'agit pour l'auteur autour de ... (sous-axe 1) et ... (sous-axe 2).
|
129 |
|
130 |
Après avoir démontré ... (axe 1), voyons à présent ... (axe 2).
|
131 |
|
132 |
-
En second lieu, le poète (l
|
133 |
-
En parlant de ... (sous-axe 2), l'auteur met l'accent en premier sur… (interprétation), comme nous pouvons le voir avec la récurrence de (du/des) ... (rappel du sous-axe 2), le poète (l'auteur) souligne ... (interprétation) toujours dans le même sens de ... (rappel du sous-axe 2) . Il use de ... (outil d
|
134 |
Ainsi, ... (axe 2) est lié (e) à ... (sous-axe 1) et à ... (sous-axe 2).
|
135 |
|
136 |
-
Somme toute, ... (titre du texte) organise son sens autour de … (axe 1) et de ... (axe 2). De ces deux centres d
|
137 |
"""
|
138 |
# --- End Prompts ---
|
139 |
|
@@ -205,10 +279,49 @@ def index():
|
|
205 |
# Assuming your main HTML file is index.html
|
206 |
return render_template('index.html')
|
207 |
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
212 |
|
213 |
@app.route('/analyze', methods=['POST'])
|
214 |
def analyze():
|
@@ -229,6 +342,12 @@ def analyze():
|
|
229 |
model_id = MODEL_ID_STANDARD
|
230 |
logging.info("Standard analysis requested, using standard model.")
|
231 |
|
|
|
|
|
|
|
|
|
|
|
|
|
232 |
# Use a temporary file to handle the image upload
|
233 |
temp_file = None # Initialize to None
|
234 |
try:
|
@@ -242,6 +361,12 @@ def analyze():
|
|
242 |
image.verify() # Verify image header
|
243 |
# Re-open after verify
|
244 |
image = Image.open(temp_file.name)
|
|
|
|
|
|
|
|
|
|
|
|
|
245 |
except (IOError, SyntaxError) as e:
|
246 |
logging.error(f"Invalid image file uploaded: {e}")
|
247 |
return jsonify({'error': f'Invalid or corrupted image file: {e}'}), 400
|
@@ -254,6 +379,8 @@ def analyze():
|
|
254 |
def generate():
|
255 |
temp_file_path = temp_file.name # Store path for finally block
|
256 |
full_tableau_content = "" # Accumulate full table content for dissertation
|
|
|
|
|
257 |
try:
|
258 |
logging.info("Starting table generation stream...")
|
259 |
# Phase 1: Génération du tableau, passing the selected model_id
|
@@ -286,6 +413,7 @@ def analyze():
|
|
286 |
yield chunk_json # Forward the error
|
287 |
# Decide if you want to return here or let it finish
|
288 |
elif chunk_data.get("type") == "dissertation":
|
|
|
289 |
yield chunk_json # Stream chunk to client
|
290 |
except json.JSONDecodeError:
|
291 |
logging.error(f"Received invalid JSON from dissertation stream: {chunk_json}")
|
@@ -297,6 +425,27 @@ def analyze():
|
|
297 |
|
298 |
logging.info("Dissertation generation stream finished.")
|
299 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
300 |
except Exception as e:
|
301 |
logging.error(f"Error during streaming generation: {e}", exc_info=True)
|
302 |
# Yield a JSON error message for the client
|
|
|
5 |
from PIL import Image
|
6 |
import tempfile
|
7 |
import json
|
8 |
+
import logging
|
9 |
+
import sqlite3
|
10 |
+
from datetime import datetime
|
11 |
+
import base64
|
12 |
+
import io
|
13 |
|
14 |
app = Flask(__name__)
|
15 |
|
|
|
43 |
)
|
44 |
]
|
45 |
|
46 |
+
# --- Database Setup ---
|
47 |
+
def init_db():
|
48 |
+
"""Initialize the SQLite database"""
|
49 |
+
conn = sqlite3.connect('analysis_data.db')
|
50 |
+
cursor = conn.cursor()
|
51 |
+
|
52 |
+
cursor.execute('''
|
53 |
+
CREATE TABLE IF NOT EXISTS analyses (
|
54 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
55 |
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
56 |
+
image_name TEXT,
|
57 |
+
image_data TEXT,
|
58 |
+
consignes TEXT,
|
59 |
+
use_deepthink BOOLEAN,
|
60 |
+
model_used TEXT,
|
61 |
+
tableau_output TEXT,
|
62 |
+
dissertation_output TEXT,
|
63 |
+
user_ip TEXT,
|
64 |
+
processing_time REAL
|
65 |
+
)
|
66 |
+
''')
|
67 |
+
|
68 |
+
conn.commit()
|
69 |
+
conn.close()
|
70 |
+
|
71 |
+
def save_analysis_data(image_name, image_data, consignes, use_deepthink, model_used, tableau_output, dissertation_output, user_ip, processing_time):
|
72 |
+
"""Save analysis data to database"""
|
73 |
+
conn = sqlite3.connect('analysis_data.db')
|
74 |
+
cursor = conn.cursor()
|
75 |
+
|
76 |
+
cursor.execute('''
|
77 |
+
INSERT INTO analyses
|
78 |
+
(image_name, image_data, consignes, use_deepthink, model_used, tableau_output, dissertation_output, user_ip, processing_time)
|
79 |
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
80 |
+
''', (image_name, image_data, consignes, use_deepthink, model_used, tableau_output, dissertation_output, user_ip, processing_time))
|
81 |
+
|
82 |
+
conn.commit()
|
83 |
+
conn.close()
|
84 |
+
|
85 |
+
def get_all_analyses():
|
86 |
+
"""Get all analyses from database"""
|
87 |
+
conn = sqlite3.connect('analysis_data.db')
|
88 |
+
cursor = conn.cursor()
|
89 |
+
|
90 |
+
cursor.execute('''
|
91 |
+
SELECT id, timestamp, image_name, consignes, use_deepthink, model_used,
|
92 |
+
tableau_output, dissertation_output, user_ip, processing_time
|
93 |
+
FROM analyses
|
94 |
+
ORDER BY timestamp DESC
|
95 |
+
''')
|
96 |
+
|
97 |
+
analyses = cursor.fetchall()
|
98 |
+
conn.close()
|
99 |
+
|
100 |
+
return analyses
|
101 |
+
|
102 |
+
def get_analysis_image(analysis_id):
|
103 |
+
"""Get image data for a specific analysis"""
|
104 |
+
conn = sqlite3.connect('analysis_data.db')
|
105 |
+
cursor = conn.cursor()
|
106 |
+
|
107 |
+
cursor.execute('SELECT image_data FROM analyses WHERE id = ?', (analysis_id,))
|
108 |
+
result = cursor.fetchone()
|
109 |
+
conn.close()
|
110 |
+
|
111 |
+
return result[0] if result else None
|
112 |
+
|
113 |
+
# Initialize database on startup
|
114 |
+
init_db()
|
115 |
+
|
116 |
# --- Prompts (Keep them as they are) ---
|
117 |
prompt_tableau = """
|
118 |
Traite selon cette méthodologie :
|
|
|
194 |
|
195 |
EXERCICE À TROUS
|
196 |
|
197 |
+
Le thème de ... (thème du texte) a souvent fait l'objet de nombreuses préoccupations dans le monde littéraire (mais pas que). C'est dans ce cadre que s'inscrit l'extrait ... (titre du texte) qui fait l'objet de notre étude, de l'écrivain... (nom de l'auteur), tiré de son œuvre ... (préciser le genre littéraire de l'œuvre) ... (maison d'édition), en ... (date de publication) à la (aux) page (s) …. Dans ce texte (type du texte) à ton ... (tonalité du texte/facultative), structure, nous verrons en premier lieu,...(axe 1) et en second lieu, ...(axe 2).
|
198 |
|
199 |
+
Dans son extrait (poème), l'auteur met en relief ... (axe 1) à travers ... (sous-axe 1) et ... (sous-axe 2).
|
200 |
|
201 |
+
S'agissant de ... (sous-axe 1), l'écrivain utilise ... (outil d'analyse 1 + référence textuelle) pour montrer ... (interprétation). Aussi (de plus), par l'usage de... (outil d'analyse 2 + référence textuelle), l'écrivain ... (interprétation). Mieux encore, ... (outil d'analyse 3 + référence textuelle) nous donne également la possibilité d'appréhender ... (sous-axe 2).
|
202 |
De plus, l'homme de lettres emploie ... (outil d'analyse 1 + référence textuelle) pour ... (interprétation). Il se sert aussi de ...(outil d'analyse 2 + référence textuelle) afin de ... (interprétation). Pour continuer sa (description, représentation), le ...(nationalité de l'auteur) se manque pas de faire recours à ... (outil d'analyse 3 référence textuelle) Ici, il s'agit pour l'auteur autour de ... (sous-axe 1) et ... (sous-axe 2).
|
203 |
|
204 |
Après avoir démontré ... (axe 1), voyons à présent ... (axe 2).
|
205 |
|
206 |
+
En second lieu, le poète (l'écrivain ou l'homme de lettres) met en exergue ... (axe 2) en s'appuyant d'une part, sur... (sous-axe 1) et d'autre part, sur... (sous-axe 2). En ce qui concerne ... (sous-axe 1), l'homme de lettres met d'abord en évidence l'aspect (le caractère) ... (interprétation) comme en témoigne l'emploi (l'usage de) ... (outil d'analyse 1 + référence textuelle). Ensuite, ... (outil d'analyse 2 + référence textuelle) dévoile que... (interprétation) Enfin, ... (outil d'analyse 3 + référence textuelle) suggère que… (interprétation) . ... (axe 2) se révèle grâce à ….
|
207 |
+
En parlant de ... (sous-axe 2), l'auteur met l'accent en premier sur… (interprétation), comme nous pouvons le voir avec la récurrence de (du/des) ... (rappel du sous-axe 2), le poète (l'auteur) souligne ... (interprétation) toujours dans le même sens de ... (rappel du sous-axe 2) . Il use de ... (outil d'analyse 2 + référence textuelle). Dès lors, on peut déduire que ...(interprétation) utilise ... (outil d'analyse 3 + référence textuelle).
|
208 |
Ainsi, ... (axe 2) est lié (e) à ... (sous-axe 1) et à ... (sous-axe 2).
|
209 |
|
210 |
+
Somme toute, ... (titre du texte) organise son sens autour de … (axe 1) et de ... (axe 2). De ces deux centres d'intérêt découlent respectivement, d'une part, … (sous-axe 1 de l'axe 1) et ... (sous-axe 2 de l'axe 1) et, d'autre part, … (sous-axe 1 de l'axe 2) et … (sous-axe 2 de l'axe 2). À travers ce texte, ...(nom de l'auteur) nous ... (opinion personnelle). Une telle optique est perceptible dans la logique de... (nom de l'auteur nous permettant de faire un rapprochement thématique), dans son œuvre ...(titre de l'œuvre), dans lequel il aborde… (bref résumé de l'œuvre en question qui peut être facultatif).
|
211 |
"""
|
212 |
# --- End Prompts ---
|
213 |
|
|
|
279 |
# Assuming your main HTML file is index.html
|
280 |
return render_template('index.html')
|
281 |
|
282 |
+
@app.route('/gestion')
|
283 |
+
def gestion():
|
284 |
+
"""Page de gestion pour afficher toutes les analyses"""
|
285 |
+
return render_template('gestion.html')
|
286 |
+
|
287 |
+
@app.route('/api/analyses')
|
288 |
+
def get_analyses():
|
289 |
+
"""API endpoint to get all analyses data"""
|
290 |
+
try:
|
291 |
+
analyses = get_all_analyses()
|
292 |
+
analyses_data = []
|
293 |
+
|
294 |
+
for analysis in analyses:
|
295 |
+
analyses_data.append({
|
296 |
+
'id': analysis[0],
|
297 |
+
'timestamp': analysis[1],
|
298 |
+
'image_name': analysis[2],
|
299 |
+
'consignes': analysis[3],
|
300 |
+
'use_deepthink': analysis[4],
|
301 |
+
'model_used': analysis[5],
|
302 |
+
'tableau_output': analysis[6],
|
303 |
+
'dissertation_output': analysis[7],
|
304 |
+
'user_ip': analysis[8],
|
305 |
+
'processing_time': analysis[9]
|
306 |
+
})
|
307 |
+
|
308 |
+
return jsonify(analyses_data)
|
309 |
+
except Exception as e:
|
310 |
+
logging.error(f"Error fetching analyses: {e}")
|
311 |
+
return jsonify({'error': 'Erreur lors de la récupération des données'}), 500
|
312 |
+
|
313 |
+
@app.route('/api/analysis/<int:analysis_id>/image')
|
314 |
+
def get_analysis_image_endpoint(analysis_id):
|
315 |
+
"""API endpoint to get image for a specific analysis"""
|
316 |
+
try:
|
317 |
+
image_data = get_analysis_image(analysis_id)
|
318 |
+
if image_data:
|
319 |
+
return Response(base64.b64decode(image_data), mimetype='image/jpeg')
|
320 |
+
else:
|
321 |
+
return jsonify({'error': 'Image non trouvée'}), 404
|
322 |
+
except Exception as e:
|
323 |
+
logging.error(f"Error fetching image: {e}")
|
324 |
+
return jsonify({'error': 'Erreur lors de la récupération de l\'image'}), 500
|
325 |
|
326 |
@app.route('/analyze', methods=['POST'])
|
327 |
def analyze():
|
|
|
342 |
model_id = MODEL_ID_STANDARD
|
343 |
logging.info("Standard analysis requested, using standard model.")
|
344 |
|
345 |
+
# Get user IP
|
346 |
+
user_ip = request.remote_addr
|
347 |
+
|
348 |
+
# Track processing time
|
349 |
+
start_time = datetime.now()
|
350 |
+
|
351 |
# Use a temporary file to handle the image upload
|
352 |
temp_file = None # Initialize to None
|
353 |
try:
|
|
|
361 |
image.verify() # Verify image header
|
362 |
# Re-open after verify
|
363 |
image = Image.open(temp_file.name)
|
364 |
+
|
365 |
+
# Convert image to base64 for storage
|
366 |
+
img_buffer = io.BytesIO()
|
367 |
+
image.save(img_buffer, format='JPEG')
|
368 |
+
image_base64 = base64.b64encode(img_buffer.getvalue()).decode('utf-8')
|
369 |
+
|
370 |
except (IOError, SyntaxError) as e:
|
371 |
logging.error(f"Invalid image file uploaded: {e}")
|
372 |
return jsonify({'error': f'Invalid or corrupted image file: {e}'}), 400
|
|
|
379 |
def generate():
|
380 |
temp_file_path = temp_file.name # Store path for finally block
|
381 |
full_tableau_content = "" # Accumulate full table content for dissertation
|
382 |
+
full_dissertation_content = "" # Accumulate full dissertation content
|
383 |
+
|
384 |
try:
|
385 |
logging.info("Starting table generation stream...")
|
386 |
# Phase 1: Génération du tableau, passing the selected model_id
|
|
|
413 |
yield chunk_json # Forward the error
|
414 |
# Decide if you want to return here or let it finish
|
415 |
elif chunk_data.get("type") == "dissertation":
|
416 |
+
full_dissertation_content += chunk_data.get("chunk", "")
|
417 |
yield chunk_json # Stream chunk to client
|
418 |
except json.JSONDecodeError:
|
419 |
logging.error(f"Received invalid JSON from dissertation stream: {chunk_json}")
|
|
|
425 |
|
426 |
logging.info("Dissertation generation stream finished.")
|
427 |
|
428 |
+
# Save analysis data to database
|
429 |
+
try:
|
430 |
+
end_time = datetime.now()
|
431 |
+
processing_time = (end_time - start_time).total_seconds()
|
432 |
+
|
433 |
+
save_analysis_data(
|
434 |
+
image_name=image_file.filename,
|
435 |
+
image_data=image_base64,
|
436 |
+
consignes=consignes,
|
437 |
+
use_deepthink=use_deepthink,
|
438 |
+
model_used=model_id,
|
439 |
+
tableau_output=full_tableau_content,
|
440 |
+
dissertation_output=full_dissertation_content,
|
441 |
+
user_ip=user_ip,
|
442 |
+
processing_time=processing_time
|
443 |
+
)
|
444 |
+
logging.info(f"Analysis data saved successfully for image: {image_file.filename}")
|
445 |
+
except Exception as save_error:
|
446 |
+
logging.error(f"Error saving analysis data: {save_error}")
|
447 |
+
# Don't yield error to client as analysis was successful
|
448 |
+
|
449 |
except Exception as e:
|
450 |
logging.error(f"Error during streaming generation: {e}", exc_info=True)
|
451 |
# Yield a JSON error message for the client
|