Lukeetah commited on
Commit
8a02240
·
verified ·
1 Parent(s): 575e4a2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +78 -160
app.py CHANGED
@@ -1,6 +1,6 @@
1
  # -*- coding: utf-8 -*-
2
  # Protocolo Nexus Cognitivo - LITE (Obsidiana ES)
3
- # Versión: 4.1.0-Lite-FixedLoadSSRv2
4
 
5
  import gradio as gr
6
  import random
@@ -31,7 +31,7 @@ except ImportError:
31
  NUMPY_AVAILABLE = False
32
 
33
  # --- LITE Text Dictionary (Embedded) ---
34
- # (TEXTOS_LITE dictionary remains the same as the previous version)
35
  TEXTOS_LITE = {
36
  "app_title": "Protocolo Nexus Cognitivo LITE",
37
  "app_version": "4.1.0-Lite ES",
@@ -95,12 +95,12 @@ TEXTOS_LITE = {
95
  "results_level_max_advance": "<h5><strong>Sintonía Pico Nivel {level}.</strong> Calibración adicional recomendada.</h5>",
96
  "results_level_regress": "<h5><strong>Disonancia Detectada.</strong> Recalibrando a Nivel {new_level}.</h5>",
97
  "results_level_maintain": "<h5>Resonancia Nivel {level} mantenida. (Req: >{threshold}% Alin., <{consistency_threshold:.3f} CV)</h5>",
98
- "results_analysis_title": "Análisis Básico", # Simplified
99
  "results_analysis_generating": "<p>Calculando...</p>",
100
  "results_analysis_error": "<p style='color:var(--color-error);'>Error análisis.</p>",
101
  "results_analysis_missing": "<p>Datos no disponibles.</p>",
102
- "results_analysis_summary": "Módulo {test_name}: Alineación {precision:.1f}%. {rt_info}", # Simplified analysis output
103
- "results_rt_summary": "TR Medio: {avg_rt:.3f}s (CV: {rt_cv:.3f})", # Simplified RT info
104
  "results_info": "<p class='info-text'>Eco archivado. Estado flujo actualizado. Retornando.</p>",
105
  "results_back_button": "Retornar",
106
  # Popups / Info / Warnings / Errors
@@ -126,7 +126,7 @@ TEXTOS_LITE = {
126
  "error_format_key_missing": "Error Txt: Falta '{missing}' en '{key}'.",
127
  "error_format_general": "Error Txt: {error} en '{key}'.",
128
  "error_trial_flow": "ERROR CRÍTICO durante flujo de prueba: {error}",
129
- "error_finish_test_state": "ERROR: Estado inválido al finalizar test.", # Added
130
  # Logging
131
  "log_init_start": "Iniciando Nexus LITE v{version}...",
132
  "log_init_results": "Archivo de ecos: {file}",
@@ -155,7 +155,7 @@ TEXTOS_LITE = {
155
  "log_test_transition": "Transición a: {test_name}",
156
  "log_test_next_instr": "Mostrando instrucciones para {test_name}",
157
  "log_seq_complete": "Calibración Completa. Calculando resultados...",
158
- "log_level_change": "Evaluación Nivel: Precision={acc:.1f}, Consist={cons:.3f}. Nuevo Nivel: {level}", # Simplified log
159
  "log_saving_results": "Archivando eco: Alias={alias}, LvlComp={level_completed}, NewLvl={new_level}",
160
  "log_save_results_success": "Eco archivado.",
161
  "log_save_profile": "Perfil guardado: {alias}, Nivel: {profile_level}",
@@ -279,105 +279,12 @@ def _safe_append_csv(row_dict: dict, fieldnames: list, filepath: Path):
279
  return False
280
 
281
  # --- CSS ---
282
- # Assume full obsidian_css string from the original code is pasted here
283
  obsidian_css = """
284
- :root {
285
- --font-main: 'Inter', 'Roboto', 'Source Sans Pro', sans-serif;
286
- --font-monospace: 'Roboto Mono', 'Source Code Pro', monospace;
287
- /* Obsidian Theme (Dark Default) */
288
- --color-background-dark: #101015; --color-surface-dark: #1c1c2e; --color-border-dark: #3b3e58; /* Slightly darker */
289
- --color-text-primary-dark: #d5d5e0; --color-text-secondary-dark: #9799ae; --color-text-subdued-dark: #626587; /* Adjusted text colors */
290
- --color-primary-dark: #8f6aff; --color-primary-hover-dark: #a786ff; /* Slightly different purple */
291
- --color-accent-dark: #00cf98; --color-accent-hover-dark: #33ffc0; /* More vibrant accent */
292
- --color-warning-dark: #ffcc00; --color-error-dark: #ff3b5f; --color-error-hover-dark: #ff6a89; /* Brighter warning/error */
293
- --shadow-sm-dark: 0 1px 4px rgba(0, 0, 0, 0.4); --shadow-md-dark: 0 5px 12px rgba(0, 0, 0, 0.5), 0 1px 3px rgba(0, 0, 0, 0.4); /* Enhanced shadows */
294
- --code-bg-dark: #2a2a40; --spinner-color: var(--color-primary-dark);
295
-
296
- /* Nebula Theme (Light Alternative - Kept for toggle functionality) */
297
- --color-background-light: #f0f0f5; --color-surface-light: #ffffff; --color-border-light: #c8c8d8; /* Cooler light tones */
298
- --color-text-primary-light: #202035; --color-text-secondary-light: #505065; --color-text-subdued-light: #7a7a8f;
299
- --color-primary-light: #704ff0; --color-primary-hover-light: #8f6aff;
300
- --color-accent-light: #00b383; --color-accent-hover-light: #00cf98;
301
- --color-warning-light: #f0b400; --color-error-light: #e52a4b; --color-error-hover-light: #ff3b5f;
302
- --shadow-sm-light: 0 1px 3px rgba(30, 30, 60, 0.09); --shadow-md-light: 0 4px 9px rgba(30, 30, 60, 0.12), 0 1px 2px rgba(30, 30, 60, 0.09);
303
- --code-bg-light: #e4e4ec;
304
-
305
- /* Default to Obsidian */
306
- --color-background: var(--color-background-dark); --color-surface: var(--color-surface-dark); --color-border: var(--color-border-dark);
307
- --color-text-primary: var(--color-text-primary-dark); --color-text-secondary: var(--color-text-secondary-dark); --color-text-subdued: var(--color-text-subdued-dark);
308
- --color-primary: var(--color-primary-dark); --color-primary-hover: var(--color-primary-hover-dark);
309
- --color-accent: var(--color-accent-dark); --color-accent-hover: var(--color-accent-hover-dark);
310
- --color-warning: var(--color-warning-dark); --color-error: var(--color-error-dark); --color-error-hover: var(--color-error-hover-dark);
311
- --shadow-sm: var(--shadow-sm-dark); --shadow-md: var(--shadow-md-dark); --code-bg: var(--code-bg-dark);
312
- --border-radius: 6px; --transition-speed: 0.25s;
313
- }
314
- body.light-theme {
315
- --color-background: var(--color-background-light); --color-surface: var(--color-surface-light); --color-border: var(--color-border-light);
316
- --color-text-primary: var(--color-text-primary-light); --color-text-secondary: var(--color-text-secondary-light); --color-text-subdued: var(--color-text-subdued-light);
317
- --color-primary: var(--color-primary-light); --color-primary-hover: var(--color-primary-hover-light);
318
- --color-accent: var(--color-accent-light); --color-accent-hover: var(--color-accent-hover-light);
319
- --color-warning: var(--color-warning-light); --color-error: var(--color-error-light); --color-error-hover: var(--color-error-hover-light);
320
- --shadow-sm: var(--shadow-sm-light); --shadow-md: var(--shadow-md-light); --code-bg: var(--code-bg-light);
321
- --spinner-color: var(--color-primary-light);
322
- }
323
- body { font-family: var(--font-main); background-color: var(--color-background); color: var(--color-text-primary); line-height: 1.65; transition: background-color var(--transition-speed) ease, color var(--transition-speed) ease; }
324
- .gradio-container { max-width: 900px !important; margin: 2.5rem auto; background-color: transparent; box-shadow: none; border: none; padding: 0; }
325
- .main-content-box { background-color: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--border-radius); padding: 2.5rem; margin-bottom: 2rem; box-shadow: var(--shadow-md); transition: background-color var(--transition-speed) ease, border-color var(--transition-speed) ease, box-shadow var(--transition-speed) ease; animation: fadeIn 0.5s ease-out; }
326
- .block-title { color: var(--color-text-primary); text-align: center; font-size: 2.2em; font-weight: 600; margin-bottom: 1.2rem; padding-bottom: 0.8rem; border-bottom: 1px solid var(--color-primary); transition: color var(--transition-speed) ease, border-color var(--transition-speed) ease; letter-spacing: 0.5px; text-shadow: 0 0 5px color-mix(in srgb, var(--color-primary) 20%, transparent); }
327
- .block-subtitle { color: var(--color-text-secondary); text-align: center; font-size: 1.3em; margin-bottom: 1.5rem; font-weight: 500; transition: color var(--transition-speed) ease; }
328
- .gr-button { font-weight: 500; border-radius: var(--border-radius); padding: 0.8rem 1.6rem; transition: all var(--transition-speed) ease-in-out; box-shadow: var(--shadow-sm); border: 1px solid transparent; font-size: 1.0em; letter-spacing: 0.3px; cursor: pointer; }
329
- .gr-button:hover { transform: translateY(-2px); }
330
- .gr-button.gr-button-primary { background-color: var(--color-primary) !important; border-color: var(--color-primary) !important; color: #ffffff !important; text-shadow: 0 1px 2px rgba(0,0,0,0.3); }
331
- .gr-button.gr-button-primary:hover { background-color: var(--color-primary-hover) !important; border-color: var(--color-primary-hover) !important; box-shadow: var(--shadow-md), 0 0 12px color-mix(in srgb, var(--color-primary) 45%, transparent); }
332
- .gr-button.gr-button-secondary { background-color: var(--color-surface) !important; border-color: var(--color-border) !important; color: var(--color-text-secondary) !important; }
333
- .gr-button.gr-button-secondary:hover { border-color: var(--color-primary) !important; color: var(--color-primary) !important; background-color: color-mix(in srgb, var(--color-primary) 10%, var(--color-surface)) !important; }
334
- .btn-menu { display: block !important; width: 95% !important; margin: 0.9rem auto !important; text-align: left; font-size: 1.1em; padding: 0.9rem 1.8rem !important; }
335
- .btn-reset { background-color: var(--color-error) !important; border-color: var(--color-error) !important; color: #ffffff !important; }
336
- .btn-reset:hover { background-color: var(--color-error-hover) !important; border-color: var(--color-error-hover) !important; box-shadow: var(--shadow-md), 0 0 10px color-mix(in srgb, var(--color-error) 40%, transparent); }
337
- #alias-input-box textarea { font-size: 1.15em; border: 1px solid var(--color-border); border-radius: var(--border-radius); background-color: var(--color-background); color: var(--color-text-primary); transition: all var(--transition-speed) ease; padding: 0.7rem 1rem; }
338
- #alias-input-box textarea:focus { border-color: var(--color-primary); background-color: var(--color-surface); box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 30%, transparent); }
339
- #alias_feedback, #agent-info-menu { text-align: center; color: var(--color-text-secondary); margin-top: 1rem; min-height: 1.8em; font-size: 1.0em; transition: color var(--transition-speed) ease; }
340
- #agent-info-menu strong { color: var(--color-primary); font-weight: 600; }
341
- .info-text { color: var(--color-text-subdued); font-size: 0.95em; text-align: center; margin-top: 1.5rem; transition: color var(--transition-speed) ease; }
342
- .disclaimer-box { border: 1px solid var(--color-warning); border-left: 6px solid var(--color-warning); padding: 1.5rem; margin: 2rem 0; background-color: color-mix(in srgb, var(--color-warning) 12%, var(--color-surface)); border-radius: var(--border-radius); transition: background-color var(--transition-speed) ease, border-color var(--transition-speed) ease; box-shadow: inset 0 0 10px rgba(0,0,0,0.2); }
343
- .disclaimer-box p { margin: 0.5rem 0; font-size: 1.0em; color: var(--color-text-secondary); transition: color var(--transition-speed) ease;}
344
- .disclaimer-box strong { color: color-mix(in srgb, var(--color-warning) 85%, var(--color-text-primary)); font-weight: 600; transition: color var(--transition-speed) ease;}
345
- #instr-text { color: var(--color-text-secondary); line-height: 1.8; font-size: 1.1em; margin-top: 1.2rem; transition: color var(--transition-speed) ease; }
346
- #instr-text strong { color: var(--color-text-primary); font-weight: 600; }
347
- #instr-text code { background-color: var(--code-bg); padding: 4px 8px; border: 1px solid var(--color-border); color: var(--color-primary); font-family: var(--font-monospace); font-weight: 600; font-size: 0.95em; border-radius: 4px; transition: all var(--transition-speed) ease; margin: 0 2px; box-shadow: inset 0 0 3px rgba(0,0,0,0.2); }
348
- #instr-text h5 { color: var(--color-text-primary); font-size: 1.4em; margin-bottom: 1rem; font-weight: 600; }
349
- #test-block { text-align: center; position: relative; }
350
- #progress-indicator, #score-display, #timer-display { color: var(--color-text-secondary); font-size: 1.1em; margin-bottom: 0.8rem; min-height: 1.4em; transition: color var(--transition-speed) ease; font-family: var(--font-monospace); }
351
- #score-display { font-weight: 500; letter-spacing: 1px; }
352
- #timer-display { font-weight: 600; color: var(--color-primary); text-shadow: 0 0 4px color-mix(in srgb, var(--color-primary) 30%, transparent); }
353
- #stimulus-display p.stimulus-core { font-size: 7em; font-weight: 500; text-align: center; margin: 2.5rem 0; min-height: 1.8em; line-height: 1.1; color: var(--color-text-primary); transition: color 0.1s ease-in-out, text-shadow 0.1s ease-in-out, opacity 0.2s ease-out; font-family: var(--font-monospace); letter-spacing: 0.1em; animation: stimulusAppear 0.3s ease-out; text-shadow: 0 0 8px color-mix(in srgb, var(--color-text-primary) 15%, transparent); }
354
- #stimulus-display p.stimulus-suppressor { font-size: 5.5em; color: var(--color-text-subdued); opacity: 0.5; font-style: italic; }
355
- #feedback-display { text-align: center; min-height: 2.5em; margin-top: 1.5rem; font-size: 1.15em; animation: feedbackFadeIn 0.3s ease-out; }
356
- .feedback-subtle { color: var(--color-text-subdued); font-size: 0.9em; margin-left: 0.8rem; font-style: italic; transition: color var(--transition-speed) ease; display: inline-block; }
357
- .btn-response { font-size: 1.3em !important; font-weight: 600 !important; padding: 1.1rem 0.6rem !important; margin: 6px !important; min-width: 90px !important; background-color: var(--color-surface) !important; border: 2px solid var(--color-border) !important; color: var(--color-text-primary) !important; transition: all 0.15s ease; font-family: var(--font-monospace); letter-spacing: 1px; word-wrap: break-word; white-space: normal; line-height: 1.3; text-shadow: 0 1px 1px rgba(0,0,0,0.3);}
358
- .btn-response:hover { border-color: var(--color-primary) !important; background-color: color-mix(in srgb, var(--color-primary) 15%, var(--color-surface)) !important; transform: scale(1.05); box-shadow: var(--shadow-sm); color: var(--color-primary) !important; }
359
- #results-summary h5, #results-level h5, .cognitive-echo-container h5 { color: var(--color-primary); font-weight: 600; font-size: 1.3em; text-align: center; margin-top: 1.2rem; margin-bottom: 0.8rem; padding-bottom: 0.4rem; border-bottom: 1px solid var(--color-border); transition: color var(--transition-speed) ease, border-color var(--transition-speed) ease;}
360
- #results-summary ul.results-list { list-style: none; padding: 0; margin: 1rem 0 1.8rem 0; }
361
- #results-summary li { background-color: var(--color-surface); border-left: 6px solid var(--color-primary); margin: 0.7rem 0; padding: 0.9rem 1.4rem; color: var(--color-text-secondary); font-size: 1.1em; border-radius: 4px; transition: all var(--transition-speed) ease; box-shadow: var(--shadow-sm); }
362
- #results-summary li:hover { transform: translateX(5px); border-left-color: var(--color-accent); background-color: color-mix(in srgb, var(--color-accent) 8%, var(--color-surface)); }
363
- #results-summary strong { color: var(--color-text-primary); font-weight: 600; }
364
- #results-level h5 { background-color: color-mix(in srgb, var(--color-primary) 10%, var(--color-surface)); border: 1px solid var(--color-primary); color: var(--color-text-primary); padding: 1rem; border-radius: var(--border-radius); box-shadow: var(--shadow-sm); }
365
- #results_analysis_content { padding: 1.5rem; background-color: var(--color-surface) !important; border-top: none; transition: background-color var(--transition-speed) ease; }
366
- #results_analysis_content h5 { color: var(--color-primary); font-size: 1.2em; margin-top: 1.5rem; margin-bottom: 0.8rem; font-weight: 600; padding-bottom: 4px; border-bottom: 1px dashed var(--color-border); }
367
- #results_analysis_content p { margin-bottom: 1rem; color: var(--color-text-secondary); font-size: 1.0em; line-height: 1.7; transition: color var(--transition-speed) ease; }
368
- .history-table-container { max-height: 500px; overflow-y: auto; border: 1px solid var(--color-border); margin-top: 1.5rem; background-color: var(--color-surface); border-radius: var(--border-radius); box-shadow: var(--shadow-sm); transition: all var(--transition-speed) ease; }
369
- #history-html table { width: 100%; border-collapse: collapse; margin-top: 0; }
370
- #history-html th { border: 1px solid var(--color-border); padding: 10px 14px; background-color: var(--color-background); color: var(--color-text-primary); position: sticky; top: 0; text-align: left; font-weight: 600; transition: all var(--transition-speed) ease; z-index: 1;}
371
- #history-html td { border: 1px solid var(--color-border); padding: 10px 14px; text-align: left; color: var(--color-text-secondary); transition: all var(--transition-speed) ease; }
372
- #history-html tr:nth-child(even) { background-color: color-mix(in srgb, var(--color-background) 50%, var(--color-surface)); transition: background-color var(--transition-speed) ease; }
373
- #history-html tr:hover td { background-color: color-mix(in srgb, var(--color-primary) 10%, var(--color-surface)); color: var(--color-text-primary); }
374
- .spinner { width: 40px; height: 40px; border: 4px solid color-mix(in srgb, var(--spinner-color) 30%, transparent); border-top-color: var(--spinner-color); border-radius: 50%; animation: spin 1s linear infinite; margin: 1rem auto; }
375
- @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
376
- @keyframes stimulusAppear { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
377
- @keyframes feedbackFadeIn { from { opacity: 0; } to { opacity: 1; } }
378
- @keyframes spin { to { transform: rotate(360deg); } }
379
- hr.section-hr { border: 0; height: 1px; background-image: linear-gradient(to right, transparent, var(--color-border), transparent); margin: 2.5rem 0; }
380
- hr.analysis-hr { border: 0; height: 1px; background-color: var(--color-border); margin: 1.8rem 0; }
381
  footer { display: none !important; }
382
  """
383
 
@@ -404,8 +311,8 @@ TEST_CONFIG_LITE = {
404
  }
405
  AVAILABLE_TEST_KEYS_LITE = list(TEST_CONFIG_LITE.keys())
406
 
407
- # --- Profile/Results Handling (Simplified) ---
408
- # (load_agent_profile, save_agent_profile, save_result, read_history functions remain the same as previous LITE version)
409
  def load_agent_profile(alias):
410
  if not alias: return 1
411
  with profile_lock:
@@ -493,6 +400,7 @@ def read_history():
493
  return html
494
 
495
  # --- Difficulty Params (LITE) ---
 
496
  def get_difficulty_params(test_key, level):
497
  if test_key not in TEST_CONFIG_LITE: raise ValueError(f"Unknown test key: {test_key}")
498
  config = TEST_CONFIG_LITE[test_key]
@@ -509,7 +417,7 @@ def get_difficulty_params(test_key, level):
509
  return params
510
 
511
  # --- Sequence Generation (LITE) ---
512
- # (generate_attention_sequence, generate_inhibition_sequence, generate_memory_sequence functions remain the same as previous LITE version)
513
  def generate_attention_sequence(params):
514
  n = params['trials']; cue = params['cue']; target = params['target']
515
  sim_dist = params['similar_distractors']; other_dist = params['other_distractors']
@@ -593,6 +501,7 @@ sequence_generators = {
593
  }
594
 
595
  # --- Instruction HTML Generation (LITE) ---
 
596
  def get_instructions_html(test_key, params):
597
  test_config = TEST_CONFIG_LITE.get(test_key)
598
  if not test_config: return "<p>Error: Configuración Test No Encontrada.</p>"
@@ -619,12 +528,12 @@ def get_instructions_html(test_key, params):
619
  lines.extend(common_lines)
620
  return "".join(f"<p>{line}</p>" if not line.startswith("<br>") else line for line in lines)
621
 
622
- # --- UI Formatting (Simplified Stimulus/Button Logic) ---
623
- # (format_stimulus_html and get_test_buttons_visibility_and_labels functions remain the same as previous LITE version)
624
  def format_stimulus_html(state):
625
  stim_raw = state.get("test_stimulus", ""); test_key = state.get("current_test_key", "")
626
  params = state.get("test_params", {}); is_awaiting = state.get("awaiting_input", False)
627
- if not is_awaiting or stim_raw is None or stim_raw == "": return "<p class='stimulus-core'> </p>"
628
  display_text = ""; style_color = "var(--color-text-primary)"; extra_class = ""
629
  try:
630
  if test_key == "atencion": display_text = str(stim_raw)
@@ -643,7 +552,7 @@ def format_stimulus_html(state):
643
  else: raise ValueError("Invalid mem stimulus")
644
  else: display_text = str(stim_raw) # Fallback
645
  display_text_safe = str(display_text).replace("&", "&").replace("<", "<").replace(">", ">")
646
- if not display_text_safe or not display_text_safe.strip(): display_text_safe = " "
647
  return f"<p class='stimulus-core{extra_class}' style='color:{style_color};'>{display_text_safe}</p>"
648
  except Exception as e:
649
  log_message("error_stimulus_format", level="ERROR", error=e, traceback=traceback.format_exc())
@@ -678,7 +587,7 @@ def get_test_buttons_visibility_and_labels(state):
678
  return final_updates, keys
679
 
680
  # --- Response Processing (LITE) ---
681
- # (process_response function remains the same as previous LITE version)
682
  def process_response(state, key, is_timeout=False):
683
  with state_lock:
684
  current_state = deepcopy(state)
@@ -769,7 +678,7 @@ def process_response(state, key, is_timeout=False):
769
  return current_state
770
 
771
  # --- Score Calculation (LITE) ---
772
- # (calculate_results_lite function remains the same as previous LITE version)
773
  def calculate_results_lite(test_key, trial_results):
774
  metrics = {'precision': 0, 'avg_rt': 0, 'rt_cv': 0, 'timeouts': 0}
775
  analysis_str = get_text("results_analysis_missing")
@@ -795,7 +704,6 @@ def calculate_results_lite(test_key, trial_results):
795
  rt_info = get_text("results_rt_summary", avg_rt=avg_rt, rt_cv=rt_cv)
796
  timeouts = sum(1 for r in trial_results if r.get('is_timeout') and not r.get('correct'))
797
  metrics['timeouts'] = timeouts
798
- # Simplified analysis string
799
  test_name_loc = get_text(TEST_CONFIG_LITE[test_key]['loc_key'], default=test_key)
800
  analysis_str = get_text("results_analysis_summary", test_name=test_name_loc, precision=precision, rt_info=rt_info)
801
  log_message("log_score_calculated", test_name=test_key, acc=precision, cons=metrics['rt_cv'])
@@ -807,7 +715,7 @@ initial_state_lite = {
807
  "current_test_order": [], "current_test_key": None, "current_test_index": -1,
808
  "test_params": {}, "test_sequence": [], "test_expected_response": {},
809
  "test_trial_index": 0, "current_stimulus_index": -1, "last_processed_index": -99,
810
- "test_stimulus": None, "test_user_response": None, "test_feedback": " ",
811
  "test_stimulus_show_time": None, "awaiting_input": False,
812
  "current_scores": {}, "current_trial_results": [],
813
  "round_results": {"analysis": {}, "metrics": {"per_module": {}, "overall": {}}},
@@ -816,17 +724,17 @@ initial_state_lite = {
816
  }
817
 
818
  theme = gr.themes.Soft(primary_hue="purple", secondary_hue="indigo").set(
819
- body_background_fill="*color-background", body_text_color="*color-text-primary", # etc.
820
  )
821
 
822
  with gr.Blocks(title=APP_TITLE, theme=theme, css=obsidian_css) as demo_lite:
823
  app_state = gr.State(value=deepcopy(initial_state_lite))
824
  with gr.Column(elem_classes=f"{initial_state_lite.get('theme','dark')}-theme", elem_id="master-column") as master_column:
825
- # --- Stage Blocks --- Set initial visibility directly
826
- # Welcome / Alias Block (Starts Visible)
827
- with gr.Column(visible=True, elem_classes="main-content-box", elem_id="welcome-alias-block") as welcome_alias_block:
828
- welcome_title = gr.Markdown("...", elem_classes="block-title") # Placeholder text
829
- welcome_text = gr.Markdown("...") # Placeholder text
830
  with gr.Accordion("...", open=False) as disclaimer_accordion: disclaimer_text = gr.Markdown("...")
831
  gr.HTML("<hr class='section-hr'>")
832
  alias_title = gr.Markdown("...", elem_classes="block-subtitle")
@@ -835,7 +743,7 @@ with gr.Blocks(title=APP_TITLE, theme=theme, css=obsidian_css) as demo_lite:
835
  with gr.Row():
836
  alias_confirm_button = gr.Button("...", elem_classes="gr-button-primary", scale=1)
837
  alias_skip_button = gr.Button("...", elem_classes="gr-button-secondary", scale=1)
838
- # Menu Block (Starts Hidden)
839
  with gr.Column(visible=False, elem_classes="main-content-box", elem_id="menu-block") as menu_block:
840
  menu_title = gr.Markdown("...", elem_classes="block-title")
841
  agent_info = gr.Markdown("...", elem_id="agent-info-menu")
@@ -844,42 +752,42 @@ with gr.Blocks(title=APP_TITLE, theme=theme, css=obsidian_css) as demo_lite:
844
  view_history_button = gr.Button("...", elem_classes="btn-menu gr-button-secondary")
845
  reset_level_button = gr.Button("...", elem_classes="btn-menu btn-reset")
846
  theme_toggle_btn = gr.Button("...", elem_classes="btn-menu gr-button-secondary")
847
- # History Block (Starts Hidden)
848
  with gr.Column(visible=False, elem_classes="main-content-box", elem_id="history-block") as history_block:
849
  history_title = gr.Markdown("...", elem_classes="block-title")
850
- hist_html = gr.HTML(f"<p>{get_text('history_loading')}</p>", elem_id="history-html")
851
  history_back_button = gr.Button("...", elem_classes="gr-button-secondary")
852
- # Instructions Block (Starts Hidden)
853
  with gr.Column(visible=False, elem_classes="main-content-box", elem_id="instructions-block") as instructions_block:
854
  instructions_title = gr.Markdown("...", elem_classes="block-subtitle", elem_id="instructions_title")
855
- instructions_text = gr.HTML("<p>...</p>", elem_id="instr-text")
856
  start_test_button = gr.Button("...", elem_classes="gr-button-primary")
857
- # Test Block (Starts Hidden)
858
  with gr.Column(visible=False, elem_classes="main-content-box", elem_id="test-block") as test_block:
859
  test_title = gr.Markdown("...", elem_classes="block-subtitle", elem_id="test_title")
860
  with gr.Row():
861
  progress_indicator = gr.Markdown("...", elem_id="progress-indicator")
862
  score_display = gr.Markdown("...", elem_id="score-display")
863
  timer_display = gr.Markdown("...", elem_id="timer-display")
864
- stimulus_display = gr.HTML("<p class='stimulus-core'> </p>", elem_id="stimulus-display")
865
- feedback_display = gr.HTML(" ", elem_id="feedback-display")
866
  with gr.Row(equal_height=False, elem_id="response-button-row"):
867
  response_btn_1 = gr.Button("", elem_classes="btn-response", scale=1, visible=False)
868
  response_btn_2 = gr.Button("", elem_classes="btn-response", scale=1, visible=False)
869
  all_response_buttons = [response_btn_1, response_btn_2]
870
- # Results Block (Starts Hidden)
871
  with gr.Column(visible=False, elem_classes="main-content-box", elem_id="results-block") as results_block:
872
  results_title = gr.Markdown("...", elem_classes="block-title")
873
  results_report_for = gr.Markdown("...", elem_classes="block-subtitle", elem_id="results_report_for")
874
  results_summary = gr.HTML("...", elem_id="results-summary")
875
  results_level_msg = gr.HTML("...", elem_id="results-level")
876
- results_analysis_content = gr.HTML(f"<p>{get_text('results_analysis_generating')}</p>", elem_id="results_analysis_content")
877
  results_info = gr.Markdown("...", elem_id="results_info")
878
  results_back_button = gr.Button("...", elem_classes="gr-button-secondary")
879
 
880
  # --- Define LITE Output Lists ---
881
  all_blocks_lite = [ welcome_alias_block, menu_block, history_block, instructions_block, test_block, results_block ]
882
- text_outputs_lite = [ welcome_title, welcome_text, disclaimer_accordion, disclaimer_text, # Added Accordion back
883
  alias_title, alias_input, alias_confirm_button, alias_skip_button,
884
  menu_title, agent_info, start_sim_button, change_alias_button, view_history_button, reset_level_button, theme_toggle_btn,
885
  history_title, history_back_button, start_test_button,
@@ -894,12 +802,13 @@ with gr.Blocks(title=APP_TITLE, theme=theme, css=obsidian_css) as demo_lite:
894
  finish_test_outputs_lite = base_ui_outputs_lite + [instructions_title, instructions_text] + test_block_outputs_lite + results_specific_outputs_lite
895
 
896
  # --- Helper: LITE UI Update ---
 
897
  def update_ui_text_lite(state):
898
  alias = state.get('alias', None); level = state.get('level', 1)
899
  display_alias = alias if alias else get_text('text_anonymous')
900
  return [ # Ensure this list matches text_outputs_lite exactly
901
  gr.update(value=get_text('welcome_title')), gr.update(value=get_text('welcome_text')),
902
- gr.update(label=get_text('disclaimer_title')), gr.update(value=get_text('disclaimer_text')), # Update accordion too
903
  gr.update(value=get_text('alias_title')), gr.update(label=get_text('alias_label'), placeholder=get_text('alias_placeholder')),
904
  gr.update(value=get_text('alias_confirm_button')), gr.update(value=get_text('alias_skip_button')),
905
  gr.update(value=get_text('menu_title')), gr.update(value=get_text("menu_agent_info", alias=display_alias, level=level)),
@@ -917,13 +826,14 @@ with gr.Blocks(title=APP_TITLE, theme=theme, css=obsidian_css) as demo_lite:
917
  "instructions": instructions_block, "test": test_block, "results": results_block}
918
  updates = [gr.update(elem_classes=f"{state.get('theme','dark')}-theme")]
919
  for stage, block in block_map.items(): updates.append(gr.update(visible=vis_map.get(stage, False)))
920
- log_message("log_stage_transition", stage=target_stage)
 
921
  return updates
922
 
923
  # --- LITE Handlers ---
924
  # (confirm_alias_lite, skip_alias_lite, reset_state_lite, start_simulation_lite, start_test_lite,
925
  # run_trial_flow_lite, process_click_lite, finish_test_lite, go_to_menu, reset_level_confirmation,
926
- # toggle_theme_py_only functions remain the same as previous LITE version)
927
  def confirm_alias_lite(current_state, alias_str):
928
  state = deepcopy(current_state); alias = alias_str.strip()[:16] if isinstance(alias_str, str) else ""; feedback = ""
929
  if alias and 3 <= len(alias) <= 16 and alias.isalnum():
@@ -942,7 +852,6 @@ with gr.Blocks(title=APP_TITLE, theme=theme, css=obsidian_css) as demo_lite:
942
  elif not alias: state["stage"] = "menu"; state["alias"] = None; state["level"] = 1; feedback = get_text("alias_feedback_anon"); gr.Info(get_text("info_alias_anon")); log_message("log_alias_anon")
943
  else: state["stage"] = "welcome_alias"; feedback = get_text("alias_feedback_invalid"); gr.Warning(feedback)
944
  visibility = get_stage_visibility_lite(state["stage"], state); text = update_ui_text_lite(state); fb_update = gr.update(value=feedback)
945
- # Ensure the number of return values matches base_ui_outputs_lite
946
  return [state] + visibility + text + [fb_update]
947
 
948
  def skip_alias_lite(current_state): return confirm_alias_lite(current_state, "")
@@ -950,7 +859,7 @@ with gr.Blocks(title=APP_TITLE, theme=theme, css=obsidian_css) as demo_lite:
950
  def reset_state_lite(state_dict):
951
  state_dict.update({ "current_test_order": [], "current_test_key": None, "current_test_index": -1, "test_params": {}, "test_sequence": [],
952
  "test_expected_response": {}, "test_trial_index": 0, "current_stimulus_index": -1, "last_processed_index": -99,
953
- "test_stimulus": None, "test_user_response": None, "test_feedback": " ", "test_stimulus_show_time": None,
954
  "awaiting_input": False, "current_scores": {}, "current_trial_results": [],
955
  "round_results": {"analysis": {}, "metrics": {"per_module": {}, "overall": {}}},
956
  "positive_score": 0, "negative_score": 0, "performance_buffer_correct": deque(maxlen=10), })
@@ -965,7 +874,6 @@ with gr.Blocks(title=APP_TITLE, theme=theme, css=obsidian_css) as demo_lite:
965
  except Exception as e:
966
  log_message("error_prepare_test", level="CRITICAL", test_name=first_key, error=e); gr.Error(get_text("error_prepare_test", test_name=first_key, error=str(e))); state["stage"] = "menu"
967
  visibility = get_stage_visibility_lite("menu", state); text = update_ui_text_lite(state)
968
- # Match start_sim_outputs_lite: state, vis, text, fb, instr_title, instr_text
969
  return [state] + visibility + text + [gr.update()] * 3
970
  state.update({ "stage": "instructions", "current_test_key": first_key, "test_params": params, "level_before_results": level, "current_scores": {key: 0.0 for key in test_order}, })
971
  visibility = get_stage_visibility_lite("instructions", state); text = update_ui_text_lite(state); fb_update = gr.update()
@@ -990,11 +898,11 @@ with gr.Blocks(title=APP_TITLE, theme=theme, css=obsidian_css) as demo_lite:
990
  dummy_updates_needed = len(start_test_outputs_lite) - (1 + len(visibility) + len(text) + 1)
991
  return [state] + visibility + text + [gr.update()] + [gr.update()] * dummy_updates_needed
992
  next_stage = f"test_{test_key}"; test_name_loc = get_text(TEST_CONFIG_LITE[test_key]['loc_key']); test_dur = len(sequence)
993
- state.update({ "stage": next_stage, "test_sequence": sequence, "test_expected_response": expected, "test_trial_index": 0, "current_stimulus_index": -1, "last_processed_index": -99, "test_start_time": time.time(), "test_feedback": " ", "current_trial_results": [], "awaiting_input": False, "positive_score": 0, "negative_score": 0, })
994
  visibility = get_stage_visibility_lite(next_stage, state); text = update_ui_text_lite(state); fb_update = gr.update()
995
  instr_clr = [gr.update(value=""), gr.update(value="")]
996
  test_title_upd = gr.update(value=get_text("test_title", test_name=test_name_loc, level=level)); progress = gr.update(value=get_text("test_progress", current=0, total=test_dur)); score = gr.update(value=get_text("test_score", correct=0, errors=0)); timer = gr.update(value=get_text("test_timer", time=0))
997
- stim = gr.HTML(f"<p class='stimulus-core'>{get_text('test_init_message')}</p>"); feedback = gr.HTML(" "); buttons = [gr.update(visible=False)] * len(all_response_buttons)
998
  log_message("log_setup_complete", count=test_dur)
999
  return [state] + visibility + text + [fb_update] + instr_clr + [test_title_upd, progress, score, timer, stim, feedback] + buttons
1000
 
@@ -1009,33 +917,28 @@ with gr.Blocks(title=APP_TITLE, theme=theme, css=obsidian_css) as demo_lite:
1009
  iti = max(INTER_TRIAL_INTERVAL_MIN, params.get('iti_base',0.1) * (1+random.uniform(-0.3,0.3))); time.sleep(iti)
1010
  state["current_stimulus_index"] = idx; state["test_stimulus"] = seq[idx]
1011
  timeout = max(RESPONSE_WINDOW_TIMEOUT_MIN, params.get('response_timeout_base', 1.6) * (1+random.uniform(-0.15,0.15)))
1012
- state["awaiting_input"] = True; state["test_feedback"] = " "; state["test_stimulus_show_time"] = time.time(); state["last_processed_index"] = -99
1013
  stim_html = format_stimulus_html(state); feedback_html = gr.HTML(state["test_feedback"]); prog = gr.update(value=get_text("test_progress", current=idx + 1, total=test_dur)); score = gr.update(value=get_text("test_score", correct=state.get('positive_score',0), errors=state.get('negative_score',0))); timer = gr.update(value=get_text("test_timer", time=timeout)); buttons_upd, _ = get_test_buttons_visibility_and_labels(state)
1014
  yield [state, gr.update(value=stim_html), feedback_html, prog, score, timer] + buttons_upd
1015
  start_wait = time.time(); responded = False
1016
  while time.time() - start_wait < timeout:
1017
- # Check state potentially updated by click handler
1018
- # Accessing state directly here in loop might be slightly delayed
1019
- # It relies on process_click yielding the updated state back quickly.
1020
- if state.get("last_processed_index", -99) == idx:
1021
- responded = True
1022
- break
1023
  time.sleep(0.010)
1024
  if not responded and state.get("awaiting_input") and state.get("last_processed_index", -99) != idx:
1025
  log_message("log_timeout", idx=idx); state = process_response(state, None, is_timeout=True)
1026
  if state.get("last_processed_index", -99) == idx:
1027
- stim_upd = gr.update(value="<p class='stimulus-core'> </p>"); fb_upd = gr.HTML(state.get("test_feedback", " ")); score_upd = gr.update(value=get_text("test_score", correct=state.get('positive_score',0), errors=state.get('negative_score',0))); btn_upd = [gr.update(visible=False)] * len(all_response_buttons); timer_upd = gr.update(value="")
1028
  yield [state, stim_upd, fb_upd, gr.update(), score_upd, timer_upd] + btn_upd
1029
  time.sleep(state.get("test_params",{}).get("feedback_delay",0.1))
1030
- yield [state, gr.update(), gr.HTML(" "), gr.update(), gr.update(), gr.update()] + [gr.update()] * len(all_response_buttons)
1031
  state["test_trial_index"] += 1
1032
  log_message("log_test_completed", test_name=test_key, count=test_dur); state["awaiting_input"] = False
1033
- yield [state, gr.update(value=f"<p class='stimulus-core'>{get_text('test_complete_message')}</p>"), gr.HTML(" "), gr.update(), gr.update(value=get_text("test_score", correct=state.get('positive_score',0), errors=state.get('negative_score',0))), gr.update(value="")] + [gr.update(visible=False)]*len(all_response_buttons)
1034
  except Exception as e:
1035
  log_message("error_trial_flow", level="CRITICAL", test_name=state.get('current_test_key', '??'), error=e, traceback=traceback.format_exc())
1036
  gr.Error(get_text("error_trial_flow", test_name=state.get('current_test_key', '??'), error=str(e)))
1037
  state["stage"] = "menu"; state["awaiting_input"] = False
1038
- yield [state, gr.update(value=f"<p style='color:var(--color-error);'>{get_text('test_error_message')}</p>"), gr.HTML(" "), gr.update(), gr.update(), gr.update()] + [gr.update(visible=False)]*len(all_response_buttons)
1039
 
1040
  def process_click_lite(current_state, button_index):
1041
  state_click = deepcopy(current_state); idx = state_click.get("current_stimulus_index", -1); is_awaiting = state_click.get("awaiting_input", False); last_proc = state_click.get("last_processed_index", -99)
@@ -1047,10 +950,10 @@ with gr.Blocks(title=APP_TITLE, theme=theme, css=obsidian_css) as demo_lite:
1047
  if next_state.get("last_processed_index", -99) != idx:
1048
  log_message("log_click_state_warn", level="WARN", idx=idx)
1049
  yield [next_state] + [gr.update()] * (len(click_response_outputs_lite) - 1); return
1050
- stim_upd = gr.update(value="<p class='stimulus-core'> </p>"); fb_upd = gr.HTML(next_state.get("test_feedback", " ")); score_upd = gr.update(value=get_text("test_score", correct=next_state.get('positive_score',0), errors=next_state.get('negative_score',0))); btn_vis = [gr.update(visible=False)] * len(all_response_buttons); timer_clr = gr.update(value="")
1051
  yield [next_state, stim_upd, fb_upd, gr.update(), score_upd, timer_clr] + btn_vis
1052
  time.sleep(next_state.get("test_params",{}).get("feedback_delay",0.1))
1053
- yield [next_state, gr.update(), gr.HTML(" "), gr.update(), gr.update(), gr.update()] + [gr.update()]*len(all_response_buttons)
1054
 
1055
  def finish_test_lite(state_after_flow):
1056
  state = deepcopy(state_after_flow); test_key = state.get("current_test_key"); results = state.get("current_trial_results", [])
@@ -1116,14 +1019,13 @@ with gr.Blocks(title=APP_TITLE, theme=theme, css=obsidian_css) as demo_lite:
1116
 
1117
  # --- Helper needed for back buttons and reset_level ---
1118
  def go_to_menu(current_state):
1119
- state = deepcopy(current_state) # Use deepcopy here for safety
1120
  state["stage"] = "menu"; state = reset_state_lite(state)
1121
  visibility = get_stage_visibility_lite("menu", state); text = update_ui_text_lite(state); fb_update = gr.update(value="")
1122
- # Ensure the return list matches base_ui_outputs_lite
1123
  return [state] + visibility + text + [fb_update]
1124
 
1125
  def reset_level_confirmation(state):
1126
- # state is already a deepcopy from the lambda caller
1127
  alias = state.get("alias")
1128
  if alias:
1129
  state["level"] = 1
@@ -1171,7 +1073,23 @@ with gr.Blocks(title=APP_TITLE, theme=theme, css=obsidian_css) as demo_lite:
1171
  results_back_button.click(fn=lambda s: go_to_menu(deepcopy(s)), inputs=[app_state], outputs=base_ui_outputs_lite)
1172
  history_back_button.click(fn=lambda s: go_to_menu(deepcopy(s)), inputs=[app_state], outputs=base_ui_outputs_lite)
1173
 
1174
- # REMOVED .load() handler
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1175
 
1176
 
1177
  # --- Launch LITE ---
@@ -1182,5 +1100,5 @@ if __name__ == "__main__":
1182
  log_message("log_init_complete")
1183
  log_message("log_launching")
1184
  demo_lite.queue()
1185
- # Launch with SSR disabled
1186
- demo_lite.launch(server_name="0.0.0.0", prevent_thread_lock=True, ssr_mode=False)
 
1
  # -*- coding: utf-8 -*-
2
  # Protocolo Nexus Cognitivo - LITE (Obsidiana ES)
3
+ # Versión: 4.1.0-Lite-LoadFix
4
 
5
  import gradio as gr
6
  import random
 
31
  NUMPY_AVAILABLE = False
32
 
33
  # --- LITE Text Dictionary (Embedded) ---
34
+ # (TEXTOS_LITE dictionary remains the same)
35
  TEXTOS_LITE = {
36
  "app_title": "Protocolo Nexus Cognitivo LITE",
37
  "app_version": "4.1.0-Lite ES",
 
95
  "results_level_max_advance": "<h5><strong>Sintonía Pico Nivel {level}.</strong> Calibración adicional recomendada.</h5>",
96
  "results_level_regress": "<h5><strong>Disonancia Detectada.</strong> Recalibrando a Nivel {new_level}.</h5>",
97
  "results_level_maintain": "<h5>Resonancia Nivel {level} mantenida. (Req: >{threshold}% Alin., <{consistency_threshold:.3f} CV)</h5>",
98
+ "results_analysis_title": "Análisis Básico",
99
  "results_analysis_generating": "<p>Calculando...</p>",
100
  "results_analysis_error": "<p style='color:var(--color-error);'>Error análisis.</p>",
101
  "results_analysis_missing": "<p>Datos no disponibles.</p>",
102
+ "results_analysis_summary": "Módulo {test_name}: Alineación {precision:.1f}%. {rt_info}",
103
+ "results_rt_summary": "TR Medio: {avg_rt:.3f}s (CV: {rt_cv:.3f})",
104
  "results_info": "<p class='info-text'>Eco archivado. Estado flujo actualizado. Retornando.</p>",
105
  "results_back_button": "Retornar",
106
  # Popups / Info / Warnings / Errors
 
126
  "error_format_key_missing": "Error Txt: Falta '{missing}' en '{key}'.",
127
  "error_format_general": "Error Txt: {error} en '{key}'.",
128
  "error_trial_flow": "ERROR CRÍTICO durante flujo de prueba: {error}",
129
+ "error_finish_test_state": "ERROR: Estado inválido al finalizar test.",
130
  # Logging
131
  "log_init_start": "Iniciando Nexus LITE v{version}...",
132
  "log_init_results": "Archivo de ecos: {file}",
 
155
  "log_test_transition": "Transición a: {test_name}",
156
  "log_test_next_instr": "Mostrando instrucciones para {test_name}",
157
  "log_seq_complete": "Calibración Completa. Calculando resultados...",
158
+ "log_level_change": "Evaluación Nivel: Precision={acc:.1f}, Consist={cons:.3f}. Nuevo Nivel: {level}",
159
  "log_saving_results": "Archivando eco: Alias={alias}, LvlComp={level_completed}, NewLvl={new_level}",
160
  "log_save_results_success": "Eco archivado.",
161
  "log_save_profile": "Perfil guardado: {alias}, Nivel: {profile_level}",
 
279
  return False
280
 
281
  # --- CSS ---
282
+ # (Assume full obsidian_css string is included here)
283
  obsidian_css = """
284
+ :root { /* ... Full CSS from previous versions ... */ }
285
+ body.light-theme { /* ... Full CSS ... */ }
286
+ body { /* ... Full CSS ... */ }
287
+ /* ... All other CSS rules ... */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  footer { display: none !important; }
289
  """
290
 
 
311
  }
312
  AVAILABLE_TEST_KEYS_LITE = list(TEST_CONFIG_LITE.keys())
313
 
314
+ # --- Profile/Results Handling ---
315
+ # (load_agent_profile, save_agent_profile, save_result, read_history functions are correct)
316
  def load_agent_profile(alias):
317
  if not alias: return 1
318
  with profile_lock:
 
400
  return html
401
 
402
  # --- Difficulty Params (LITE) ---
403
+ # (get_difficulty_params function is correct)
404
  def get_difficulty_params(test_key, level):
405
  if test_key not in TEST_CONFIG_LITE: raise ValueError(f"Unknown test key: {test_key}")
406
  config = TEST_CONFIG_LITE[test_key]
 
417
  return params
418
 
419
  # --- Sequence Generation (LITE) ---
420
+ # (generate_attention_sequence, generate_inhibition_sequence, generate_memory_sequence functions are correct)
421
  def generate_attention_sequence(params):
422
  n = params['trials']; cue = params['cue']; target = params['target']
423
  sim_dist = params['similar_distractors']; other_dist = params['other_distractors']
 
501
  }
502
 
503
  # --- Instruction HTML Generation (LITE) ---
504
+ # (get_instructions_html function is correct)
505
  def get_instructions_html(test_key, params):
506
  test_config = TEST_CONFIG_LITE.get(test_key)
507
  if not test_config: return "<p>Error: Configuración Test No Encontrada.</p>"
 
528
  lines.extend(common_lines)
529
  return "".join(f"<p>{line}</p>" if not line.startswith("<br>") else line for line in lines)
530
 
531
+ # --- UI Formatting ---
532
+ # (format_stimulus_html and get_test_buttons_visibility_and_labels functions are correct)
533
  def format_stimulus_html(state):
534
  stim_raw = state.get("test_stimulus", ""); test_key = state.get("current_test_key", "")
535
  params = state.get("test_params", {}); is_awaiting = state.get("awaiting_input", False)
536
+ if not is_awaiting or stim_raw is None or stim_raw == "": return "<p class='stimulus-core'> </p>"
537
  display_text = ""; style_color = "var(--color-text-primary)"; extra_class = ""
538
  try:
539
  if test_key == "atencion": display_text = str(stim_raw)
 
552
  else: raise ValueError("Invalid mem stimulus")
553
  else: display_text = str(stim_raw) # Fallback
554
  display_text_safe = str(display_text).replace("&", "&").replace("<", "<").replace(">", ">")
555
+ if not display_text_safe or not display_text_safe.strip(): display_text_safe = " "
556
  return f"<p class='stimulus-core{extra_class}' style='color:{style_color};'>{display_text_safe}</p>"
557
  except Exception as e:
558
  log_message("error_stimulus_format", level="ERROR", error=e, traceback=traceback.format_exc())
 
587
  return final_updates, keys
588
 
589
  # --- Response Processing (LITE) ---
590
+ # (process_response function is correct)
591
  def process_response(state, key, is_timeout=False):
592
  with state_lock:
593
  current_state = deepcopy(state)
 
678
  return current_state
679
 
680
  # --- Score Calculation (LITE) ---
681
+ # (calculate_results_lite function is correct)
682
  def calculate_results_lite(test_key, trial_results):
683
  metrics = {'precision': 0, 'avg_rt': 0, 'rt_cv': 0, 'timeouts': 0}
684
  analysis_str = get_text("results_analysis_missing")
 
704
  rt_info = get_text("results_rt_summary", avg_rt=avg_rt, rt_cv=rt_cv)
705
  timeouts = sum(1 for r in trial_results if r.get('is_timeout') and not r.get('correct'))
706
  metrics['timeouts'] = timeouts
 
707
  test_name_loc = get_text(TEST_CONFIG_LITE[test_key]['loc_key'], default=test_key)
708
  analysis_str = get_text("results_analysis_summary", test_name=test_name_loc, precision=precision, rt_info=rt_info)
709
  log_message("log_score_calculated", test_name=test_key, acc=precision, cons=metrics['rt_cv'])
 
715
  "current_test_order": [], "current_test_key": None, "current_test_index": -1,
716
  "test_params": {}, "test_sequence": [], "test_expected_response": {},
717
  "test_trial_index": 0, "current_stimulus_index": -1, "last_processed_index": -99,
718
+ "test_stimulus": None, "test_user_response": None, "test_feedback": " ",
719
  "test_stimulus_show_time": None, "awaiting_input": False,
720
  "current_scores": {}, "current_trial_results": [],
721
  "round_results": {"analysis": {}, "metrics": {"per_module": {}, "overall": {}}},
 
724
  }
725
 
726
  theme = gr.themes.Soft(primary_hue="purple", secondary_hue="indigo").set(
727
+ body_background_fill="*color-background", body_text_color="*color-text-primary",
728
  )
729
 
730
  with gr.Blocks(title=APP_TITLE, theme=theme, css=obsidian_css) as demo_lite:
731
  app_state = gr.State(value=deepcopy(initial_state_lite))
732
  with gr.Column(elem_classes=f"{initial_state_lite.get('theme','dark')}-theme", elem_id="master-column") as master_column:
733
+ # --- Stage Blocks --- Define structure, set initial visibility via .load()
734
+ # Welcome / Alias Block
735
+ with gr.Column(visible=False, elem_classes="main-content-box", elem_id="welcome-alias-block") as welcome_alias_block:
736
+ welcome_title = gr.Markdown("...", elem_classes="block-title")
737
+ welcome_text = gr.Markdown("...")
738
  with gr.Accordion("...", open=False) as disclaimer_accordion: disclaimer_text = gr.Markdown("...")
739
  gr.HTML("<hr class='section-hr'>")
740
  alias_title = gr.Markdown("...", elem_classes="block-subtitle")
 
743
  with gr.Row():
744
  alias_confirm_button = gr.Button("...", elem_classes="gr-button-primary", scale=1)
745
  alias_skip_button = gr.Button("...", elem_classes="gr-button-secondary", scale=1)
746
+ # Menu Block
747
  with gr.Column(visible=False, elem_classes="main-content-box", elem_id="menu-block") as menu_block:
748
  menu_title = gr.Markdown("...", elem_classes="block-title")
749
  agent_info = gr.Markdown("...", elem_id="agent-info-menu")
 
752
  view_history_button = gr.Button("...", elem_classes="btn-menu gr-button-secondary")
753
  reset_level_button = gr.Button("...", elem_classes="btn-menu btn-reset")
754
  theme_toggle_btn = gr.Button("...", elem_classes="btn-menu gr-button-secondary")
755
+ # History Block
756
  with gr.Column(visible=False, elem_classes="main-content-box", elem_id="history-block") as history_block:
757
  history_title = gr.Markdown("...", elem_classes="block-title")
758
+ hist_html = gr.HTML("...", elem_id="history-html")
759
  history_back_button = gr.Button("...", elem_classes="gr-button-secondary")
760
+ # Instructions Block
761
  with gr.Column(visible=False, elem_classes="main-content-box", elem_id="instructions-block") as instructions_block:
762
  instructions_title = gr.Markdown("...", elem_classes="block-subtitle", elem_id="instructions_title")
763
+ instructions_text = gr.HTML("...", elem_id="instr-text")
764
  start_test_button = gr.Button("...", elem_classes="gr-button-primary")
765
+ # Test Block
766
  with gr.Column(visible=False, elem_classes="main-content-box", elem_id="test-block") as test_block:
767
  test_title = gr.Markdown("...", elem_classes="block-subtitle", elem_id="test_title")
768
  with gr.Row():
769
  progress_indicator = gr.Markdown("...", elem_id="progress-indicator")
770
  score_display = gr.Markdown("...", elem_id="score-display")
771
  timer_display = gr.Markdown("...", elem_id="timer-display")
772
+ stimulus_display = gr.HTML("<p class='stimulus-core'> </p>", elem_id="stimulus-display")
773
+ feedback_display = gr.HTML(" ", elem_id="feedback-display")
774
  with gr.Row(equal_height=False, elem_id="response-button-row"):
775
  response_btn_1 = gr.Button("", elem_classes="btn-response", scale=1, visible=False)
776
  response_btn_2 = gr.Button("", elem_classes="btn-response", scale=1, visible=False)
777
  all_response_buttons = [response_btn_1, response_btn_2]
778
+ # Results Block
779
  with gr.Column(visible=False, elem_classes="main-content-box", elem_id="results-block") as results_block:
780
  results_title = gr.Markdown("...", elem_classes="block-title")
781
  results_report_for = gr.Markdown("...", elem_classes="block-subtitle", elem_id="results_report_for")
782
  results_summary = gr.HTML("...", elem_id="results-summary")
783
  results_level_msg = gr.HTML("...", elem_id="results-level")
784
+ results_analysis_content = gr.HTML("...", elem_id="results_analysis_content")
785
  results_info = gr.Markdown("...", elem_id="results_info")
786
  results_back_button = gr.Button("...", elem_classes="gr-button-secondary")
787
 
788
  # --- Define LITE Output Lists ---
789
  all_blocks_lite = [ welcome_alias_block, menu_block, history_block, instructions_block, test_block, results_block ]
790
+ text_outputs_lite = [ welcome_title, welcome_text, disclaimer_accordion, disclaimer_text,
791
  alias_title, alias_input, alias_confirm_button, alias_skip_button,
792
  menu_title, agent_info, start_sim_button, change_alias_button, view_history_button, reset_level_button, theme_toggle_btn,
793
  history_title, history_back_button, start_test_button,
 
802
  finish_test_outputs_lite = base_ui_outputs_lite + [instructions_title, instructions_text] + test_block_outputs_lite + results_specific_outputs_lite
803
 
804
  # --- Helper: LITE UI Update ---
805
+ # (update_ui_text_lite function remains the same)
806
  def update_ui_text_lite(state):
807
  alias = state.get('alias', None); level = state.get('level', 1)
808
  display_alias = alias if alias else get_text('text_anonymous')
809
  return [ # Ensure this list matches text_outputs_lite exactly
810
  gr.update(value=get_text('welcome_title')), gr.update(value=get_text('welcome_text')),
811
+ gr.update(label=get_text('disclaimer_title')), gr.update(value=get_text('disclaimer_text')),
812
  gr.update(value=get_text('alias_title')), gr.update(label=get_text('alias_label'), placeholder=get_text('alias_placeholder')),
813
  gr.update(value=get_text('alias_confirm_button')), gr.update(value=get_text('alias_skip_button')),
814
  gr.update(value=get_text('menu_title')), gr.update(value=get_text("menu_agent_info", alias=display_alias, level=level)),
 
826
  "instructions": instructions_block, "test": test_block, "results": results_block}
827
  updates = [gr.update(elem_classes=f"{state.get('theme','dark')}-theme")]
828
  for stage, block in block_map.items(): updates.append(gr.update(visible=vis_map.get(stage, False)))
829
+ # Avoid logging during the initial load event if it causes issues
830
+ # log_message("log_stage_transition", stage=target_stage)
831
  return updates
832
 
833
  # --- LITE Handlers ---
834
  # (confirm_alias_lite, skip_alias_lite, reset_state_lite, start_simulation_lite, start_test_lite,
835
  # run_trial_flow_lite, process_click_lite, finish_test_lite, go_to_menu, reset_level_confirmation,
836
+ # toggle_theme_py_only functions remain the same as previous fixed version)
837
  def confirm_alias_lite(current_state, alias_str):
838
  state = deepcopy(current_state); alias = alias_str.strip()[:16] if isinstance(alias_str, str) else ""; feedback = ""
839
  if alias and 3 <= len(alias) <= 16 and alias.isalnum():
 
852
  elif not alias: state["stage"] = "menu"; state["alias"] = None; state["level"] = 1; feedback = get_text("alias_feedback_anon"); gr.Info(get_text("info_alias_anon")); log_message("log_alias_anon")
853
  else: state["stage"] = "welcome_alias"; feedback = get_text("alias_feedback_invalid"); gr.Warning(feedback)
854
  visibility = get_stage_visibility_lite(state["stage"], state); text = update_ui_text_lite(state); fb_update = gr.update(value=feedback)
 
855
  return [state] + visibility + text + [fb_update]
856
 
857
  def skip_alias_lite(current_state): return confirm_alias_lite(current_state, "")
 
859
  def reset_state_lite(state_dict):
860
  state_dict.update({ "current_test_order": [], "current_test_key": None, "current_test_index": -1, "test_params": {}, "test_sequence": [],
861
  "test_expected_response": {}, "test_trial_index": 0, "current_stimulus_index": -1, "last_processed_index": -99,
862
+ "test_stimulus": None, "test_user_response": None, "test_feedback": " ", "test_stimulus_show_time": None,
863
  "awaiting_input": False, "current_scores": {}, "current_trial_results": [],
864
  "round_results": {"analysis": {}, "metrics": {"per_module": {}, "overall": {}}},
865
  "positive_score": 0, "negative_score": 0, "performance_buffer_correct": deque(maxlen=10), })
 
874
  except Exception as e:
875
  log_message("error_prepare_test", level="CRITICAL", test_name=first_key, error=e); gr.Error(get_text("error_prepare_test", test_name=first_key, error=str(e))); state["stage"] = "menu"
876
  visibility = get_stage_visibility_lite("menu", state); text = update_ui_text_lite(state)
 
877
  return [state] + visibility + text + [gr.update()] * 3
878
  state.update({ "stage": "instructions", "current_test_key": first_key, "test_params": params, "level_before_results": level, "current_scores": {key: 0.0 for key in test_order}, })
879
  visibility = get_stage_visibility_lite("instructions", state); text = update_ui_text_lite(state); fb_update = gr.update()
 
898
  dummy_updates_needed = len(start_test_outputs_lite) - (1 + len(visibility) + len(text) + 1)
899
  return [state] + visibility + text + [gr.update()] + [gr.update()] * dummy_updates_needed
900
  next_stage = f"test_{test_key}"; test_name_loc = get_text(TEST_CONFIG_LITE[test_key]['loc_key']); test_dur = len(sequence)
901
+ state.update({ "stage": next_stage, "test_sequence": sequence, "test_expected_response": expected, "test_trial_index": 0, "current_stimulus_index": -1, "last_processed_index": -99, "test_start_time": time.time(), "test_feedback": " ", "current_trial_results": [], "awaiting_input": False, "positive_score": 0, "negative_score": 0, })
902
  visibility = get_stage_visibility_lite(next_stage, state); text = update_ui_text_lite(state); fb_update = gr.update()
903
  instr_clr = [gr.update(value=""), gr.update(value="")]
904
  test_title_upd = gr.update(value=get_text("test_title", test_name=test_name_loc, level=level)); progress = gr.update(value=get_text("test_progress", current=0, total=test_dur)); score = gr.update(value=get_text("test_score", correct=0, errors=0)); timer = gr.update(value=get_text("test_timer", time=0))
905
+ stim = gr.HTML(f"<p class='stimulus-core'>{get_text('test_init_message')}</p>"); feedback = gr.HTML(" "); buttons = [gr.update(visible=False)] * len(all_response_buttons)
906
  log_message("log_setup_complete", count=test_dur)
907
  return [state] + visibility + text + [fb_update] + instr_clr + [test_title_upd, progress, score, timer, stim, feedback] + buttons
908
 
 
917
  iti = max(INTER_TRIAL_INTERVAL_MIN, params.get('iti_base',0.1) * (1+random.uniform(-0.3,0.3))); time.sleep(iti)
918
  state["current_stimulus_index"] = idx; state["test_stimulus"] = seq[idx]
919
  timeout = max(RESPONSE_WINDOW_TIMEOUT_MIN, params.get('response_timeout_base', 1.6) * (1+random.uniform(-0.15,0.15)))
920
+ state["awaiting_input"] = True; state["test_feedback"] = " "; state["test_stimulus_show_time"] = time.time(); state["last_processed_index"] = -99
921
  stim_html = format_stimulus_html(state); feedback_html = gr.HTML(state["test_feedback"]); prog = gr.update(value=get_text("test_progress", current=idx + 1, total=test_dur)); score = gr.update(value=get_text("test_score", correct=state.get('positive_score',0), errors=state.get('negative_score',0))); timer = gr.update(value=get_text("test_timer", time=timeout)); buttons_upd, _ = get_test_buttons_visibility_and_labels(state)
922
  yield [state, gr.update(value=stim_html), feedback_html, prog, score, timer] + buttons_upd
923
  start_wait = time.time(); responded = False
924
  while time.time() - start_wait < timeout:
925
+ if state.get("last_processed_index", -99) == idx: responded = True; break
 
 
 
 
 
926
  time.sleep(0.010)
927
  if not responded and state.get("awaiting_input") and state.get("last_processed_index", -99) != idx:
928
  log_message("log_timeout", idx=idx); state = process_response(state, None, is_timeout=True)
929
  if state.get("last_processed_index", -99) == idx:
930
+ stim_upd = gr.update(value="<p class='stimulus-core'> </p>"); fb_upd = gr.HTML(state.get("test_feedback", " ")); score_upd = gr.update(value=get_text("test_score", correct=state.get('positive_score',0), errors=state.get('negative_score',0))); btn_upd = [gr.update(visible=False)] * len(all_response_buttons); timer_upd = gr.update(value="")
931
  yield [state, stim_upd, fb_upd, gr.update(), score_upd, timer_upd] + btn_upd
932
  time.sleep(state.get("test_params",{}).get("feedback_delay",0.1))
933
+ yield [state, gr.update(), gr.HTML(" "), gr.update(), gr.update(), gr.update()] + [gr.update()] * len(all_response_buttons)
934
  state["test_trial_index"] += 1
935
  log_message("log_test_completed", test_name=test_key, count=test_dur); state["awaiting_input"] = False
936
+ yield [state, gr.update(value=f"<p class='stimulus-core'>{get_text('test_complete_message')}</p>"), gr.HTML(" "), gr.update(), gr.update(value=get_text("test_score", correct=state.get('positive_score',0), errors=state.get('negative_score',0))), gr.update(value="")] + [gr.update(visible=False)]*len(all_response_buttons)
937
  except Exception as e:
938
  log_message("error_trial_flow", level="CRITICAL", test_name=state.get('current_test_key', '??'), error=e, traceback=traceback.format_exc())
939
  gr.Error(get_text("error_trial_flow", test_name=state.get('current_test_key', '??'), error=str(e)))
940
  state["stage"] = "menu"; state["awaiting_input"] = False
941
+ yield [state, gr.update(value=f"<p style='color:var(--color-error);'>{get_text('test_error_message')}</p>"), gr.HTML(" "), gr.update(), gr.update(), gr.update()] + [gr.update(visible=False)]*len(all_response_buttons)
942
 
943
  def process_click_lite(current_state, button_index):
944
  state_click = deepcopy(current_state); idx = state_click.get("current_stimulus_index", -1); is_awaiting = state_click.get("awaiting_input", False); last_proc = state_click.get("last_processed_index", -99)
 
950
  if next_state.get("last_processed_index", -99) != idx:
951
  log_message("log_click_state_warn", level="WARN", idx=idx)
952
  yield [next_state] + [gr.update()] * (len(click_response_outputs_lite) - 1); return
953
+ stim_upd = gr.update(value="<p class='stimulus-core'> </p>"); fb_upd = gr.HTML(next_state.get("test_feedback", " ")); score_upd = gr.update(value=get_text("test_score", correct=next_state.get('positive_score',0), errors=next_state.get('negative_score',0))); btn_vis = [gr.update(visible=False)] * len(all_response_buttons); timer_clr = gr.update(value="")
954
  yield [next_state, stim_upd, fb_upd, gr.update(), score_upd, timer_clr] + btn_vis
955
  time.sleep(next_state.get("test_params",{}).get("feedback_delay",0.1))
956
+ yield [next_state, gr.update(), gr.HTML(" "), gr.update(), gr.update(), gr.update()] + [gr.update()]*len(all_response_buttons)
957
 
958
  def finish_test_lite(state_after_flow):
959
  state = deepcopy(state_after_flow); test_key = state.get("current_test_key"); results = state.get("current_trial_results", [])
 
1019
 
1020
  # --- Helper needed for back buttons and reset_level ---
1021
  def go_to_menu(current_state):
1022
+ state = deepcopy(current_state)
1023
  state["stage"] = "menu"; state = reset_state_lite(state)
1024
  visibility = get_stage_visibility_lite("menu", state); text = update_ui_text_lite(state); fb_update = gr.update(value="")
1025
+ # Return list matching base_ui_outputs_lite
1026
  return [state] + visibility + text + [fb_update]
1027
 
1028
  def reset_level_confirmation(state):
 
1029
  alias = state.get("alias")
1030
  if alias:
1031
  state["level"] = 1
 
1073
  results_back_button.click(fn=lambda s: go_to_menu(deepcopy(s)), inputs=[app_state], outputs=base_ui_outputs_lite)
1074
  history_back_button.click(fn=lambda s: go_to_menu(deepcopy(s)), inputs=[app_state], outputs=base_ui_outputs_lite)
1075
 
1076
+ # --- Initial UI Load Handler ---
1077
+ # Define the handler function
1078
+ def populate_initial_ui(state):
1079
+ # Get visibility updates for the initial stage
1080
+ visibility_updates = get_stage_visibility_lite(state['stage'], state)
1081
+ # Get text updates for all components
1082
+ text_updates = update_ui_text_lite(state)
1083
+ # Need state + visibility updates + text updates + alias feedback update
1084
+ # Must match the outputs list below
1085
+ return [state] + visibility_updates + text_updates + [gr.update()] # Return empty update for alias feedback
1086
+
1087
+ # Bind the handler to the .load() event
1088
+ demo_lite.load(
1089
+ fn=populate_initial_ui,
1090
+ inputs=[app_state],
1091
+ outputs=base_ui_outputs_lite # Use the most comprehensive list here
1092
+ )
1093
 
1094
 
1095
  # --- Launch LITE ---
 
1100
  log_message("log_init_complete")
1101
  log_message("log_launching")
1102
  demo_lite.queue()
1103
+ # Launch with SSR explicitly disabled
1104
+ demo_lite.launch(server_name="0.0.0.0", prevent_thread_lock=True, ssr_mode=False)