AIdeaText commited on
Commit
a96a5a5
·
verified ·
2 Parent(s): dbbd215 aa06d49

Merge branch #AIdeaText/v3' into 'AIdeaText/v4'

Browse files
modules/database/discourse_mongo_db.py CHANGED
@@ -1,7 +1,12 @@
1
  # modules/database/discourse_mongo_db.py
2
- import matplotlib.pyplot as plt # Añadir esta importación al inicio
3
  import io
4
  import base64
 
 
 
 
 
5
 
6
  from .mongo_db import (
7
  get_collection,
@@ -11,14 +16,11 @@ from .mongo_db import (
11
  delete_document
12
  )
13
 
14
- from datetime import datetime, timezone
15
-
16
- import logging
17
-
18
  logger = logging.getLogger(__name__)
19
-
20
  COLLECTION_NAME = 'student_discourse_analysis'
21
 
 
22
  def store_student_discourse_result(username, text1, text2, analysis_result):
23
  """
24
  Guarda el resultado del análisis de discurso comparativo en MongoDB.
@@ -120,10 +122,6 @@ def get_student_discourse_analysis(username, limit=10):
120
  logger.error(f"Error recuperando análisis del discurso: {str(e)}")
121
  return []
122
  #####################################################################################
123
-
124
-
125
-
126
-
127
 
128
  def get_student_discourse_data(username):
129
  """
@@ -149,6 +147,7 @@ def get_student_discourse_data(username):
149
  logger.error(f"Error al obtener datos del discurso: {str(e)}")
150
  return {'entries': []}
151
 
 
152
  def update_student_discourse_analysis(analysis_id, update_data):
153
  """
154
  Actualiza un análisis del discurso existente.
@@ -161,6 +160,7 @@ def update_student_discourse_analysis(analysis_id, update_data):
161
  logger.error(f"Error al actualizar análisis del discurso: {str(e)}")
162
  return False
163
 
 
164
  def delete_student_discourse_analysis(analysis_id):
165
  """
166
  Elimina un análisis del discurso.
 
1
  # modules/database/discourse_mongo_db.py
2
+ # Importaciones estándar
3
  import io
4
  import base64
5
+ from datetime import datetime, timezone
6
+ import logging
7
+
8
+ # Importaciones de terceros
9
+ import matplotlib.pyplot as plt
10
 
11
  from .mongo_db import (
12
  get_collection,
 
16
  delete_document
17
  )
18
 
19
+ # Configuración del logger
 
 
 
20
  logger = logging.getLogger(__name__)
 
21
  COLLECTION_NAME = 'student_discourse_analysis'
22
 
23
+ ########################################################################
24
  def store_student_discourse_result(username, text1, text2, analysis_result):
25
  """
26
  Guarda el resultado del análisis de discurso comparativo en MongoDB.
 
122
  logger.error(f"Error recuperando análisis del discurso: {str(e)}")
123
  return []
124
  #####################################################################################
 
 
 
 
125
 
126
  def get_student_discourse_data(username):
127
  """
 
147
  logger.error(f"Error al obtener datos del discurso: {str(e)}")
148
  return {'entries': []}
149
 
150
+ ###########################################################################
151
  def update_student_discourse_analysis(analysis_id, update_data):
152
  """
153
  Actualiza un análisis del discurso existente.
 
160
  logger.error(f"Error al actualizar análisis del discurso: {str(e)}")
161
  return False
162
 
163
+ ###########################################################################
164
  def delete_student_discourse_analysis(analysis_id):
165
  """
166
  Elimina un análisis del discurso.
modules/database/sql_db.py CHANGED
@@ -1,3 +1,5 @@
 
 
1
  from .database_init import get_sql_containers
2
  from datetime import datetime, timezone
3
  import logging
 
1
+ #modules/database/sql_db.py
2
+
3
  from .database_init import get_sql_containers
4
  from datetime import datetime, timezone
5
  import logging
modules/discourse/discourse_interface.py CHANGED
@@ -125,6 +125,10 @@ def display_discourse_interface(lang_code, nlp_models, discourse_t):
125
  logger.error(f"Error general en interfaz del discurso: {str(e)}")
126
  st.error(discourse_t.get('general_error', 'Se produjo un error. Por favor, intente de nuevo.'))
127
 
 
 
 
 
128
  def display_discourse_results(result, lang_code, discourse_t):
129
  """
130
  Muestra los resultados del análisis del discurso
@@ -133,39 +137,144 @@ def display_discourse_results(result, lang_code, discourse_t):
133
  st.warning(discourse_t.get('no_results', 'No hay resultados disponibles'))
134
  return
135
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  col1, col2 = st.columns(2)
137
 
138
  # Documento 1
139
  with col1:
140
- with st.expander(discourse_t.get('doc1_title', 'Documento 1'), expanded=True):
141
- st.subheader(discourse_t.get('key_concepts', 'Conceptos Clave'))
142
- if 'key_concepts1' in result:
143
- df1 = pd.DataFrame(result['key_concepts1'], columns=['Concepto', 'Frecuencia'])
144
- df1['Frecuencia'] = df1['Frecuencia'].round(2)
145
- st.table(df1)
146
-
147
- if 'graph1' in result:
148
- st.pyplot(result['graph1'])
149
- else:
150
- st.warning(discourse_t.get('graph_not_available', 'Gráfico no disponible'))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  else:
152
- st.warning(discourse_t.get('concepts_not_available', 'Conceptos no disponibles'))
 
 
153
 
154
  # Documento 2
155
  with col2:
156
- with st.expander(discourse_t.get('doc2_title', 'Documento 2'), expanded=True):
157
- st.subheader(discourse_t.get('key_concepts', 'Conceptos Clave'))
158
- if 'key_concepts2' in result:
159
- df2 = pd.DataFrame(result['key_concepts2'], columns=['Concepto', 'Frecuencia'])
160
- df2['Frecuencia'] = df2['Frecuencia'].round(2)
161
- st.table(df2)
162
-
163
- if 'graph2' in result:
164
- st.pyplot(result['graph2'])
165
- else:
166
- st.warning(discourse_t.get('graph_not_available', 'Gráfico no disponible'))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  else:
168
- st.warning(discourse_t.get('concepts_not_available', 'Conceptos no disponibles'))
 
 
169
 
170
  # Nota informativa sobre la comparación
171
  st.info(discourse_t.get('comparison_note',
 
125
  logger.error(f"Error general en interfaz del discurso: {str(e)}")
126
  st.error(discourse_t.get('general_error', 'Se produjo un error. Por favor, intente de nuevo.'))
127
 
128
+
129
+
130
+ #####################################################################################################################
131
+
132
  def display_discourse_results(result, lang_code, discourse_t):
133
  """
134
  Muestra los resultados del análisis del discurso
 
137
  st.warning(discourse_t.get('no_results', 'No hay resultados disponibles'))
138
  return
139
 
140
+ # Estilo CSS
141
+ st.markdown("""
142
+ <style>
143
+ .concepts-container {
144
+ display: flex;
145
+ flex-wrap: nowrap;
146
+ gap: 8px;
147
+ padding: 12px;
148
+ background-color: #f8f9fa;
149
+ border-radius: 8px;
150
+ overflow-x: auto;
151
+ margin-bottom: 15px;
152
+ white-space: nowrap;
153
+ }
154
+ .concept-item {
155
+ background-color: white;
156
+ border-radius: 4px;
157
+ padding: 6px 10px;
158
+ display: inline-flex;
159
+ align-items: center;
160
+ gap: 4px;
161
+ box-shadow: 0 1px 2px rgba(0,0,0,0.1);
162
+ flex-shrink: 0;
163
+ }
164
+ .concept-name {
165
+ font-weight: 500;
166
+ color: #1f2937;
167
+ font-size: 0.85em;
168
+ }
169
+ .concept-freq {
170
+ color: #6b7280;
171
+ font-size: 0.75em;
172
+ }
173
+ .graph-container {
174
+ background-color: white;
175
+ padding: 15px;
176
+ border-radius: 8px;
177
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
178
+ margin-top: 10px;
179
+ }
180
+ </style>
181
+ """, unsafe_allow_html=True)
182
+
183
  col1, col2 = st.columns(2)
184
 
185
  # Documento 1
186
  with col1:
187
+ st.subheader(discourse_t.get('doc1_title', 'Documento 1'))
188
+ st.markdown(discourse_t.get('key_concepts', 'Conceptos Clave'))
189
+ if 'key_concepts1' in result:
190
+ concepts_html = f"""
191
+ <div class="concepts-container">
192
+ {''.join([
193
+ f'<div class="concept-item"><span class="concept-name">{concept}</span>'
194
+ f'<span class="concept-freq">({freq:.2f})</span></div>'
195
+ for concept, freq in result['key_concepts1']
196
+ ])}
197
+ </div>
198
+ """
199
+ st.markdown(concepts_html, unsafe_allow_html=True)
200
+
201
+ if 'graph1' in result:
202
+ st.markdown('<div class="graph-container">', unsafe_allow_html=True)
203
+ st.pyplot(result['graph1'])
204
+
205
+ # Botones y controles
206
+ button_col1, spacer_col1 = st.columns([1,4])
207
+ with button_col1:
208
+ if 'graph1_bytes' in result:
209
+ st.download_button(
210
+ label="📥 " + discourse_t.get('download_graph', "Download"),
211
+ data=result['graph1_bytes'],
212
+ file_name="discourse_graph1.png",
213
+ mime="image/png",
214
+ use_container_width=True
215
+ )
216
+
217
+ # Interpretación como texto normal sin expander
218
+ st.markdown("**📊 Interpretación del grafo:**")
219
+ st.markdown("""
220
+ - 🔀 Las flechas indican la dirección de la relación entre conceptos
221
+ - 🎨 Los colores más intensos indican conceptos más centrales en el texto
222
+ - ⭕ El tamaño de los nodos representa la frecuencia del concepto
223
+ - ↔️ El grosor de las líneas indica la fuerza de la conexión
224
+ """)
225
+
226
+ st.markdown('</div>', unsafe_allow_html=True)
227
  else:
228
+ st.warning(discourse_t.get('graph_not_available', 'Gráfico no disponible'))
229
+ else:
230
+ st.warning(discourse_t.get('concepts_not_available', 'Conceptos no disponibles'))
231
 
232
  # Documento 2
233
  with col2:
234
+ st.subheader(discourse_t.get('doc2_title', 'Documento 2'))
235
+ st.markdown(discourse_t.get('key_concepts', 'Conceptos Clave'))
236
+ if 'key_concepts2' in result:
237
+ concepts_html = f"""
238
+ <div class="concepts-container">
239
+ {''.join([
240
+ f'<div class="concept-item"><span class="concept-name">{concept}</span>'
241
+ f'<span class="concept-freq">({freq:.2f})</span></div>'
242
+ for concept, freq in result['key_concepts2']
243
+ ])}
244
+ </div>
245
+ """
246
+ st.markdown(concepts_html, unsafe_allow_html=True)
247
+
248
+ if 'graph2' in result:
249
+ st.markdown('<div class="graph-container">', unsafe_allow_html=True)
250
+ st.pyplot(result['graph2'])
251
+
252
+ # Botones y controles
253
+ button_col2, spacer_col2 = st.columns([1,4])
254
+ with button_col2:
255
+ if 'graph2_bytes' in result:
256
+ st.download_button(
257
+ label="📥 " + discourse_t.get('download_graph', "Download"),
258
+ data=result['graph2_bytes'],
259
+ file_name="discourse_graph2.png",
260
+ mime="image/png",
261
+ use_container_width=True
262
+ )
263
+
264
+ # Interpretación como texto normal sin expander
265
+ st.markdown("**📊 Interpretación del grafo:**")
266
+ st.markdown("""
267
+ - 🔀 Las flechas indican la dirección de la relación entre conceptos
268
+ - 🎨 Los colores más intensos indican conceptos más centrales en el texto
269
+ - ⭕ El tamaño de los nodos representa la frecuencia del concepto
270
+ - ↔️ El grosor de las líneas indica la fuerza de la conexión
271
+ """)
272
+
273
+ st.markdown('</div>', unsafe_allow_html=True)
274
  else:
275
+ st.warning(discourse_t.get('graph_not_available', 'Gráfico no disponible'))
276
+ else:
277
+ st.warning(discourse_t.get('concepts_not_available', 'Conceptos no disponibles'))
278
 
279
  # Nota informativa sobre la comparación
280
  st.info(discourse_t.get('comparison_note',
modules/discourse/discourse_live_interface.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modules/discourse/discourse/discourse_live_interface.py
2
+
3
+ import streamlit as st
4
+ from streamlit_float import *
5
+ from streamlit_antd_components import *
6
+ import pandas as pd
7
+ import logging
8
+ import io
9
+ import matplotlib.pyplot as plt
10
+
11
+ # Configuración del logger
12
+ logger = logging.getLogger(__name__)
13
+
14
+ # Importaciones locales
15
+ from .discourse_process import perform_discourse_analysis
16
+ from .discourse_interface import display_discourse_results # Añadida esta importación
17
+ from ..utils.widget_utils import generate_unique_key
18
+ from ..database.discourse_mongo_db import store_student_discourse_result
19
+ from ..database.chat_mongo_db import store_chat_history, get_chat_history
20
+
21
+
22
+ #####################################################################################################
23
+ def fig_to_bytes(fig):
24
+ """Convierte una figura de matplotlib a bytes."""
25
+ try:
26
+ buf = io.BytesIO()
27
+ fig.savefig(buf, format='png', dpi=300, bbox_inches='tight')
28
+ buf.seek(0)
29
+ return buf.getvalue()
30
+ except Exception as e:
31
+ logger.error(f"Error en fig_to_bytes: {str(e)}")
32
+ return None
33
+
34
+ #################################################################################################
35
+ def display_discourse_live_interface(lang_code, nlp_models, discourse_t):
36
+ """
37
+ Interfaz para el análisis del discurso en vivo con layout mejorado
38
+ """
39
+ try:
40
+ if 'discourse_live_state' not in st.session_state:
41
+ st.session_state.discourse_live_state = {
42
+ 'analysis_count': 0,
43
+ 'current_text1': '',
44
+ 'current_text2': '',
45
+ 'last_result': None,
46
+ 'text_changed': False
47
+ }
48
+
49
+ # Título
50
+ st.subheader(discourse_t.get('enter_text', 'Ingrese sus textos'))
51
+
52
+ # Área de entrada de textos en dos columnas
53
+ text_col1, text_col2 = st.columns(2)
54
+
55
+ # Texto 1
56
+ with text_col1:
57
+ st.markdown("**Texto 1 (Patrón)**")
58
+ text_input1 = st.text_area(
59
+ "Texto 1",
60
+ height=200,
61
+ key="discourse_live_text1",
62
+ value=st.session_state.discourse_live_state.get('current_text1', ''),
63
+ label_visibility="collapsed"
64
+ )
65
+ st.session_state.discourse_live_state['current_text1'] = text_input1
66
+
67
+ # Texto 2
68
+ with text_col2:
69
+ st.markdown("**Texto 2 (Comparación)**")
70
+ text_input2 = st.text_area(
71
+ "Texto 2",
72
+ height=200,
73
+ key="discourse_live_text2",
74
+ value=st.session_state.discourse_live_state.get('current_text2', ''),
75
+ label_visibility="collapsed"
76
+ )
77
+ st.session_state.discourse_live_state['current_text2'] = text_input2
78
+
79
+ # Botón de análisis centrado
80
+ col1, col2, col3 = st.columns([1,2,1])
81
+ with col1:
82
+ analyze_button = st.button(
83
+ discourse_t.get('analyze_button', 'Analizar'),
84
+ key="discourse_live_analyze",
85
+ type="primary",
86
+ icon="🔍",
87
+ disabled=not (text_input1 and text_input2),
88
+ use_container_width=True
89
+ )
90
+
91
+ # Proceso y visualización de resultados
92
+ if analyze_button and text_input1 and text_input2:
93
+ try:
94
+ with st.spinner(discourse_t.get('processing', 'Procesando...')):
95
+ result = perform_discourse_analysis(
96
+ text_input1,
97
+ text_input2,
98
+ nlp_models[lang_code],
99
+ lang_code
100
+ )
101
+
102
+ if result['success']:
103
+ # Procesar ambos gráficos
104
+ for graph_key in ['graph1', 'graph2']:
105
+ if graph_key in result and result[graph_key] is not None:
106
+ bytes_key = f'{graph_key}_bytes'
107
+ graph_bytes = fig_to_bytes(result[graph_key])
108
+ if graph_bytes:
109
+ result[bytes_key] = graph_bytes
110
+ plt.close(result[graph_key])
111
+
112
+ st.session_state.discourse_live_state['last_result'] = result
113
+ st.session_state.discourse_live_state['analysis_count'] += 1
114
+
115
+ store_student_discourse_result(
116
+ st.session_state.username,
117
+ text_input1,
118
+ text_input2,
119
+ result
120
+ )
121
+
122
+ # Mostrar resultados
123
+ st.markdown("---")
124
+ st.subheader(discourse_t.get('results_title', 'Resultados del Análisis'))
125
+ display_discourse_results(result, lang_code, discourse_t)
126
+
127
+ else:
128
+ st.error(result.get('message', 'Error en el análisis'))
129
+
130
+ except Exception as e:
131
+ logger.error(f"Error en análisis: {str(e)}")
132
+ st.error(discourse_t.get('error_processing', f'Error al procesar el texto: {str(e)}'))
133
+
134
+ # Mostrar resultados previos si existen
135
+ elif 'last_result' in st.session_state.discourse_live_state and \
136
+ st.session_state.discourse_live_state['last_result'] is not None:
137
+
138
+ st.markdown("---")
139
+ st.subheader(discourse_t.get('previous_results', 'Resultados del Análisis Anterior'))
140
+ display_discourse_results(
141
+ st.session_state.discourse_live_state['last_result'],
142
+ lang_code,
143
+ discourse_t
144
+ )
145
+
146
+ except Exception as e:
147
+ logger.error(f"Error general en interfaz del discurso en vivo: {str(e)}")
148
+ st.error(discourse_t.get('general_error', "Se produjo un error. Por favor, intente de nuevo."))
149
+
150
+
151
+
modules/semantic/semantic_interface.py CHANGED
@@ -143,62 +143,135 @@ def display_semantic_results(semantic_result, lang_code, semantic_t):
143
  """
144
  Muestra los resultados del análisis semántico de conceptos clave.
145
  """
146
- # Verificar resultado
147
  if semantic_result is None or not semantic_result['success']:
148
  st.warning(semantic_t.get('no_results', 'No results available'))
149
  return
150
 
151
  analysis = semantic_result['analysis']
152
 
153
- # Crear contenedor para los resultados con proporciones ajustadas
154
- col1, col2 = st.columns([1, 2]) # Cambio de [1, 1] a [1, 2] para dar más espacio al grafo
155
-
156
- # Columna 1: Lista de conceptos clave
157
- with col1:
158
- st.subheader(semantic_t.get('key_concepts', 'Key Concepts'))
159
- if 'key_concepts' in analysis and analysis['key_concepts']:
160
- # Crear tabla de conceptos
161
- df = pd.DataFrame(
162
- analysis['key_concepts'],
163
- columns=[
164
- semantic_t.get('concept', 'Concept'),
165
- semantic_t.get('frequency', 'Frequency')
166
- ]
167
- )
168
- st.dataframe(
169
- df,
170
- hide_index=True,
171
- column_config={
172
- semantic_t.get('frequency', 'Frequency'): st.column_config.NumberColumn(
173
- format="%.2f"
174
- )
175
- },
176
- height=400 # Añadido para dar más altura a la tabla
177
- )
178
- else:
179
- st.info(semantic_t.get('no_concepts', 'No key concepts found'))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
- # Columna 2: Gráfico de conceptos
182
- with col2:
183
- st.subheader(semantic_t.get('concept_graph', 'Concepts Graph'))
184
- if 'concept_graph' in analysis and analysis['concept_graph'] is not None:
185
- # Contenedor para centrar la imagen
186
  st.markdown(
187
  """
188
  <style>
189
- .stImage > img {
190
- max-width: 100%;
191
- display: block;
192
- margin: 0 auto;
193
- }
 
 
 
 
 
 
 
194
  </style>
195
- """,
196
  unsafe_allow_html=True
197
  )
198
- st.image(analysis['concept_graph'], use_column_width=True)
199
- else:
200
- st.info(semantic_t.get('no_graph', 'No concept graph available'))
201
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  '''
203
  # Botón de exportación al final
204
  if 'semantic_analysis_counter' in st.session_state:
 
143
  """
144
  Muestra los resultados del análisis semántico de conceptos clave.
145
  """
 
146
  if semantic_result is None or not semantic_result['success']:
147
  st.warning(semantic_t.get('no_results', 'No results available'))
148
  return
149
 
150
  analysis = semantic_result['analysis']
151
 
152
+ # Mostrar conceptos clave en formato horizontal
153
+ st.subheader(semantic_t.get('key_concepts', 'Key Concepts'))
154
+ if 'key_concepts' in analysis and analysis['key_concepts']:
155
+ # Crear tabla de conceptos
156
+ df = pd.DataFrame(
157
+ analysis['key_concepts'],
158
+ columns=[
159
+ semantic_t.get('concept', 'Concept'),
160
+ semantic_t.get('frequency', 'Frequency')
161
+ ]
162
+ )
163
+
164
+ # Convertir DataFrame a formato horizontal
165
+ st.write(
166
+ """
167
+ <style>
168
+ .concept-table {
169
+ display: flex;
170
+ flex-wrap: wrap;
171
+ gap: 10px;
172
+ margin-bottom: 20px;
173
+ }
174
+ .concept-item {
175
+ background-color: #f0f2f6;
176
+ border-radius: 5px;
177
+ padding: 8px 12px;
178
+ display: flex;
179
+ align-items: center;
180
+ gap: 8px;
181
+ }
182
+ .concept-name {
183
+ font-weight: bold;
184
+ }
185
+ .concept-freq {
186
+ color: #666;
187
+ font-size: 0.9em;
188
+ }
189
+ </style>
190
+ <div class="concept-table">
191
+ """ +
192
+ ''.join([
193
+ f'<div class="concept-item"><span class="concept-name">{concept}</span>'
194
+ f'<span class="concept-freq">({freq:.2f})</span></div>'
195
+ for concept, freq in df.values
196
+ ]) +
197
+ "</div>",
198
+ unsafe_allow_html=True
199
+ )
200
+ else:
201
+ st.info(semantic_t.get('no_concepts', 'No key concepts found'))
202
 
203
+ # Gráfico de conceptos
204
+ st.subheader(semantic_t.get('concept_graph', 'Concepts Graph'))
205
+ if 'concept_graph' in analysis and analysis['concept_graph'] is not None:
206
+ try:
207
+ # Container para el grafo con estilos mejorados
208
  st.markdown(
209
  """
210
  <style>
211
+ .graph-container {
212
+ background-color: white;
213
+ border-radius: 10px;
214
+ padding: 20px;
215
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
216
+ margin: 10px 0;
217
+ }
218
+ .button-container {
219
+ display: flex;
220
+ gap: 10px;
221
+ margin: 10px 0;
222
+ }
223
  </style>
224
+ """,
225
  unsafe_allow_html=True
226
  )
 
 
 
227
 
228
+ with st.container():
229
+ st.markdown('<div class="graph-container">', unsafe_allow_html=True)
230
+
231
+ # Mostrar grafo
232
+ graph_bytes = analysis['concept_graph']
233
+ graph_base64 = base64.b64encode(graph_bytes).decode()
234
+ st.markdown(
235
+ f'<img src="data:image/png;base64,{graph_base64}" alt="Concept Graph" style="width:100%;"/>',
236
+ unsafe_allow_html=True
237
+ )
238
+
239
+ # Leyenda del grafo
240
+ st.caption(semantic_t.get(
241
+ 'graph_description',
242
+ 'Visualización de relaciones entre conceptos clave identificados en el texto.'
243
+ ))
244
+
245
+ st.markdown('</div>', unsafe_allow_html=True)
246
+
247
+ # Contenedor para botones
248
+ col1, col2 = st.columns([1,4])
249
+ with col1:
250
+ st.download_button(
251
+ label="📥 " + semantic_t.get('download_graph', "Download"),
252
+ data=graph_bytes,
253
+ file_name="semantic_graph.png",
254
+ mime="image/png",
255
+ use_container_width=True
256
+ )
257
+
258
+ # Expandible con la interpretación
259
+ with st.expander("📊 " + semantic_t.get('graph_help', "Graph Interpretation")):
260
+ st.markdown("""
261
+ - 🔀 Las flechas indican la dirección de la relación entre conceptos
262
+ - 🎨 Los colores más intensos indican conceptos más centrales en el texto
263
+ - ⭕ El tamaño de los nodos representa la frecuencia del concepto
264
+ - ↔️ El grosor de las líneas indica la fuerza de la conexión
265
+ """)
266
+
267
+ except Exception as e:
268
+ logger.error(f"Error displaying graph: {str(e)}")
269
+ st.error(semantic_t.get('graph_error', 'Error displaying the graph'))
270
+ else:
271
+ st.info(semantic_t.get('no_graph', 'No concept graph available'))
272
+
273
+
274
+ ########################################################################################
275
  '''
276
  # Botón de exportación al final
277
  if 'semantic_analysis_counter' in st.session_state:
modules/semantic/semantic_live_interface.py CHANGED
@@ -20,54 +20,54 @@ from ..database.chat_mongo_db import store_chat_history, get_chat_history
20
 
21
  def display_semantic_live_interface(lang_code, nlp_models, semantic_t):
22
  """
23
- Interfaz para el análisis semántico en vivo
24
- Args:
25
- lang_code: Código del idioma actual
26
- nlp_models: Modelos de spaCy cargados
27
- semantic_t: Diccionario de traducciones semánticas
28
  """
29
  try:
30
- # 1. Inicializar el estado de la sesión para el análisis en vivo
31
  if 'semantic_live_state' not in st.session_state:
32
  st.session_state.semantic_live_state = {
33
  'analysis_count': 0,
34
- 'last_analysis': None,
35
- 'current_text': ''
 
36
  }
37
 
38
- # 2. Crear dos columnas
39
- col1, col2 = st.columns(2)
 
 
 
 
 
 
40
 
41
  # Columna izquierda: Entrada de texto
42
- with col1:
43
  st.subheader(semantic_t.get('enter_text', 'Ingrese su texto'))
44
 
45
- # Área de texto para input
46
  text_input = st.text_area(
47
  semantic_t.get('text_input_label', 'Escriba o pegue su texto aquí'),
48
- height=400,
49
- key=f"semantic_live_text_{st.session_state.semantic_live_state['analysis_count']}"
 
 
 
50
  )
51
 
52
- # Botón de análisis
53
  analyze_button = st.button(
54
  semantic_t.get('analyze_button', 'Analizar'),
55
- key=f"semantic_live_analyze_{st.session_state.semantic_live_state['analysis_count']}",
56
  type="primary",
57
  icon="🔍",
58
  disabled=not text_input,
59
  use_container_width=True
60
  )
61
 
62
- # Columna derecha: Visualización de resultados
63
- with col2:
64
- st.subheader(semantic_t.get('live_results', 'Resultados en vivo'))
65
-
66
- # Procesar análisis cuando se presiona el botón
67
  if analyze_button and text_input:
68
  try:
69
  with st.spinner(semantic_t.get('processing', 'Procesando...')):
70
- # Realizar análisis
71
  analysis_result = process_semantic_input(
72
  text_input,
73
  lang_code,
@@ -76,49 +76,122 @@ def display_semantic_live_interface(lang_code, nlp_models, semantic_t):
76
  )
77
 
78
  if analysis_result['success']:
79
- # Guardar resultado
80
- st.session_state.semantic_live_result = analysis_result
81
  st.session_state.semantic_live_state['analysis_count'] += 1
 
82
 
83
- # Guardar en base de datos
84
  store_student_semantic_result(
85
  st.session_state.username,
86
  text_input,
87
  analysis_result['analysis']
88
  )
89
-
90
- # Mostrar gráfico de conceptos
91
- if 'concept_graph' in analysis_result['analysis'] and analysis_result['analysis']['concept_graph'] is not None:
92
- st.image(analysis_result['analysis']['concept_graph'])
93
- else:
94
- st.info(semantic_t.get('no_graph', 'No hay gráfico disponible'))
95
-
96
- # Mostrar tabla de conceptos clave
97
- if 'key_concepts' in analysis_result['analysis'] and analysis_result['analysis']['key_concepts']:
98
- st.subheader(semantic_t.get('key_concepts', 'Conceptos Clave'))
99
- df = pd.DataFrame(
100
- analysis_result['analysis']['key_concepts'],
101
- columns=[
102
- semantic_t.get('concept', 'Concepto'),
103
- semantic_t.get('frequency', 'Frecuencia')
104
- ]
105
- )
106
- st.dataframe(
107
- df,
108
- hide_index=True,
109
- column_config={
110
- semantic_t.get('frequency', 'Frecuencia'): st.column_config.NumberColumn(
111
- format="%.2f"
112
- )
113
- }
114
- )
115
  else:
116
- st.error(analysis_result['message'])
117
 
118
  except Exception as e:
119
- logger.error(f"Error en análisis semántico en vivo: {str(e)}")
120
- st.error(semantic_t.get('error_processing', f'Error al procesar el texto: {str(e)}'))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
  except Exception as e:
123
  logger.error(f"Error general en interfaz semántica en vivo: {str(e)}")
124
- st.error(semantic_t.get('general_error', "Se produjo un error. Por favor, intente de nuevo."))
 
 
20
 
21
  def display_semantic_live_interface(lang_code, nlp_models, semantic_t):
22
  """
23
+ Interfaz para el análisis semántico en vivo con proporciones de columna ajustadas
 
 
 
 
24
  """
25
  try:
26
+ # 1. Inicializar el estado de la sesión de manera más robusta
27
  if 'semantic_live_state' not in st.session_state:
28
  st.session_state.semantic_live_state = {
29
  'analysis_count': 0,
30
+ 'current_text': '',
31
+ 'last_result': None,
32
+ 'text_changed': False
33
  }
34
 
35
+ # 2. Función para manejar cambios en el texto
36
+ def on_text_change():
37
+ current_text = st.session_state.semantic_live_text
38
+ st.session_state.semantic_live_state['current_text'] = current_text
39
+ st.session_state.semantic_live_state['text_changed'] = True
40
+
41
+ # 3. Crear columnas con nueva proporción (1:3)
42
+ input_col, result_col = st.columns([1, 3])
43
 
44
  # Columna izquierda: Entrada de texto
45
+ with input_col:
46
  st.subheader(semantic_t.get('enter_text', 'Ingrese su texto'))
47
 
48
+ # Área de texto con manejo de eventos
49
  text_input = st.text_area(
50
  semantic_t.get('text_input_label', 'Escriba o pegue su texto aquí'),
51
+ height=500,
52
+ key="semantic_live_text",
53
+ value=st.session_state.semantic_live_state.get('current_text', ''),
54
+ on_change=on_text_change,
55
+ label_visibility="collapsed" # Oculta el label para mayor estabilidad
56
  )
57
 
58
+ # Botón de análisis y procesamiento
59
  analyze_button = st.button(
60
  semantic_t.get('analyze_button', 'Analizar'),
61
+ key="semantic_live_analyze",
62
  type="primary",
63
  icon="🔍",
64
  disabled=not text_input,
65
  use_container_width=True
66
  )
67
 
 
 
 
 
 
68
  if analyze_button and text_input:
69
  try:
70
  with st.spinner(semantic_t.get('processing', 'Procesando...')):
 
71
  analysis_result = process_semantic_input(
72
  text_input,
73
  lang_code,
 
76
  )
77
 
78
  if analysis_result['success']:
79
+ st.session_state.semantic_live_state['last_result'] = analysis_result
 
80
  st.session_state.semantic_live_state['analysis_count'] += 1
81
+ st.session_state.semantic_live_state['text_changed'] = False
82
 
 
83
  store_student_semantic_result(
84
  st.session_state.username,
85
  text_input,
86
  analysis_result['analysis']
87
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  else:
89
+ st.error(analysis_result.get('message', 'Error en el análisis'))
90
 
91
  except Exception as e:
92
+ logger.error(f"Error en análisis: {str(e)}")
93
+ st.error(semantic_t.get('error_processing', 'Error al procesar el texto'))
94
+
95
+ # Columna derecha: Visualización de resultados
96
+ with result_col:
97
+ st.subheader(semantic_t.get('live_results', 'Resultados en vivo'))
98
+
99
+ if 'last_result' in st.session_state.semantic_live_state and \
100
+ st.session_state.semantic_live_state['last_result'] is not None:
101
+
102
+ analysis = st.session_state.semantic_live_state['last_result']['analysis']
103
+
104
+ if 'key_concepts' in analysis and analysis['key_concepts'] and \
105
+ 'concept_graph' in analysis and analysis['concept_graph'] is not None:
106
+
107
+ st.markdown("""
108
+ <style>
109
+ .unified-container {
110
+ background-color: white;
111
+ border-radius: 10px;
112
+ overflow: hidden;
113
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
114
+ width: 100%;
115
+ margin-bottom: 1rem;
116
+ }
117
+ .concept-table {
118
+ display: flex;
119
+ flex-wrap: nowrap; /* Evita el wrap */
120
+ gap: 6px; /* Reducido el gap */
121
+ padding: 10px;
122
+ background-color: #f8f9fa;
123
+ overflow-x: auto; /* Permite scroll horizontal si es necesario */
124
+ white-space: nowrap; /* Mantiene todo en una línea */
125
+ }
126
+ .concept-item {
127
+ background-color: white;
128
+ border-radius: 4px;
129
+ padding: 4px 8px; /* Padding reducido */
130
+ display: inline-flex; /* Cambiado a inline-flex */
131
+ align-items: center;
132
+ gap: 4px; /* Gap reducido */
133
+ box-shadow: 0 1px 2px rgba(0,0,0,0.1);
134
+ flex-shrink: 0; /* Evita que los items se encojan */
135
+ }
136
+ .concept-name {
137
+ font-weight: 500;
138
+ color: #1f2937;
139
+ font-size: 0.8em; /* Tamaño de fuente reducido */
140
+ }
141
+ .concept-freq {
142
+ color: #6b7280;
143
+ font-size: 0.75em; /* Tamaño de fuente reducido */
144
+ }
145
+ .graph-section {
146
+ padding: 20px;
147
+ background-color: white;
148
+ }
149
+ </style>
150
+ """, unsafe_allow_html=True)
151
+
152
+ with st.container():
153
+ # Conceptos en una sola línea
154
+ concepts_html = """
155
+ <div class="unified-container">
156
+ <div class="concept-table">
157
+ """
158
+ concepts_html += ''.join(
159
+ f'<div class="concept-item"><span class="concept-name">{concept}</span>'
160
+ f'<span class="concept-freq">({freq:.2f})</span></div>'
161
+ for concept, freq in analysis['key_concepts']
162
+ )
163
+ concepts_html += "</div></div>"
164
+ st.markdown(concepts_html, unsafe_allow_html=True)
165
+
166
+ # Grafo
167
+ if 'concept_graph' in analysis and analysis['concept_graph'] is not None:
168
+ st.image(
169
+ analysis['concept_graph'],
170
+ use_container_width=True
171
+ )
172
+
173
+ # Botones y controles
174
+ button_col, spacer_col = st.columns([1,5])
175
+ with button_col:
176
+ st.download_button(
177
+ label="📥 " + semantic_t.get('download_graph', "Download"),
178
+ data=analysis['concept_graph'],
179
+ file_name="semantic_live_graph.png",
180
+ mime="image/png",
181
+ use_container_width=True
182
+ )
183
+
184
+ with st.expander("📊 " + semantic_t.get('graph_help', "Graph Interpretation")):
185
+ st.markdown("""
186
+ - 🔀 Las flechas indican la dirección de la relación entre conceptos
187
+ - 🎨 Los colores más intensos indican conceptos más centrales en el texto
188
+ - ⭕ El tamaño de los nodos representa la frecuencia del concepto
189
+ - ↔️ El grosor de las líneas indica la fuerza de la conexión
190
+ """)
191
+ else:
192
+ st.info(semantic_t.get('no_graph', 'No hay datos para mostrar'))
193
 
194
  except Exception as e:
195
  logger.error(f"Error general en interfaz semántica en vivo: {str(e)}")
196
+ st.error(semantic_t.get('general_error', "Se produjo un error. Por favor, intente de nuevo."))
197
+
modules/text_analysis/semantic_analysis.py CHANGED
@@ -20,6 +20,7 @@ logger = logging.getLogger(__name__)
20
  # 4. Importaciones locales
21
  from .stopwords import (
22
  process_text,
 
23
  get_custom_stopwords,
24
  get_stopwords_for_spacy
25
  )
@@ -182,25 +183,48 @@ def perform_semantic_analysis(text, nlp, lang_code):
182
 
183
  def identify_key_concepts(doc, stopwords, min_freq=2, min_length=3):
184
  """
185
- Identifica conceptos clave en el texto.
 
 
 
 
 
 
 
186
  """
187
  try:
188
  word_freq = Counter()
189
 
 
 
 
 
 
 
190
  for token in doc:
191
- if (token.lemma_.lower() not in stopwords and
192
- len(token.lemma_) >= min_length and
193
- token.is_alpha and
194
- not token.is_punct and
195
- not token.like_num):
 
 
 
 
 
 
 
 
196
 
 
197
  word_freq[token.lemma_.lower()] += 1
198
 
 
199
  concepts = [(word, freq) for word, freq in word_freq.items()
200
  if freq >= min_freq]
201
  concepts.sort(key=lambda x: x[1], reverse=True)
202
 
203
- logger.info(f"Identified {len(concepts)} key concepts")
204
  return concepts[:10]
205
 
206
  except Exception as e:
@@ -208,9 +232,10 @@ def identify_key_concepts(doc, stopwords, min_freq=2, min_length=3):
208
  return []
209
 
210
  ########################################################################
 
211
  def create_concept_graph(doc, key_concepts):
212
  """
213
- Crea un grafo de relaciones entre conceptos.
214
  Args:
215
  doc: Documento procesado por spaCy
216
  key_concepts: Lista de tuplas (concepto, frecuencia)
@@ -223,26 +248,30 @@ def create_concept_graph(doc, key_concepts):
223
  # Crear un conjunto de conceptos clave para búsqueda rápida
224
  concept_words = {concept[0].lower() for concept in key_concepts}
225
 
 
 
 
 
 
226
  # Añadir nodos al grafo
227
  for concept, freq in key_concepts:
228
  G.add_node(concept.lower(), weight=freq)
229
 
230
  # Analizar cada oración
231
  for sent in doc.sents:
232
- # Obtener conceptos en la oración actual
233
  current_concepts = []
234
  for token in sent:
235
- if token.lemma_.lower() in concept_words:
 
236
  current_concepts.append(token.lemma_.lower())
237
 
238
  # Crear conexiones entre conceptos en la misma oración
239
  for i, concept1 in enumerate(current_concepts):
240
  for concept2 in current_concepts[i+1:]:
241
  if concept1 != concept2:
242
- # Si ya existe la arista, incrementar el peso
243
  if G.has_edge(concept1, concept2):
244
  G[concept1][concept2]['weight'] += 1
245
- # Si no existe, crear nueva arista con peso 1
246
  else:
247
  G.add_edge(concept1, concept2, weight=1)
248
 
@@ -250,53 +279,109 @@ def create_concept_graph(doc, key_concepts):
250
 
251
  except Exception as e:
252
  logger.error(f"Error en create_concept_graph: {str(e)}")
253
- # Retornar un grafo vacío en caso de error
254
  return nx.Graph()
255
 
256
  ###############################################################################
 
257
  def visualize_concept_graph(G, lang_code):
258
  """
259
- Visualiza el grafo de conceptos con nodos ajustados según la longitud del texto.
 
 
 
 
 
260
  """
261
  try:
262
- # Crear nueva figura con mayor tamaño
263
- fig = plt.figure(figsize=(15, 10)) # Aumentado de (12, 8) a (15, 10)
264
 
265
  if not G.nodes():
266
  logger.warning("Grafo vacío, retornando figura vacía")
267
  return fig
268
 
269
- # Calcular layout con más espacio
270
- pos = nx.spring_layout(G, k=2, iterations=50) # Aumentado k de 1 a 2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
 
272
  # Calcular factor de escala basado en número de nodos
273
- num_nodes = len(G.nodes())
274
  scale_factor = 1000 if num_nodes < 10 else 500 if num_nodes < 20 else 200
275
 
276
  # Obtener pesos ajustados
277
- node_weights = [G.nodes[node].get('weight', 1) * scale_factor for node in G.nodes()]
278
- edge_weights = [G[u][v].get('weight', 1) for u, v in G.edges()]
279
-
280
- # Dibujar grafo
281
- nx.draw_networkx_nodes(G, pos,
282
- node_size=node_weights,
283
- node_color='lightblue',
284
- alpha=0.6)
285
-
286
- nx.draw_networkx_edges(G, pos,
287
- width=edge_weights,
288
- alpha=0.5,
289
- edge_color='gray')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
 
291
  # Ajustar tamaño de fuente según número de nodos
292
  font_size = 12 if num_nodes < 10 else 10 if num_nodes < 20 else 8
293
 
294
- nx.draw_networkx_labels(G, pos,
295
- font_size=font_size,
296
- font_weight='bold')
297
-
298
- plt.title("Red de conceptos relacionados", pad=20)
299
- plt.axis('off')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
 
301
  return fig
302
 
@@ -304,7 +389,6 @@ def visualize_concept_graph(G, lang_code):
304
  logger.error(f"Error en visualize_concept_graph: {str(e)}")
305
  return plt.figure() # Retornar figura vacía en caso de error
306
 
307
-
308
  ########################################################################
309
  def create_entity_graph(entities):
310
  G = nx.Graph()
 
20
  # 4. Importaciones locales
21
  from .stopwords import (
22
  process_text,
23
+ clean_text,
24
  get_custom_stopwords,
25
  get_stopwords_for_spacy
26
  )
 
183
 
184
  def identify_key_concepts(doc, stopwords, min_freq=2, min_length=3):
185
  """
186
+ Identifica conceptos clave en el texto, excluyendo entidades nombradas.
187
+ Args:
188
+ doc: Documento procesado por spaCy
189
+ stopwords: Lista de stopwords
190
+ min_freq: Frecuencia mínima para considerar un concepto
191
+ min_length: Longitud mínima del concepto
192
+ Returns:
193
+ List[Tuple[str, int]]: Lista de tuplas (concepto, frecuencia)
194
  """
195
  try:
196
  word_freq = Counter()
197
 
198
+ # Crear conjunto de tokens que son parte de entidades
199
+ entity_tokens = set()
200
+ for ent in doc.ents:
201
+ entity_tokens.update(token.i for token in ent)
202
+
203
+ # Procesar tokens
204
  for token in doc:
205
+ # Verificar si el token no es parte de una entidad nombrada
206
+ if (token.i not in entity_tokens and # No es parte de una entidad
207
+ token.lemma_.lower() not in stopwords and # No es stopword
208
+ len(token.lemma_) >= min_length and # Longitud mínima
209
+ token.is_alpha and # Es alfabético
210
+ not token.is_punct and # No es puntuación
211
+ not token.like_num and # No es número
212
+ not token.is_space and # No es espacio
213
+ not token.is_stop and # No es stopword de spaCy
214
+ not token.pos_ == 'PROPN' and # No es nombre propio
215
+ not token.pos_ == 'SYM' and # No es símbolo
216
+ not token.pos_ == 'NUM' and # No es número
217
+ not token.pos_ == 'X'): # No es otro
218
 
219
+ # Convertir a minúsculas y añadir al contador
220
  word_freq[token.lemma_.lower()] += 1
221
 
222
+ # Filtrar conceptos por frecuencia mínima y ordenar por frecuencia
223
  concepts = [(word, freq) for word, freq in word_freq.items()
224
  if freq >= min_freq]
225
  concepts.sort(key=lambda x: x[1], reverse=True)
226
 
227
+ logger.info(f"Identified {len(concepts)} key concepts after excluding entities")
228
  return concepts[:10]
229
 
230
  except Exception as e:
 
232
  return []
233
 
234
  ########################################################################
235
+
236
  def create_concept_graph(doc, key_concepts):
237
  """
238
+ Crea un grafo de relaciones entre conceptos, ignorando entidades.
239
  Args:
240
  doc: Documento procesado por spaCy
241
  key_concepts: Lista de tuplas (concepto, frecuencia)
 
248
  # Crear un conjunto de conceptos clave para búsqueda rápida
249
  concept_words = {concept[0].lower() for concept in key_concepts}
250
 
251
+ # Crear conjunto de tokens que son parte de entidades
252
+ entity_tokens = set()
253
+ for ent in doc.ents:
254
+ entity_tokens.update(token.i for token in ent)
255
+
256
  # Añadir nodos al grafo
257
  for concept, freq in key_concepts:
258
  G.add_node(concept.lower(), weight=freq)
259
 
260
  # Analizar cada oración
261
  for sent in doc.sents:
262
+ # Obtener conceptos en la oración actual, excluyendo entidades
263
  current_concepts = []
264
  for token in sent:
265
+ if (token.i not in entity_tokens and
266
+ token.lemma_.lower() in concept_words):
267
  current_concepts.append(token.lemma_.lower())
268
 
269
  # Crear conexiones entre conceptos en la misma oración
270
  for i, concept1 in enumerate(current_concepts):
271
  for concept2 in current_concepts[i+1:]:
272
  if concept1 != concept2:
 
273
  if G.has_edge(concept1, concept2):
274
  G[concept1][concept2]['weight'] += 1
 
275
  else:
276
  G.add_edge(concept1, concept2, weight=1)
277
 
 
279
 
280
  except Exception as e:
281
  logger.error(f"Error en create_concept_graph: {str(e)}")
 
282
  return nx.Graph()
283
 
284
  ###############################################################################
285
+
286
  def visualize_concept_graph(G, lang_code):
287
  """
288
+ Visualiza el grafo de conceptos con layout consistente.
289
+ Args:
290
+ G: networkx.Graph - Grafo de conceptos
291
+ lang_code: str - Código del idioma
292
+ Returns:
293
+ matplotlib.figure.Figure - Figura del grafo
294
  """
295
  try:
296
+ # Crear nueva figura con mayor tamaño y definir los ejes explícitamente
297
+ fig, ax = plt.subplots(figsize=(15, 10))
298
 
299
  if not G.nodes():
300
  logger.warning("Grafo vacío, retornando figura vacía")
301
  return fig
302
 
303
+ # Convertir grafo no dirigido a dirigido para mostrar flechas
304
+ DG = nx.DiGraph(G)
305
+
306
+ # Calcular centralidad de los nodos para el color
307
+ centrality = nx.degree_centrality(G)
308
+
309
+ # Establecer semilla para reproducibilidad
310
+ seed = 42
311
+
312
+ # Calcular layout con parámetros fijos
313
+ pos = nx.spring_layout(
314
+ DG,
315
+ k=2, # Distancia ideal entre nodos
316
+ iterations=50, # Número de iteraciones
317
+ seed=seed # Semilla fija para reproducibilidad
318
+ )
319
 
320
  # Calcular factor de escala basado en número de nodos
321
+ num_nodes = len(DG.nodes())
322
  scale_factor = 1000 if num_nodes < 10 else 500 if num_nodes < 20 else 200
323
 
324
  # Obtener pesos ajustados
325
+ node_weights = [DG.nodes[node].get('weight', 1) * scale_factor for node in DG.nodes()]
326
+ edge_weights = [DG[u][v].get('weight', 1) for u, v in DG.edges()]
327
+
328
+ # Crear mapa de colores basado en centralidad
329
+ node_colors = [plt.cm.viridis(centrality[node]) for node in DG.nodes()]
330
+
331
+ # Dibujar nodos
332
+ nodes = nx.draw_networkx_nodes(
333
+ DG,
334
+ pos,
335
+ node_size=node_weights,
336
+ node_color=node_colors,
337
+ alpha=0.7,
338
+ ax=ax
339
+ )
340
+
341
+ # Dibujar aristas con flechas
342
+ edges = nx.draw_networkx_edges(
343
+ DG,
344
+ pos,
345
+ width=edge_weights,
346
+ alpha=0.6,
347
+ edge_color='gray',
348
+ arrows=True,
349
+ arrowsize=20,
350
+ arrowstyle='->',
351
+ connectionstyle='arc3,rad=0.2',
352
+ ax=ax
353
+ )
354
 
355
  # Ajustar tamaño de fuente según número de nodos
356
  font_size = 12 if num_nodes < 10 else 10 if num_nodes < 20 else 8
357
 
358
+ # Dibujar etiquetas con fondo blanco para mejor legibilidad
359
+ labels = nx.draw_networkx_labels(
360
+ DG,
361
+ pos,
362
+ font_size=font_size,
363
+ font_weight='bold',
364
+ bbox=dict(
365
+ facecolor='white',
366
+ edgecolor='none',
367
+ alpha=0.7
368
+ ),
369
+ ax=ax
370
+ )
371
+
372
+ # Añadir leyenda de centralidad
373
+ sm = plt.cm.ScalarMappable(
374
+ cmap=plt.cm.viridis,
375
+ norm=plt.Normalize(vmin=0, vmax=1)
376
+ )
377
+ sm.set_array([])
378
+ plt.colorbar(sm, ax=ax, label='Centralidad del concepto')
379
+
380
+ plt.title("Red de conceptos relacionados", pad=20, fontsize=14)
381
+ ax.set_axis_off()
382
+
383
+ # Ajustar el layout para que la barra de color no se superponga
384
+ plt.tight_layout()
385
 
386
  return fig
387
 
 
389
  logger.error(f"Error en visualize_concept_graph: {str(e)}")
390
  return plt.figure() # Retornar figura vacía en caso de error
391
 
 
392
  ########################################################################
393
  def create_entity_graph(entities):
394
  G = nx.Graph()
modules/ui/ui.py CHANGED
@@ -64,6 +64,8 @@ from ..semantic.semantic_interface import (
64
 
65
  from ..semantic.semantic_live_interface import display_semantic_live_interface
66
 
 
 
67
  from ..discourse.discourse_interface import ( # Agregar esta importación
68
  display_discourse_interface,
69
  display_discourse_results
@@ -328,6 +330,10 @@ def user_page(lang_code, t):
328
  if 'selected_tab' not in st.session_state:
329
  st.session_state.selected_tab = 0
330
 
 
 
 
 
331
  # Manejar la carga inicial de datos del usuario
332
  if 'user_data' not in st.session_state:
333
  with st.spinner(t.get('loading_data', "Cargando tus datos...")):
@@ -394,66 +400,136 @@ def user_page(lang_code, t):
394
  # Mostrar chatbot en sidebar
395
  display_sidebar_chat(lang_code, chatbot_t)
396
 
 
 
 
 
 
 
 
 
 
 
 
 
397
  # Sistema de tabs
398
- tabs = st.tabs([
399
  t.get('morpho_tab', 'Análisis Morfosintáctico'),
400
- t.get('semantic_live_tab', 'Análisis Semántico Vivo'), # Nuevo tab
401
  t.get('semantic_tab', 'Análisis Semántico'),
402
- t.get('discourse_tab', 'Análsis de discurso'),
 
403
  t.get('activities_tab', 'Mis Actividades'),
404
  t.get('feedback_tab', 'Formulario de Comentarios')
405
- ])
 
 
406
 
407
  # Manejar el contenido de cada tab
408
  for index, tab in enumerate(tabs):
409
- if tab.selected:
410
- st.session_state.selected_tab = index
411
-
412
  with tab:
413
  try:
414
- if index == 0:
 
 
 
 
 
 
 
 
 
 
 
415
  display_morphosyntax_interface(
416
  st.session_state.lang_code,
417
  st.session_state.nlp_models,
418
  t.get('TRANSLATIONS', {})
419
  )
420
 
421
- elif index == 1: # Nuevo caso para el análisis semántico vivo
 
422
  display_semantic_live_interface(
423
  st.session_state.lang_code,
424
  st.session_state.nlp_models,
425
  t.get('TRANSLATIONS', {})
426
  )
427
-
428
-
429
- elif index == 2:
430
  display_semantic_interface(
431
  st.session_state.lang_code,
432
  st.session_state.nlp_models,
433
  t.get('TRANSLATIONS', {})
434
  )
435
 
436
- elif index == 3:
 
 
 
 
 
 
 
 
 
 
437
  display_discourse_interface(
438
  st.session_state.lang_code,
439
  st.session_state.nlp_models,
440
  t.get('TRANSLATIONS', {})
441
  )
442
- elif index == 4:
 
 
443
  display_student_activities(
444
  username=st.session_state.username,
445
  lang_code=st.session_state.lang_code,
446
  t=t.get('ACTIVITIES_TRANSLATIONS', {})
447
  )
448
- elif index == 5:
 
 
449
  display_feedback_form(
450
  st.session_state.lang_code,
451
  t
452
  )
 
453
  except Exception as e:
 
 
 
 
454
  logger.error(f"Error en tab {index}: {str(e)}")
455
  st.error(t.get('tab_error', 'Error al cargar esta sección'))
456
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
457
  # Panel de depuración (solo visible en desarrollo)
458
  if st.session_state.get('debug_mode', False):
459
  with st.expander("Debug Info"):
 
64
 
65
  from ..semantic.semantic_live_interface import display_semantic_live_interface
66
 
67
+ from ..discourse.discourse_live_interface import display_discourse_live_interface
68
+
69
  from ..discourse.discourse_interface import ( # Agregar esta importación
70
  display_discourse_interface,
71
  display_discourse_results
 
330
  if 'selected_tab' not in st.session_state:
331
  st.session_state.selected_tab = 0
332
 
333
+ # Inicializar el estado del análisis en vivo
334
+ if 'semantic_live_active' not in st.session_state:
335
+ st.session_state.semantic_live_active = False
336
+
337
  # Manejar la carga inicial de datos del usuario
338
  if 'user_data' not in st.session_state:
339
  with st.spinner(t.get('loading_data', "Cargando tus datos...")):
 
400
  # Mostrar chatbot en sidebar
401
  display_sidebar_chat(lang_code, chatbot_t)
402
 
403
+ # Inicializar estados para todos los tabs
404
+ if 'tab_states' not in st.session_state:
405
+ st.session_state.tab_states = {
406
+ 'morpho_active': False,
407
+ 'semantic_live_active': False,
408
+ 'semantic_active': False,
409
+ 'discourse_live_active': False,
410
+ 'discourse_active': False,
411
+ 'activities_active': False,
412
+ 'feedback_active': False
413
+ }
414
+
415
  # Sistema de tabs
416
+ tab_names = [
417
  t.get('morpho_tab', 'Análisis Morfosintáctico'),
418
+ t.get('semantic_live_tab', 'Análisis Semántico Vivo'),
419
  t.get('semantic_tab', 'Análisis Semántico'),
420
+ t.get('discourse_live_tab', 'Análisis de Discurso Vivo'),
421
+ t.get('discourse_tab', 'Análsis de Discurso'),
422
  t.get('activities_tab', 'Mis Actividades'),
423
  t.get('feedback_tab', 'Formulario de Comentarios')
424
+ ]
425
+
426
+ tabs = st.tabs(tab_names)
427
 
428
  # Manejar el contenido de cada tab
429
  for index, tab in enumerate(tabs):
 
 
 
430
  with tab:
431
  try:
432
+ # Actualizar el tab seleccionado solo si no hay un análisis activo
433
+ if tab.selected and st.session_state.selected_tab != index:
434
+ can_switch = True
435
+ for state_key in st.session_state.tab_states.keys():
436
+ if st.session_state.tab_states[state_key] and index != get_tab_index(state_key):
437
+ can_switch = False
438
+ break
439
+ if can_switch:
440
+ st.session_state.selected_tab = index
441
+
442
+ if index == 0: # Morfosintáctico
443
+ st.session_state.tab_states['morpho_active'] = True
444
  display_morphosyntax_interface(
445
  st.session_state.lang_code,
446
  st.session_state.nlp_models,
447
  t.get('TRANSLATIONS', {})
448
  )
449
 
450
+ elif index == 1: # Semántico Vivo
451
+ st.session_state.tab_states['semantic_live_active'] = True
452
  display_semantic_live_interface(
453
  st.session_state.lang_code,
454
  st.session_state.nlp_models,
455
  t.get('TRANSLATIONS', {})
456
  )
457
+
458
+ elif index == 2: # Semántico
459
+ st.session_state.tab_states['semantic_active'] = True
460
  display_semantic_interface(
461
  st.session_state.lang_code,
462
  st.session_state.nlp_models,
463
  t.get('TRANSLATIONS', {})
464
  )
465
 
466
+ elif index == 3: # Discurso Vivo
467
+ st.session_state.tab_states['discourse_live_active'] = True
468
+ display_discourse_live_interface(
469
+ st.session_state.lang_code,
470
+ st.session_state.nlp_models,
471
+ t.get('TRANSLATIONS', {})
472
+ )
473
+
474
+
475
+ elif index == 4: # Discurso
476
+ st.session_state.tab_states['discourse_active'] = True
477
  display_discourse_interface(
478
  st.session_state.lang_code,
479
  st.session_state.nlp_models,
480
  t.get('TRANSLATIONS', {})
481
  )
482
+
483
+ elif index == 5: # Actividades
484
+ st.session_state.tab_states['activities_active'] = True
485
  display_student_activities(
486
  username=st.session_state.username,
487
  lang_code=st.session_state.lang_code,
488
  t=t.get('ACTIVITIES_TRANSLATIONS', {})
489
  )
490
+
491
+ elif index == 6: # Feedback
492
+ st.session_state.tab_states['feedback_active'] = True
493
  display_feedback_form(
494
  st.session_state.lang_code,
495
  t
496
  )
497
+
498
  except Exception as e:
499
+ # Desactivar el estado en caso de error
500
+ state_key = get_state_key_for_index(index)
501
+ if state_key:
502
+ st.session_state.tab_states[state_key] = False
503
  logger.error(f"Error en tab {index}: {str(e)}")
504
  st.error(t.get('tab_error', 'Error al cargar esta sección'))
505
 
506
+ # Funciones auxiliares para manejar los estados de los tabs
507
+ def get_tab_index(state_key):
508
+ """Obtiene el índice del tab basado en la clave de estado"""
509
+ index_map = {
510
+ 'morpho_active': 0,
511
+ 'semantic_live_active': 1,
512
+ 'semantic_active': 2,
513
+ 'discourse_live_active': 3,
514
+ 'discourse_active': 4,
515
+ 'activities_active': 5,
516
+ 'feedback_active': 6
517
+ }
518
+ return index_map.get(state_key, -1)
519
+
520
+ def get_state_key_for_index(index):
521
+ """Obtiene la clave de estado basada en el índice del tab"""
522
+ state_map = {
523
+ 0: 'morpho_active',
524
+ 1: 'semantic_live_active',
525
+ 2: 'semantic_active',
526
+ 3: 'discourse_live_active',
527
+ 3: 'discourse_active',
528
+ 4: 'activities_active',
529
+ 5: 'feedback_active'
530
+ }
531
+ return state_map.get(index)
532
+
533
  # Panel de depuración (solo visible en desarrollo)
534
  if st.session_state.get('debug_mode', False):
535
  with st.expander("Debug Info"):