AIdeaText commited on
Commit
a63fc5b
·
verified ·
1 Parent(s): 30862f0

Update modules/studentact/current_situation_interface.py

Browse files
modules/studentact/current_situation_interface.py CHANGED
@@ -1,436 +1,448 @@
1
- # modules/studentact/current_situation_interface.py
2
-
3
- import streamlit as st
4
- import logging
5
- from ..utils.widget_utils import generate_unique_key
6
- import matplotlib.pyplot as plt
7
- import numpy as np
8
- from ..database.current_situation_mongo_db import store_current_situation_result
9
-
10
- # Importaciones locales
11
- from translations import get_translations
12
-
13
- # Importamos la función de recomendaciones personalizadas si existe
14
- try:
15
- from .claude_recommendations import display_personalized_recommendations
16
- except ImportError:
17
- # Si no existe el módulo, definimos una función placeholder
18
- def display_personalized_recommendations(text, metrics, text_type, lang_code, t):
19
- # Obtener el mensaje de advertencia traducido si está disponible
20
- warning = t.get('module_not_available', "Módulo de recomendaciones personalizadas no disponible. Por favor, contacte al administrador.")
21
- st.warning(warning)
22
-
23
- from .current_situation_analysis import (
24
- analyze_text_dimensions,
25
- analyze_clarity,
26
- analyze_vocabulary_diversity,
27
- analyze_cohesion,
28
- analyze_structure,
29
- get_dependency_depths,
30
- normalize_score,
31
- generate_sentence_graphs,
32
- generate_word_connections,
33
- generate_connection_paths,
34
- create_vocabulary_network,
35
- create_syntax_complexity_graph,
36
- create_cohesion_heatmap
37
- )
38
-
39
- # Configuración del estilo de matplotlib para el gráfico de radar
40
- plt.rcParams['font.family'] = 'sans-serif'
41
- plt.rcParams['axes.grid'] = True
42
- plt.rcParams['axes.spines.top'] = False
43
- plt.rcParams['axes.spines.right'] = False
44
-
45
- logger = logging.getLogger(__name__)
46
-
47
- # Definición de tipos de texto con umbrales
48
- TEXT_TYPES = {
49
- 'academic_article': {
50
- # Los nombres se obtendrán de las traducciones
51
- 'thresholds': {
52
- 'vocabulary': {'min': 0.70, 'target': 0.85},
53
- 'structure': {'min': 0.75, 'target': 0.90},
54
- 'cohesion': {'min': 0.65, 'target': 0.80},
55
- 'clarity': {'min': 0.70, 'target': 0.85}
56
- }
57
- },
58
- 'student_essay': {
59
- 'thresholds': {
60
- 'vocabulary': {'min': 0.60, 'target': 0.75},
61
- 'structure': {'min': 0.65, 'target': 0.80},
62
- 'cohesion': {'min': 0.55, 'target': 0.70},
63
- 'clarity': {'min': 0.60, 'target': 0.75}
64
- }
65
- },
66
- 'general_communication': {
67
- 'thresholds': {
68
- 'vocabulary': {'min': 0.50, 'target': 0.65},
69
- 'structure': {'min': 0.55, 'target': 0.70},
70
- 'cohesion': {'min': 0.45, 'target': 0.60},
71
- 'clarity': {'min': 0.50, 'target': 0.65}
72
- }
73
- }
74
- }
75
-
76
- ####################################################
77
- ####################################################
78
- def display_current_situation_interface(lang_code, nlp_models, t):
79
- """
80
- Interfaz simplificada con gráfico de radar para visualizar métricas.
81
- """
82
- # Agregar logs para depuración
83
- logger.info(f"Idioma: {lang_code}")
84
- logger.info(f"Claves en t: {list(t.keys())}")
85
-
86
- # Inicializar estados si no existen
87
- if 'text_input' not in st.session_state:
88
- st.session_state.text_input = ""
89
- if 'text_area' not in st.session_state:
90
- st.session_state.text_area = ""
91
- if 'show_results' not in st.session_state:
92
- st.session_state.show_results = False
93
- if 'current_doc' not in st.session_state:
94
- st.session_state.current_doc = None
95
- if 'current_metrics' not in st.session_state:
96
- st.session_state.current_metrics = None
97
- if 'current_recommendations' not in st.session_state:
98
- st.session_state.current_recommendations = None
99
-
100
- try:
101
- # Container principal con dos columnas
102
- with st.container():
103
- input_col, results_col = st.columns([1,2])
104
-
105
- ###############################################################################################
106
- # CSS personalizado para que el formulario ocupe todo el alto disponible
107
- st.markdown("""
108
- <style>
109
- /* Hacer que la columna tenga una altura definida */
110
- [data-testid="column"] {
111
- min-height: 900px;
112
- height: 100vh; /* 100% del alto visible de la ventana */
113
- }
114
-
115
- /* Hacer que el formulario ocupe el espacio disponible en la columna */
116
- .stForm {
117
- height: calc(100% - 40px); /* Ajuste por márgenes y paddings */
118
- display: flex;
119
- flex-direction: column;
120
- }
121
-
122
- /* Hacer que el área de texto se expanda dentro del formulario */
123
- .stForm .stTextArea {
124
- flex: 1;
125
- display: flex;
126
- flex-direction: column;
127
- }
128
-
129
- /* El textarea en sí debe expandirse */
130
- .stForm .stTextArea textarea {
131
- flex: 1;
132
- min-height: 750px !important;
133
- }
134
- </style>
135
- """, unsafe_allow_html=True)
136
-
137
- ###############################################################################################
138
- with input_col:
139
- with st.form(key=f"text_input_form_{lang_code}"):
140
- text_input = st.text_area(
141
- t.get('input_prompt', "Escribe o pega tu texto aquí:"),
142
- height=800,
143
- key=f"text_area_{lang_code}",
144
- value=st.session_state.text_input,
145
- help=t.get('help', "Este texto será analizado para darte recomendaciones personalizadas")
146
- )
147
-
148
- submit_button = st.form_submit_button(
149
- t.get('analyze_button', "Analizar mi escritura"),
150
- type="primary",
151
- use_container_width=True
152
- )
153
-
154
- if submit_button:
155
- if text_input.strip():
156
- st.session_state.text_input = text_input
157
-
158
- #######################################################################
159
- # Código para análisis...
160
- try:
161
- with st.spinner(t.get('processing', "Analizando...")): # Usando t.get directamente
162
- doc = nlp_models[lang_code](text_input)
163
- metrics = analyze_text_dimensions(doc)
164
-
165
- storage_success = store_current_situation_result(
166
- username=st.session_state.username,
167
- text=text_input,
168
- metrics=metrics,
169
- feedback=None
170
- )
171
-
172
- if not storage_success:
173
- logger.warning("No se pudo guardar el análisis en la base de datos")
174
-
175
- st.session_state.current_doc = doc
176
- st.session_state.current_metrics = metrics
177
- st.session_state.show_results = True
178
-
179
- except Exception as e:
180
- logger.error(f"Error en análisis: {str(e)}")
181
- st.error(t.get('analysis_error', "Error al analizar el texto")) # Usando t.get directamente
182
-
183
- # Mostrar resultados en la columna derecha
184
- with results_col:
185
- if st.session_state.show_results and st.session_state.current_metrics is not None:
186
- # Primero los radio buttons para tipo de texto - usando t.get directamente
187
- st.markdown(f"### {t.get('text_type_header', 'Tipo de texto')}")
188
-
189
- # Preparar opciones de tipos de texto con nombres traducidos
190
- text_type_options = {}
191
- for text_type_key in TEXT_TYPES.keys():
192
- # Fallback a nombres genéricos si no hay traducción
193
- default_names = {
194
- 'academic_article': 'Academic Article' if lang_code == 'en' else 'Артикул академічний' if lang_code == 'uk' else 'Artículo Académico',
195
- 'student_essay': 'Student Essay' if lang_code == 'en' else 'Студентське есе' if lang_code == 'uk' else 'Trabajo Universitario',
196
- 'general_communication': 'General Communication' if lang_code == 'en' else 'Загальна комунікація' if lang_code == 'uk' else 'Comunicación General'
197
- }
198
- text_type_options[text_type_key] = default_names.get(text_type_key, text_type_key)
199
-
200
- text_type = st.radio(
201
- label=t.get('text_type_header', "Tipo de texto"), # Usando t.get directamente
202
- options=list(TEXT_TYPES.keys()),
203
- format_func=lambda x: text_type_options.get(x, x),
204
- horizontal=True,
205
- key="text_type_radio",
206
- label_visibility="collapsed",
207
- help=t.get('text_type_help', "Selecciona el tipo de texto para ajustar los criterios de evaluación") # Usando t.get directamente
208
- )
209
-
210
- st.session_state.current_text_type = text_type
211
-
212
- # Crear subtabs con nombres traducidos
213
- diagnosis_tab = "Diagnosis" if lang_code == 'en' else "Діагностика" if lang_code == 'uk' else "Diagnóstico"
214
- recommendations_tab = "Recommendations" if lang_code == 'en' else "Рекомендації" if lang_code == 'uk' else "Recomendaciones"
215
-
216
- subtab1, subtab2 = st.tabs([diagnosis_tab, recommendations_tab])
217
-
218
- # Mostrar resultados en el primer subtab
219
- with subtab1:
220
- display_diagnosis(
221
- metrics=st.session_state.current_metrics,
222
- text_type=text_type,
223
- lang_code=lang_code,
224
- t=t # Pasar t directamente, no current_situation_t
225
- )
226
-
227
- # Mostrar recomendaciones en el segundo subtab
228
- with subtab2:
229
- # Llamar directamente a la función de recomendaciones personalizadas
230
- display_personalized_recommendations(
231
- text=text_input,
232
- metrics=st.session_state.current_metrics,
233
- text_type=text_type,
234
- lang_code=lang_code,
235
- t=t
236
- )
237
-
238
- except Exception as e:
239
- logger.error(f"Error en interfaz principal: {str(e)}")
240
- st.error(t.get('error_interface', "Ocurrió un error al cargar la interfaz")) # Usando t.get directamente
241
-
242
- #################################################################
243
- #################################################################
244
- def display_diagnosis(metrics, text_type=None, lang_code='es', t=None):
245
- """
246
- Muestra los resultados del análisis: métricas verticalmente y gráfico radar.
247
- """
248
- try:
249
- # Asegurar que tenemos traducciones
250
- if t is None:
251
- t = {}
252
-
253
- # Traducciones para títulos y etiquetas
254
- dimension_labels = {
255
- 'es': {
256
- 'title': "Tipo de texto",
257
- 'vocabulary': "Vocabulario",
258
- 'structure': "Estructura",
259
- 'cohesion': "Cohesión",
260
- 'clarity': "Claridad",
261
- 'improvement': "⚠️ Por mejorar",
262
- 'acceptable': "📈 Aceptable",
263
- 'optimal': "✅ Óptimo",
264
- 'target': "Meta: {:.2f}"
265
- },
266
- 'en': {
267
- 'title': "Text Type",
268
- 'vocabulary': "Vocabulary",
269
- 'structure': "Structure",
270
- 'cohesion': "Cohesion",
271
- 'clarity': "Clarity",
272
- 'improvement': "⚠️ Needs improvement",
273
- 'acceptable': "📈 Acceptable",
274
- 'optimal': "✅ Optimal",
275
- 'target': "Target: {:.2f}"
276
- },
277
- 'uk': {
278
- 'title': "Тип тексту",
279
- 'vocabulary': "Словниковий запас",
280
- 'structure': "Структура",
281
- 'cohesion': "Зв'язність",
282
- 'clarity': "Ясність",
283
- 'improvement': "⚠️ Потребує покращення",
284
- 'acceptable': "📈 Прийнятно",
285
- 'optimal': "✅ Оптимально",
286
- 'target': "Ціль: {:.2f}"
287
- }
288
- }
289
-
290
- # Obtener traducciones para el idioma actual, con fallback a español
291
- labels = dimension_labels.get(lang_code, dimension_labels['es'])
292
-
293
- # Usar valor por defecto si no se especifica tipo
294
- text_type = text_type or 'student_essay'
295
-
296
- # Obtener umbrales según el tipo de texto
297
- thresholds = TEXT_TYPES[text_type]['thresholds']
298
-
299
- # Crear dos columnas para las métricas y el gráfico
300
- metrics_col, graph_col = st.columns([1, 1.5])
301
-
302
- # Columna de métricas
303
- with metrics_col:
304
- metrics_config = [
305
- {
306
- 'label': labels['vocabulary'],
307
- 'key': 'vocabulary',
308
- 'value': metrics['vocabulary']['normalized_score'],
309
- 'help': t.get('vocabulary_help', "Riqueza y variedad del vocabulario"),
310
- 'thresholds': thresholds['vocabulary']
311
- },
312
- {
313
- 'label': labels['structure'],
314
- 'key': 'structure',
315
- 'value': metrics['structure']['normalized_score'],
316
- 'help': t.get('structure_help', "Organización y complejidad de oraciones"),
317
- 'thresholds': thresholds['structure']
318
- },
319
- {
320
- 'label': labels['cohesion'],
321
- 'key': 'cohesion',
322
- 'value': metrics['cohesion']['normalized_score'],
323
- 'help': t.get('cohesion_help', "Conexión y fluidez entre ideas"),
324
- 'thresholds': thresholds['cohesion']
325
- },
326
- {
327
- 'label': labels['clarity'],
328
- 'key': 'clarity',
329
- 'value': metrics['clarity']['normalized_score'],
330
- 'help': t.get('clarity_help', "Facilidad de comprensión del texto"),
331
- 'thresholds': thresholds['clarity']
332
- }
333
- ]
334
-
335
- # Mostrar métricas con textos traducidos
336
- for metric in metrics_config:
337
- value = metric['value']
338
- if value < metric['thresholds']['min']:
339
- status = labels['improvement']
340
- color = "inverse"
341
- elif value < metric['thresholds']['target']:
342
- status = labels['acceptable']
343
- color = "off"
344
- else:
345
- status = labels['optimal']
346
- color = "normal"
347
-
348
- target_text = labels['target'].format(metric['thresholds']['target'])
349
-
350
- st.metric(
351
- metric['label'],
352
- f"{value:.2f}",
353
- f"{status} ({target_text})",
354
- delta_color=color,
355
- help=metric['help']
356
- )
357
- st.markdown("<div style='margin-bottom: 0.5rem;'></div>", unsafe_allow_html=True)
358
-
359
- # Gráfico radar en la columna derecha
360
- with graph_col:
361
- display_radar_chart(metrics_config, thresholds, lang_code) # Pasar el parámetro lang_code
362
-
363
- except Exception as e:
364
- logger.error(f"Error mostrando resultados: {str(e)}")
365
- st.error(t.get('error_results', "Error al mostrar los resultados"))
366
-
367
- ##################################################################
368
- ##################################################################
369
- def display_radar_chart(metrics_config, thresholds, lang_code='es'):
370
- """
371
- Muestra el gráfico radar con los resultados.
372
- """
373
- try:
374
- # Traducción de las etiquetas de leyenda según el idioma
375
- legend_translations = {
376
- 'es': {'min': 'Mínimo', 'target': 'Meta', 'user': 'Tu escritura'},
377
- 'en': {'min': 'Minimum', 'target': 'Target', 'user': 'Your writing'},
378
- 'uk': {'min': 'Мінімум', 'target': 'Ціль', 'user': 'Ваш текст'}
379
- }
380
-
381
- # Usar español por defecto si el idioma no está soportado
382
- translations = legend_translations.get(lang_code, legend_translations['es'])
383
-
384
- # Preparar datos para el gráfico
385
- categories = [m['label'] for m in metrics_config]
386
- values_user = [m['value'] for m in metrics_config]
387
- min_values = [m['thresholds']['min'] for m in metrics_config]
388
- target_values = [m['thresholds']['target'] for m in metrics_config]
389
-
390
- # Crear y configurar gráfico
391
- fig = plt.figure(figsize=(8, 8))
392
- ax = fig.add_subplot(111, projection='polar')
393
-
394
- # Configurar radar
395
- angles = [n / float(len(categories)) * 2 * np.pi for n in range(len(categories))]
396
- angles += angles[:1]
397
- values_user += values_user[:1]
398
- min_values += min_values[:1]
399
- target_values += target_values[:1]
400
-
401
- # Configurar ejes
402
- ax.set_xticks(angles[:-1])
403
- ax.set_xticklabels(categories, fontsize=10)
404
- circle_ticks = np.arange(0, 1.1, 0.2)
405
- ax.set_yticks(circle_ticks)
406
- ax.set_yticklabels([f'{tick:.1f}' for tick in circle_ticks], fontsize=8)
407
- ax.set_ylim(0, 1)
408
-
409
- # Dibujar áreas de umbrales con etiquetas traducidas
410
- ax.plot(angles, min_values, '#e74c3c', linestyle='--', linewidth=1, label=translations['min'], alpha=0.5)
411
- ax.plot(angles, target_values, '#2ecc71', linestyle='--', linewidth=1, label=translations['target'], alpha=0.5)
412
- ax.fill_between(angles, target_values, [1]*len(angles), color='#2ecc71', alpha=0.1)
413
- ax.fill_between(angles, [0]*len(angles), min_values, color='#e74c3c', alpha=0.1)
414
-
415
- # Dibujar valores del usuario con etiqueta traducida
416
- ax.plot(angles, values_user, '#3498db', linewidth=2, label=translations['user'])
417
- ax.fill(angles, values_user, '#3498db', alpha=0.2)
418
-
419
- # Ajustar leyenda
420
- ax.legend(
421
- loc='upper right',
422
- bbox_to_anchor=(1.3, 1.1),
423
- fontsize=10,
424
- frameon=True,
425
- facecolor='white',
426
- edgecolor='none',
427
- shadow=True
428
- )
429
-
430
- plt.tight_layout()
431
- st.pyplot(fig)
432
- plt.close()
433
-
434
- except Exception as e:
435
- logger.error(f"Error mostrando gráfico radar: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
436
  st.error("Error al mostrar el gráfico")
 
1
+ # modules/studentact/current_situation_interface.py
2
+
3
+ import streamlit as st
4
+ import logging
5
+ from ..utils.widget_utils import generate_unique_key
6
+ import matplotlib.pyplot as plt
7
+ import numpy as np
8
+ from ..database.current_situation_mongo_db import store_current_situation_result
9
+
10
+ # Importaciones locales
11
+ from translations import get_translations
12
+
13
+ # Importamos la función de recomendaciones personalizadas si existe
14
+ try:
15
+ from .claude_recommendations import display_personalized_recommendations
16
+ except ImportError:
17
+ # Si no existe el módulo, definimos una función placeholder
18
+ def display_personalized_recommendations(text, metrics, text_type, lang_code, t):
19
+ # Obtener el mensaje de advertencia traducido si está disponible
20
+ warning = t.get('module_not_available', "Módulo de recomendaciones personalizadas no disponible. Por favor, contacte al administrador.")
21
+ st.warning(warning)
22
+
23
+ from .current_situation_analysis import (
24
+ analyze_text_dimensions,
25
+ analyze_clarity,
26
+ analyze_vocabulary_diversity,
27
+ analyze_cohesion,
28
+ analyze_structure,
29
+ get_dependency_depths,
30
+ normalize_score,
31
+ generate_sentence_graphs,
32
+ generate_word_connections,
33
+ generate_connection_paths,
34
+ create_vocabulary_network,
35
+ create_syntax_complexity_graph,
36
+ create_cohesion_heatmap
37
+ )
38
+
39
+ # Configuración del estilo de matplotlib para el gráfico de radar
40
+ plt.rcParams['font.family'] = 'sans-serif'
41
+ plt.rcParams['axes.grid'] = True
42
+ plt.rcParams['axes.spines.top'] = False
43
+ plt.rcParams['axes.spines.right'] = False
44
+
45
+ logger = logging.getLogger(__name__)
46
+
47
+ # Definición de tipos de texto con umbrales
48
+ TEXT_TYPES = {
49
+ 'academic_article': {
50
+ # Los nombres se obtendrán de las traducciones
51
+ 'thresholds': {
52
+ 'vocabulary': {'min': 0.70, 'target': 0.85},
53
+ 'structure': {'min': 0.75, 'target': 0.90},
54
+ 'cohesion': {'min': 0.65, 'target': 0.80},
55
+ 'clarity': {'min': 0.70, 'target': 0.85}
56
+ }
57
+ },
58
+ 'student_essay': {
59
+ 'thresholds': {
60
+ 'vocabulary': {'min': 0.60, 'target': 0.75},
61
+ 'structure': {'min': 0.65, 'target': 0.80},
62
+ 'cohesion': {'min': 0.55, 'target': 0.70},
63
+ 'clarity': {'min': 0.60, 'target': 0.75}
64
+ }
65
+ },
66
+ 'general_communication': {
67
+ 'thresholds': {
68
+ 'vocabulary': {'min': 0.50, 'target': 0.65},
69
+ 'structure': {'min': 0.55, 'target': 0.70},
70
+ 'cohesion': {'min': 0.45, 'target': 0.60},
71
+ 'clarity': {'min': 0.50, 'target': 0.65}
72
+ }
73
+ }
74
+ }
75
+
76
+ ####################################################
77
+ ####################################################
78
+ def display_current_situation_interface(lang_code, nlp_models, t):
79
+ """
80
+ Interfaz simplificada con gráfico de radar para visualizar métricas.
81
+ """
82
+ # Agregar logs para depuración
83
+ logger.info(f"Idioma: {lang_code}")
84
+ logger.info(f"Claves en t: {list(t.keys())}")
85
+
86
+ # Inicializar estados si no existen
87
+ if 'text_input' not in st.session_state:
88
+ st.session_state.text_input = ""
89
+ if 'text_area' not in st.session_state:
90
+ st.session_state.text_area = ""
91
+ if 'show_results' not in st.session_state:
92
+ st.session_state.show_results = False
93
+ if 'current_doc' not in st.session_state:
94
+ st.session_state.current_doc = None
95
+ if 'current_metrics' not in st.session_state:
96
+ st.session_state.current_metrics = None
97
+ if 'current_recommendations' not in st.session_state:
98
+ st.session_state.current_recommendations = None
99
+
100
+ try:
101
+ # Container principal con dos columnas
102
+ with st.container():
103
+ input_col, results_col = st.columns([1,2])
104
+
105
+ ###############################################################################################
106
+ # CSS personalizado para que el formulario ocupe todo el alto disponible
107
+ st.markdown("""
108
+ <style>
109
+ /* Hacer que la columna tenga una altura definida */
110
+ [data-testid="column"] {
111
+ min-height: 900px;
112
+ height: 100vh; /* 100% del alto visible de la ventana */
113
+ }
114
+
115
+ /* Hacer que el formulario ocupe el espacio disponible en la columna */
116
+ .stForm {
117
+ height: calc(100% - 40px); /* Ajuste por márgenes y paddings */
118
+ display: flex;
119
+ flex-direction: column;
120
+ }
121
+
122
+ /* Hacer que el área de texto se expanda dentro del formulario */
123
+ .stForm .stTextArea {
124
+ flex: 1;
125
+ display: flex;
126
+ flex-direction: column;
127
+ }
128
+
129
+ /* El textarea en sí debe expandirse */
130
+ .stForm .stTextArea textarea {
131
+ flex: 1;
132
+ min-height: 750px !important;
133
+ }
134
+ </style>
135
+ """, unsafe_allow_html=True)
136
+
137
+ ###############################################################################################
138
+ with input_col:
139
+ with st.form(key=f"text_input_form_{lang_code}"):
140
+ text_input = st.text_area(
141
+ t.get('input_prompt', "Escribe o pega tu texto aquí:"),
142
+ height=800,
143
+ key=f"text_area_{lang_code}",
144
+ value=st.session_state.text_input,
145
+ help=t.get('help', "Este texto será analizado para darte recomendaciones personalizadas")
146
+ )
147
+
148
+ submit_button = st.form_submit_button(
149
+ t.get('analyze_button', "Analizar mi escritura"),
150
+ type="primary",
151
+ use_container_width=True
152
+ )
153
+
154
+ if submit_button:
155
+ if text_input.strip():
156
+ st.session_state.text_input = text_input
157
+
158
+ #######################################################################
159
+ # Código para análisis...
160
+ try:
161
+ with st.spinner(t.get('processing', "Analizando...")): # Usando t.get directamente
162
+ doc = nlp_models[lang_code](text_input)
163
+ metrics = analyze_text_dimensions(doc)
164
+
165
+ storage_success = store_current_situation_result(
166
+ username=st.session_state.username,
167
+ text=text_input,
168
+ metrics=metrics,
169
+ feedback=None
170
+ )
171
+
172
+ if not storage_success:
173
+ logger.warning("No se pudo guardar el análisis en la base de datos")
174
+
175
+ st.session_state.current_doc = doc
176
+ st.session_state.current_metrics = metrics
177
+ st.session_state.show_results = True
178
+
179
+ except Exception as e:
180
+ logger.error(f"Error en análisis: {str(e)}")
181
+ st.error(t.get('analysis_error', "Error al analizar el texto")) # Usando t.get directamente
182
+
183
+ # Mostrar resultados en la columna derecha
184
+ with results_col:
185
+ if st.session_state.show_results and st.session_state.current_metrics is not None:
186
+ # Primero los radio buttons para tipo de texto - usando t.get directamente
187
+ st.markdown(f"### {t.get('text_type_header', 'Tipo de texto')}")
188
+
189
+ # Preparar opciones de tipos de texto con nombres traducidos
190
+ text_type_options = {}
191
+ for text_type_key in TEXT_TYPES.keys():
192
+ # Fallback a nombres genéricos si no hay traducción
193
+ default_names = {
194
+ 'academic_article': 'Academic Article' if lang_code == 'en' else 'Article Académique' if lang_code == 'fr' else 'Artigo Acadêmico' if lang_code == 'pt' else 'Artículo Académico',
195
+ 'student_essay': 'Student Essay' if lang_code == 'en' else 'Devoir Universitaire' if lang_code == 'fr' else 'Trabalho Universitário' if lang_code == 'pt' else 'Trabajo Universitario',
196
+ 'general_communication': 'General Communication' if lang_code == 'en' else 'Communication Générale' if lang_code == 'fr' else 'Comunicação Geral' if lang_code == 'pt' else 'Comunicación General'
197
+ }
198
+ text_type_options[text_type_key] = default_names.get(text_type_key, text_type_key)
199
+
200
+ text_type = st.radio(
201
+ label=t.get('text_type_header', "Tipo de texto"), # Usando t.get directamente
202
+ options=list(TEXT_TYPES.keys()),
203
+ format_func=lambda x: text_type_options.get(x, x),
204
+ horizontal=True,
205
+ key="text_type_radio",
206
+ label_visibility="collapsed",
207
+ help=t.get('text_type_help', "Selecciona el tipo de texto para ajustar los criterios de evaluación") # Usando t.get directamente
208
+ )
209
+
210
+ st.session_state.current_text_type = text_type
211
+
212
+ # Crear subtabs con nombres traducidos
213
+ diagnosis_tab = "Diagnosis" if lang_code == 'en' else "Diagnostic" if lang_code == 'fr' else "Diagnóstico" if lang_code == 'pt' else "Diagnóstico"
214
+ recommendations_tab = "Recommendations" if lang_code == 'en' else "Recommandations" if lang_code == 'fr' else "Recomendações" if lang_code == 'pt' else "Recomendaciones"
215
+
216
+ subtab1, subtab2 = st.tabs([diagnosis_tab, recommendations_tab])
217
+
218
+ # Mostrar resultados en el primer subtab
219
+ with subtab1:
220
+ display_diagnosis(
221
+ metrics=st.session_state.current_metrics,
222
+ text_type=text_type,
223
+ lang_code=lang_code,
224
+ t=t # Pasar t directamente, no current_situation_t
225
+ )
226
+
227
+ # Mostrar recomendaciones en el segundo subtab
228
+ with subtab2:
229
+ # Llamar directamente a la función de recomendaciones personalizadas
230
+ display_personalized_recommendations(
231
+ text=text_input,
232
+ metrics=st.session_state.current_metrics,
233
+ text_type=text_type,
234
+ lang_code=lang_code,
235
+ t=t
236
+ )
237
+
238
+ except Exception as e:
239
+ logger.error(f"Error en interfaz principal: {str(e)}")
240
+ st.error(t.get('error_interface', "Ocurrió un error al cargar la interfaz")) # Usando t.get directamente
241
+
242
+ #################################################################
243
+ #################################################################
244
+ def display_diagnosis(metrics, text_type=None, lang_code='es', t=None):
245
+ """
246
+ Muestra los resultados del análisis: métricas verticalmente y gráfico radar.
247
+ """
248
+ try:
249
+ # Asegurar que tenemos traducciones
250
+ if t is None:
251
+ t = {}
252
+
253
+ # Traducciones para títulos y etiquetas
254
+ dimension_labels = {
255
+ 'es': {
256
+ 'title': "Tipo de texto",
257
+ 'vocabulary': "Vocabulario",
258
+ 'structure': "Estructura",
259
+ 'cohesion': "Cohesión",
260
+ 'clarity': "Claridad",
261
+ 'improvement': "⚠️ Por mejorar",
262
+ 'acceptable': "📈 Aceptable",
263
+ 'optimal': "✅ Óptimo",
264
+ 'target': "Meta: {:.2f}"
265
+ },
266
+ 'en': {
267
+ 'title': "Text Type",
268
+ 'vocabulary': "Vocabulary",
269
+ 'structure': "Structure",
270
+ 'cohesion': "Cohesion",
271
+ 'clarity': "Clarity",
272
+ 'improvement': "⚠️ Needs improvement",
273
+ 'acceptable': "📈 Acceptable",
274
+ 'optimal': "✅ Optimal",
275
+ 'target': "Target: {:.2f}"
276
+ },
277
+ 'fr': {
278
+ 'title': "Type de texte",
279
+ 'vocabulary': "Vocabulaire",
280
+ 'structure': "Structure",
281
+ 'cohesion': "Cohésion",
282
+ 'clarity': "Clarté",
283
+ 'improvement': "⚠️ À améliorer",
284
+ 'acceptable': "📈 Acceptable",
285
+ 'optimal': "✅ Optimal",
286
+ 'target': "Objectif: {:.2f}"
287
+ },
288
+ 'pt': {
289
+ 'title': "Tipo de texto",
290
+ 'vocabulary': "Vocabulário",
291
+ 'structure': "Estrutura",
292
+ 'cohesion': "Coesão",
293
+ 'clarity': "Clareza",
294
+ 'improvement': "⚠️ Precisa melhorar",
295
+ 'acceptable': "📈 Aceitável",
296
+ 'optimal': "✅ Ótimo",
297
+ 'target': "Meta: {:.2f}"
298
+ }
299
+ }
300
+
301
+ # Obtener traducciones para el idioma actual, con fallback a español
302
+ labels = dimension_labels.get(lang_code, dimension_labels['es'])
303
+
304
+ # Usar valor por defecto si no se especifica tipo
305
+ text_type = text_type or 'student_essay'
306
+
307
+ # Obtener umbrales según el tipo de texto
308
+ thresholds = TEXT_TYPES[text_type]['thresholds']
309
+
310
+ # Crear dos columnas para las métricas y el gráfico
311
+ metrics_col, graph_col = st.columns([1, 1.5])
312
+
313
+ # Columna de métricas
314
+ with metrics_col:
315
+ metrics_config = [
316
+ {
317
+ 'label': labels['vocabulary'],
318
+ 'key': 'vocabulary',
319
+ 'value': metrics['vocabulary']['normalized_score'],
320
+ 'help': t.get('vocabulary_help', "Riqueza y variedad del vocabulario"),
321
+ 'thresholds': thresholds['vocabulary']
322
+ },
323
+ {
324
+ 'label': labels['structure'],
325
+ 'key': 'structure',
326
+ 'value': metrics['structure']['normalized_score'],
327
+ 'help': t.get('structure_help', "Organización y complejidad de oraciones"),
328
+ 'thresholds': thresholds['structure']
329
+ },
330
+ {
331
+ 'label': labels['cohesion'],
332
+ 'key': 'cohesion',
333
+ 'value': metrics['cohesion']['normalized_score'],
334
+ 'help': t.get('cohesion_help', "Conexión y fluidez entre ideas"),
335
+ 'thresholds': thresholds['cohesion']
336
+ },
337
+ {
338
+ 'label': labels['clarity'],
339
+ 'key': 'clarity',
340
+ 'value': metrics['clarity']['normalized_score'],
341
+ 'help': t.get('clarity_help', "Facilidad de comprensión del texto"),
342
+ 'thresholds': thresholds['clarity']
343
+ }
344
+ ]
345
+
346
+ # Mostrar métricas con textos traducidos
347
+ for metric in metrics_config:
348
+ value = metric['value']
349
+ if value < metric['thresholds']['min']:
350
+ status = labels['improvement']
351
+ color = "inverse"
352
+ elif value < metric['thresholds']['target']:
353
+ status = labels['acceptable']
354
+ color = "off"
355
+ else:
356
+ status = labels['optimal']
357
+ color = "normal"
358
+
359
+ target_text = labels['target'].format(metric['thresholds']['target'])
360
+
361
+ st.metric(
362
+ metric['label'],
363
+ f"{value:.2f}",
364
+ f"{status} ({target_text})",
365
+ delta_color=color,
366
+ help=metric['help']
367
+ )
368
+ st.markdown("<div style='margin-bottom: 0.5rem;'></div>", unsafe_allow_html=True)
369
+
370
+ # Gráfico radar en la columna derecha
371
+ with graph_col:
372
+ display_radar_chart(metrics_config, thresholds, lang_code) # Pasar el parámetro lang_code
373
+
374
+ except Exception as e:
375
+ logger.error(f"Error mostrando resultados: {str(e)}")
376
+ st.error(t.get('error_results', "Error al mostrar los resultados"))
377
+
378
+ ##################################################################
379
+ ##################################################################
380
+ def display_radar_chart(metrics_config, thresholds, lang_code='es'):
381
+ """
382
+ Muestra el gráfico radar con los resultados.
383
+ """
384
+ try:
385
+ # Traducción de las etiquetas de leyenda según el idioma
386
+ legend_translations = {
387
+ 'es': {'min': 'Mínimo', 'target': 'Meta', 'user': 'Tu escritura'},
388
+ 'en': {'min': 'Minimum', 'target': 'Target', 'user': 'Your writing'},
389
+ 'fr': {'min': 'Minimum', 'target': 'Objectif', 'user': 'Votre écriture'},
390
+ 'pt': {'min': 'Mínimo', 'target': 'Meta', 'user': 'Sua escrita'}
391
+ }
392
+
393
+ # Usar español por defecto si el idioma no está soportado
394
+ translations = legend_translations.get(lang_code, legend_translations['es'])
395
+
396
+ # Preparar datos para el gráfico
397
+ categories = [m['label'] for m in metrics_config]
398
+ values_user = [m['value'] for m in metrics_config]
399
+ min_values = [m['thresholds']['min'] for m in metrics_config]
400
+ target_values = [m['thresholds']['target'] for m in metrics_config]
401
+
402
+ # Crear y configurar gráfico
403
+ fig = plt.figure(figsize=(8, 8))
404
+ ax = fig.add_subplot(111, projection='polar')
405
+
406
+ # Configurar radar
407
+ angles = [n / float(len(categories)) * 2 * np.pi for n in range(len(categories))]
408
+ angles += angles[:1]
409
+ values_user += values_user[:1]
410
+ min_values += min_values[:1]
411
+ target_values += target_values[:1]
412
+
413
+ # Configurar ejes
414
+ ax.set_xticks(angles[:-1])
415
+ ax.set_xticklabels(categories, fontsize=10)
416
+ circle_ticks = np.arange(0, 1.1, 0.2)
417
+ ax.set_yticks(circle_ticks)
418
+ ax.set_yticklabels([f'{tick:.1f}' for tick in circle_ticks], fontsize=8)
419
+ ax.set_ylim(0, 1)
420
+
421
+ # Dibujar áreas de umbrales con etiquetas traducidas
422
+ ax.plot(angles, min_values, '#e74c3c', linestyle='--', linewidth=1, label=translations['min'], alpha=0.5)
423
+ ax.plot(angles, target_values, '#2ecc71', linestyle='--', linewidth=1, label=translations['target'], alpha=0.5)
424
+ ax.fill_between(angles, target_values, [1]*len(angles), color='#2ecc71', alpha=0.1)
425
+ ax.fill_between(angles, [0]*len(angles), min_values, color='#e74c3c', alpha=0.1)
426
+
427
+ # Dibujar valores del usuario con etiqueta traducida
428
+ ax.plot(angles, values_user, '#3498db', linewidth=2, label=translations['user'])
429
+ ax.fill(angles, values_user, '#3498db', alpha=0.2)
430
+
431
+ # Ajustar leyenda
432
+ ax.legend(
433
+ loc='upper right',
434
+ bbox_to_anchor=(1.3, 1.1),
435
+ fontsize=10,
436
+ frameon=True,
437
+ facecolor='white',
438
+ edgecolor='none',
439
+ shadow=True
440
+ )
441
+
442
+ plt.tight_layout()
443
+ st.pyplot(fig)
444
+ plt.close()
445
+
446
+ except Exception as e:
447
+ logger.error(f"Error mostrando gráfico radar: {str(e)}")
448
  st.error("Error al mostrar el gráfico")