File size: 9,163 Bytes
7204409
 
 
 
 
 
8a9498e
 
8795508
8a9498e
7204409
 
 
 
 
 
 
 
4b8f61c
7204409
 
 
4b8f61c
 
7204409
 
4b8f61c
7204409
 
 
 
 
 
 
 
275558c
4b8f61c
7204409
 
 
8a9498e
 
 
 
275558c
8a9498e
 
7204409
8a9498e
 
 
 
7204409
8a9498e
 
 
 
275558c
8a9498e
 
7204409
8a9498e
 
 
 
7204409
8a9498e
 
 
 
275558c
8a9498e
 
7204409
8a9498e
 
 
 
7204409
 
 
 
275558c
 
 
 
8a9498e
7204409
8a9498e
 
7204409
 
8a9498e
 
7204409
 
 
 
8a9498e
7204409
 
8a9498e
 
7204409
8a9498e
275558c
8a9498e
 
7204409
 
 
 
 
8a9498e
7204409
 
 
2dbb80d
 
 
 
 
 
8795508
2dbb80d
 
650e951
2dbb80d
 
8a9498e
7204409
 
8a9498e
7204409
 
8a9498e
 
7204409
8a9498e
650e951
8a9498e
 
 
 
 
7204409
 
650e951
275558c
8a9498e
7204409
 
 
 
 
 
 
 
8a9498e
 
7204409
 
 
8a9498e
 
275558c
7204409
 
 
 
 
8a9498e
 
7204409
 
 
 
 
68cc99d
7204409
 
 
68cc99d
 
 
7204409
68cc99d
 
7204409
 
 
 
 
8a9498e
 
 
 
 
275558c
8a9498e
 
7204409
 
 
 
8a9498e
 
7204409
8a9498e
7204409
8a9498e
7204409
1ade1bc
 
 
 
 
 
7204409
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
"""
callbacks.py
-------------
Ce module définit les callbacks de l'application Dash en utilisant les abstractions
définies dans le sous-package helpers."""

from dash import Input, Output, State, no_update
import datetime
import os
from pathlib import Path
from loguru import logger
from dotenv import load_dotenv

load_dotenv()

from helpers.processor import Processor
from helpers.s3 import S3Client
from helpers.models import S3Config

from app import app

from global_vars import data, BUCKET_NAME
PERSIST_FILE = "results.json"

# Instanciation du Processor
processor = Processor()

# Instanciation du client S3 à partir de la configuration
s3_config = S3Config(
    bucket_name=BUCKET_NAME,
    endpoint_url=os.getenv("AWS_ENDPOINT_URL_S3"),
    access_key=os.getenv("AWS_ACCESS_KEY_ID"),
    secret_key=os.getenv("AWS_SECRET_ACCESS_KEY")
)
s3_client = S3Client(s3_config)


# -----------------------------------------------------------------------------
# CALLBACKS D'AFFICHAGE DES SECTIONS
# -----------------------------------------------------------------------------
@app.callback(
    Output("chapter-section", "style"),
    Output("pseudo-continue-button", "style"),
    Input("pseudo-continue-button", "n_clicks"),
    State("user-info", "value")
)
def show_chapter_section(n_clicks, user_info):
    """Affiche la section chapitre après saisie d'une information utilisateur."""
    if n_clicks and user_info:
        return {"display": "block"}, {"display": "none"}
    return {"display": "none"}, {"display": "block"}


@app.callback(
    Output("page-section", "style"),
    Output("chapter-continue-button", "style"),
    Input("chapter-continue-button", "n_clicks"),
    State("chapter-dropdown", "value")
)
def show_page_section(n_clicks, chapter_value):
    """Affiche la section page après sélection d'un chapitre."""
    if n_clicks and chapter_value:
        return {"display": "block"}, {"display": "none"}
    return {"display": "none"}, {"display": "block"}


@app.callback(
    Output("transcription-section", "style"),
    Output("start-button", "style"),
    Input("start-button", "n_clicks"),
    State("page-dropdown", "value")
)
def show_transcription_section(n_clicks, page_value):
    """Affiche la section transcription après sélection d'une page."""
    if n_clicks and page_value:
        return {"display": "block"}, {"display": "none"}
    return {"display": "none"}, {"display": "block"}


# -----------------------------------------------------------------------------
# CALLBACK DE MISE À JOUR DU DROPDOWN DES PAGES
# -----------------------------------------------------------------------------
@app.callback(
    Output("page-dropdown", "options"),
    Input("chapter-dropdown", "value")
)
def update_pages(chapter_value):
    """Met à jour les options du dropdown de pages selon le chapitre sélectionné."""
    if chapter_value:
        chapter_path = Path(chapter_value)
        pages = [d for d in chapter_path.iterdir() if d.is_dir()] if chapter_path.exists() else []
        return [{"label": d.name, "value": str(d)} for d in pages]
    return []


# -----------------------------------------------------------------------------
# CALLBACK DE MISE À JOUR AUDIO ET DES SUGGESTIONS (via dcc.Store)
# -----------------------------------------------------------------------------
@app.callback(
    Output("audio-store", "data"),
    Output("values-store", "data"),
    Output("audio-player", "src"),
    Output("suggestion-checklist", "options"),
    Output("hidden-message", "style"),
    Input("page-dropdown", "value"),
    State("chapter-dropdown", "value")
)
def update_audio_and_suggestions(page_value, chapter_value):
    """
    Met à jour les stores pour les chemins audio et les suggestions.
    Affiche le premier segment audio et les 6 premières suggestions.
    """
    hidden_style = {"display": "none"}
    if page_value and chapter_value:
        # Utilise la méthode abstraite pour charger les transcriptions et extraire l'état
        possible_values, audio_paths = processor.load_page_verses_and_audios(s3_client, page_value, data)
        options = [{"label": t, "value": t} for t in possible_values[:6]]
        if len(audio_paths)>0: #control end of page
            audio_src =  audio_paths[0]
            return audio_paths, possible_values, audio_src, options, hidden_style
        
        hidden_style = {"display": "block"}  # Show the hidden message
        try:
            os.remove(PERSIST_FILE)
        except:
            pass

        return no_update, no_update, no_update, no_update, hidden_style                
    return no_update, no_update, no_update, no_update, hidden_style                

# CALLBACK POUR LE TRAITEMENT DE LA TRANSCIPTION
# -----------------------------------------------------------------------------
@app.callback(
    Output("audio-store", "data", allow_duplicate=True),
    Output("values-store", "data", allow_duplicate=True),
    Output("audio-player", "src", allow_duplicate=True),
    Output("suggestion-checklist", "options", allow_duplicate=True),
    Output("suggestion-checklist", "value", allow_duplicate=True),
    Output("confirmation-message", "children"),
    Output("transcription-store", "data"),
    Input("submit-button", "n_clicks"),
    State("suggestion-checklist", "value"),
    State("user-info", "value"),
    State("page-dropdown", "value"),
    State("audio-player", "src"),
    State("audio-store", "data"),
    State("values-store", "data"),
    State("transcription-store", "data"),
    prevent_initial_call=True
)
def update_transcription(n_clicks, selected_transcriptions, user_info, page_value,
                         current_audio, audio_store, values_store, stored_transcriptions):
    """
    Traite la soumission d'une transcription :
      - Ajoute l'entrée avec timestamp dans le store de transcription.
      - Retire le segment audio traité et les suggestions utilisées.
      - Met à jour l'audio et les options de la checklist.
    """
    if n_clicks > 0 and page_value and current_audio:
        timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        stored_transcriptions = stored_transcriptions if stored_transcriptions is not None else []
        stored_transcriptions.append({
            "segment_path": current_audio,
            "transcriptions": selected_transcriptions,
            "timestamp": timestamp,
            "user_id": user_info
        })
        # Mise à jour du store audio
        if audio_store and isinstance(audio_store, list):
            audio_store.pop(0)
            next_audio = audio_store[0] if audio_store else ""
        else:
            next_audio = ""
        # Mise à jour du store de suggestions
        if values_store and isinstance(values_store, list):
            for val in selected_transcriptions:
                if val in values_store:
                    values_store.remove(val)
        next_options = [{"label": t, "value": t} for t in (values_store[:6] + ["autre transcription"])] if values_store else ["autre transcription"]
        confirmation_message = (f"Transcriptions sélectionnées : {', '.join(selected_transcriptions)}"
                                if selected_transcriptions else "Aucune transcription sélectionnée.")
        # Réinitialisation de la checklist
        print(next_options)
        print("************")
        if (len(next_options)>1):
            return audio_store, values_store, next_audio, next_options, [], confirmation_message, stored_transcriptions
    
    return no_update, no_update, no_update, no_update, no_update, "fin de la page, veuillez sauvegarder", stored_transcriptions


# -----------------------------------------------------------------------------
# CALLBACK POUR LA SAUVEGARDE DES RÉSULTATS
# -----------------------------------------------------------------------------
@app.callback(
    Output("confirmation-message", "children", allow_duplicate=True),
    Input("save-results-button", "n_clicks"),
    State("page-dropdown", "value"),
    State("transcription-store", "data"),
    prevent_initial_call=True
)
def save_results(n_clicks, page_value, stored_transcriptions):
    """
    Sauvegarde les transcriptions en combinant les données persistantes existantes
    avec les nouvelles et en les uploadant sur S3.
    """
    if n_clicks > 0 and page_value:
        try:
            initial_transcriptions = processor.load_persistent_data(PERSIST_FILE)
        except Exception as e:
            logger.error(f"Erreur lors du chargement des données persistantes : {e}")
            initial_transcriptions = []
        combined_transcriptions = initial_transcriptions + (stored_transcriptions if stored_transcriptions else [])
        if len(combined_transcriptions)>0:
            processor.save_persistent_data(combined_transcriptions, PERSIST_FILE)
            cleaned_page = page_value.replace("\\", "/").replace("assets/", "")
            s3_key = f"labelling/{cleaned_page}/{PERSIST_FILE}"
            s3_client.upload_file(PERSIST_FILE, s3_key)
            return "Les résultats ont été sauvegardés avec succès."
    return no_update