GuglielmoTor commited on
Commit
5188732
·
verified ·
1 Parent(s): f364ce4

Update ui/okr_ui_generator.py

Browse files
Files changed (1) hide show
  1. ui/okr_ui_generator.py +78 -99
ui/okr_ui_generator.py CHANGED
@@ -10,17 +10,28 @@ def create_enhanced_okr_tab():
10
  Creates a modern, visually appealing OKR tab with improved layout and styling.
11
  This version includes full support for Gradio's dark mode and a redesigned,
12
  more readable objective header by using a robust CSS variable-based theming approach.
 
13
 
14
  Returns:
15
  gr.HTML: The Gradio HTML component that will display the formatted OKRs.
16
  """
17
 
18
  # --- Refactored CSS for Robust Dark Mode Theming ---
19
- # This version uses CSS variables for both light and dark modes, which is a more
20
- # reliable and maintainable way to handle themes. Instead of overriding dozens of
21
- # individual CSS rules, we just redefine the color variables once.
22
  okr_custom_css = """
23
  <style>
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  /* ----------------------------------------- */
25
  /* --- THEME & COLOR VARIABLES --- */
26
  /* ----------------------------------------- */
@@ -216,39 +227,50 @@ def create_enhanced_okr_tab():
216
  </style>
217
  <script>
218
  // This script is responsible for syncing the theme with the main Gradio app.
219
- // It checks if the parent document has the 'dark' class and applies it here.
220
- // A MutationObserver watches for real-time changes when the user toggles the theme.
221
  function syncThemeWithParent() {
222
  try {
223
  const parentHtml = window.parent.document.querySelector('html');
224
- if (parentHtml) {
225
- if (parentHtml.classList.contains('dark')) {
226
- document.documentElement.classList.add('dark');
 
 
 
 
227
  } else {
228
- document.documentElement.classList.remove('dark');
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  }
230
  }
231
  } catch (e) {
232
  console.error("Theme Sync Error:", e);
 
 
 
 
 
 
233
  }
234
  }
235
 
236
- try {
237
- const observer = new MutationObserver(syncThemeWithParent);
238
- const parentHtml = window.parent.document.querySelector('html');
239
- if (parentHtml) {
240
- observer.observe(parentHtml, { attributes: true, attributeFilter: ['class'] });
241
- }
242
- } catch (e) {
243
- console.error("Could not set up theme MutationObserver:", e);
244
- }
245
 
246
- // Run sync on load and after short delays as a fallback.
247
- document.addEventListener('DOMContentLoaded', () => {
248
- syncThemeWithParent();
249
- setTimeout(syncThemeWithParent, 50);
250
- setTimeout(syncThemeWithParent, 250);
251
- });
252
  </script>
253
  """
254
 
@@ -265,15 +287,9 @@ def create_enhanced_okr_tab():
265
  return okr_display_html
266
 
267
  def get_initial_okr_display() -> str:
268
- """
269
- Returns the initial HTML display for the OKR tab, showing a loading state.
270
-
271
- Returns:
272
- str: HTML string for the initial OKR display.
273
- """
274
- # This HTML uses the CSS classes defined above. The styles are applied
275
- # automatically based on the variables, which are theme-aware.
276
  return """
 
277
  <div class="okr-container">
278
  <div class="okr-header">
279
  <div class="okr-title">
@@ -304,20 +320,12 @@ def get_initial_okr_display() -> str:
304
  """
305
 
306
  def format_okrs_for_enhanced_display(reconstruction_cache: dict) -> str:
307
- """
308
- Enhanced formatting function that creates beautiful HTML for OKR display
309
- from the reconstruction cache dictionary.
310
-
311
- Args:
312
- reconstruction_cache (dict): The reconstruction cache containing reconstructed data.
313
-
314
- Returns:
315
- str: A comprehensive HTML string representing the OKRs, or an empty state HTML.
316
- """
317
  if not reconstruction_cache:
318
  logger.warning("No reconstruction cache found for display.")
319
  return get_empty_okr_state()
320
 
 
321
  for report_id, report_data in reconstruction_cache.items():
322
  if isinstance(report_data, dict) and 'actionable_okrs' in report_data:
323
  raw_results = {'actionable_okrs': report_data['actionable_okrs']}
@@ -333,21 +341,17 @@ def format_okrs_for_enhanced_display(reconstruction_cache: dict) -> str:
333
  logger.info("No OKRs found in 'actionable_okrs' list.")
334
  return get_empty_okr_state()
335
 
 
336
  total_objectives = len(okrs_list)
337
  total_key_results = sum(len(okr.get('key_results', [])) for okr in okrs_list)
338
- total_tasks = sum(
339
- len(kr.get('tasks', []))
340
- for okr in okrs_list
341
- for kr in okr.get('key_results', [])
342
- )
343
- high_priority_tasks = sum(
344
- 1 for okr in okrs_list
345
- for kr in okr.get('key_results', [])
346
- for task in kr.get('tasks', [])
347
- if task.get('priority', '').lower() == 'high'
348
- )
349
-
350
- html_parts = [f"""
351
  <div class="okr-container">
352
  <div class="okr-header">
353
  <div class="okr-title">
@@ -363,7 +367,7 @@ def format_okrs_for_enhanced_display(reconstruction_cache: dict) -> str:
363
  <div class="stat-card"><div class="stat-number">{high_priority_tasks}</div><div class="stat-label">High Priority</div></div>
364
  </div>
365
  <div class="okr-content">
366
- """]
367
 
368
  for okr_idx, okr_data in enumerate(okrs_list):
369
  if not isinstance(okr_data, dict):
@@ -395,29 +399,22 @@ def format_okrs_for_enhanced_display(reconstruction_cache: dict) -> str:
395
  html_parts.append('<div class="empty-state">No key results defined for this objective.</div>')
396
  else:
397
  for kr_idx, kr_data in enumerate(key_results):
398
- if not isinstance(kr_data, dict):
399
- logger.warning(f"Key Result item at index {kr_idx} is not a dictionary, skipping.")
400
- continue
401
 
402
  kr_desc = kr_data.get('description', f"Unnamed Key Result {kr_idx + 1}")
403
- target_metric = kr_data.get('target_metric', '')
404
- target_value = kr_data.get('target_value', '')
405
- kr_type = kr_data.get('key_result_type', '')
406
- data_subject = kr_data.get('data_subject', '')
407
-
408
  html_parts.append(f"""
409
  <div class="key-result">
410
  <div class="kr-header">
411
  <div class="kr-title">Key Result {kr_idx + 1}: {kr_desc}</div>
412
  <div class="kr-metrics">
413
  """)
414
- if target_metric and target_value:
415
- html_parts.append(f'<div class="kr-metric">Target: {target_metric} → {target_value}</div>')
416
- if kr_type:
417
- html_parts.append(f'<div class="kr-metric">Type: {kr_type}</div>')
418
- if data_subject:
419
- html_parts.append(f'<div class="kr-metric">Data Subject: {data_subject}</div>')
420
- html_parts.append('</div></div>') # Close kr-metrics and kr-header
421
 
422
  tasks = kr_data.get('tasks', [])
423
  if tasks and isinstance(tasks, list):
@@ -427,7 +424,6 @@ def format_okrs_for_enhanced_display(reconstruction_cache: dict) -> str:
427
  task_desc = task_data.get('description', f"Unnamed Task {task_idx + 1}")
428
  priority = task_data.get('priority', 'Medium').lower()
429
  priority_class = f"priority-{priority}" if priority in ['high', 'medium', 'low'] else 'priority-medium'
430
-
431
  html_parts.append(f"""
432
  <div class="task-item">
433
  <div class="task-header">
@@ -441,37 +437,22 @@ def format_okrs_for_enhanced_display(reconstruction_cache: dict) -> str:
441
  <div class="task-detail-item"><span class="task-detail-label">Responsible:</span><span>{task_data.get('responsible_party', 'N/A')}</span></div>
442
  </div>
443
  """)
444
-
445
- detail_lines = []
446
- if task_data.get('deliverable'): detail_lines.append(f'<strong>Objective/Deliverable:</strong> {task_data.get("deliverable")}')
447
- if task_data.get('success_criteria_metrics'): detail_lines.append(f'<strong>Success Metrics:</strong> {task_data.get("success_criteria_metrics")}')
448
- if task_data.get('why'): detail_lines.append(f'<strong>Rationale:</strong> {task_data.get("why")}')
449
- if task_data.get('priority_justification'): detail_lines.append(f'<strong>Priority Justification:</strong> {task_data.get("priority_justification")}')
450
- if task_data.get('dependencies'): detail_lines.append(f'<strong>Dependencies:</strong> {task_data.get("dependencies")}')
451
-
452
  if detail_lines:
453
- html_parts.append('<div class="task-description">')
454
- html_parts.append('<br>'.join(detail_lines))
455
- html_parts.append('</div>')
456
-
457
- html_parts.append('</div>') # Close task-item
458
- html_parts.append('</div>') # Close tasks-section
459
  else:
460
- html_parts.append('<div class="tasks-section"><div class="empty-state-small">No tasks defined for this Key Result.</div></div>')
461
- html_parts.append('</div>') # Close key-result
462
- html_parts.append('</div></div>') # Close key-results-container and okr-objective
463
- html_parts.append('</div></div>') # Close okr-content and okr-container
464
  return ''.join(html_parts)
465
 
466
-
467
  def get_empty_okr_state() -> str:
468
- """
469
- Returns empty state HTML for when no OKRs are available.
470
-
471
- Returns:
472
- str: HTML string for the empty OKR state.
473
- """
474
  return """
 
475
  <div class="okr-container">
476
  <div class="okr-header">
477
  <div class="okr-title">
@@ -480,14 +461,12 @@ def get_empty_okr_state() -> str:
480
  </div>
481
  <div class="okr-subtitle">Intelligent objectives and key results based on your LinkedIn analytics</div>
482
  </div>
483
-
484
  <div class="okr-stats-bar">
485
  <div class="stat-card"><div class="stat-number">0</div><div class="stat-label">Objectives</div></div>
486
  <div class="stat-card"><div class="stat-number">0</div><div class="stat-label">Key Results</div></div>
487
  <div class="stat-card"><div class="stat-number">0</div><div class="stat-label">Tasks</div></div>
488
  <div class="stat-card"><div class="stat-number">0</div><div class="stat-label">High Priority</div></div>
489
  </div>
490
-
491
  <div class="okr-content">
492
  <div class="empty-state">
493
  <div class="empty-state-icon">📋</div>
 
10
  Creates a modern, visually appealing OKR tab with improved layout and styling.
11
  This version includes full support for Gradio's dark mode and a redesigned,
12
  more readable objective header by using a robust CSS variable-based theming approach.
13
+ Includes a visual debugger for theme detection.
14
 
15
  Returns:
16
  gr.HTML: The Gradio HTML component that will display the formatted OKRs.
17
  """
18
 
19
  # --- Refactored CSS for Robust Dark Mode Theming ---
 
 
 
20
  okr_custom_css = """
21
  <style>
22
+ /* --- Debugger Style --- */
23
+ #theme-debug {
24
+ position: fixed;
25
+ top: 10px;
26
+ left: 10px;
27
+ padding: 5px 10px;
28
+ border-radius: 5px;
29
+ font-family: sans-serif;
30
+ font-size: 12px;
31
+ font-weight: bold;
32
+ z-index: 9999;
33
+ }
34
+
35
  /* ----------------------------------------- */
36
  /* --- THEME & COLOR VARIABLES --- */
37
  /* ----------------------------------------- */
 
227
  </style>
228
  <script>
229
  // This script is responsible for syncing the theme with the main Gradio app.
230
+ // It now includes a visual debugger to confirm its status.
 
231
  function syncThemeWithParent() {
232
  try {
233
  const parentHtml = window.parent.document.querySelector('html');
234
+ const iframeHtml = document.documentElement;
235
+ const debugDiv = document.getElementById('theme-debug');
236
+
237
+ if (parentHtml && iframeHtml) {
238
+ const isDark = parentHtml.classList.contains('dark');
239
+ if (isDark) {
240
+ iframeHtml.classList.add('dark');
241
  } else {
242
+ iframeHtml.classList.remove('dark');
243
+ }
244
+
245
+ // Update the visual debugger
246
+ if (debugDiv) {
247
+ if (isDark) {
248
+ debugDiv.textContent = 'Dark';
249
+ debugDiv.style.backgroundColor = '#3b82f6'; // Blue
250
+ debugDiv.style.color = 'white';
251
+ } else {
252
+ debugDiv.textContent = 'Light';
253
+ debugDiv.style.backgroundColor = '#dc2626'; // Red
254
+ debugDiv.style.color = 'white';
255
+ }
256
  }
257
  }
258
  } catch (e) {
259
  console.error("Theme Sync Error:", e);
260
+ const debugDiv = document.getElementById('theme-debug');
261
+ if (debugDiv) {
262
+ debugDiv.textContent = 'Error';
263
+ debugDiv.style.backgroundColor = 'yellow';
264
+ debugDiv.style.color = 'black';
265
+ }
266
  }
267
  }
268
 
269
+ // Use a more reliable polling mechanism instead of MutationObserver for wider compatibility.
270
+ setInterval(syncThemeWithParent, 250);
 
 
 
 
 
 
 
271
 
272
+ // Run on load.
273
+ document.addEventListener('DOMContentLoaded', syncThemeWithParent);
 
 
 
 
274
  </script>
275
  """
276
 
 
287
  return okr_display_html
288
 
289
  def get_initial_okr_display() -> str:
290
+ """Returns the initial HTML display for the OKR tab, showing a loading state."""
 
 
 
 
 
 
 
291
  return """
292
+ <div id="theme-debug">Loading...</div>
293
  <div class="okr-container">
294
  <div class="okr-header">
295
  <div class="okr-title">
 
320
  """
321
 
322
  def format_okrs_for_enhanced_display(reconstruction_cache: dict) -> str:
323
+ """Enhanced formatting function that creates beautiful HTML for OKR display."""
 
 
 
 
 
 
 
 
 
324
  if not reconstruction_cache:
325
  logger.warning("No reconstruction cache found for display.")
326
  return get_empty_okr_state()
327
 
328
+ # (Code to extract actionable_okrs... remains the same)
329
  for report_id, report_data in reconstruction_cache.items():
330
  if isinstance(report_data, dict) and 'actionable_okrs' in report_data:
331
  raw_results = {'actionable_okrs': report_data['actionable_okrs']}
 
341
  logger.info("No OKRs found in 'actionable_okrs' list.")
342
  return get_empty_okr_state()
343
 
344
+ # (Code to calculate stats... remains the same)
345
  total_objectives = len(okrs_list)
346
  total_key_results = sum(len(okr.get('key_results', [])) for okr in okrs_list)
347
+ total_tasks = sum(len(kr.get('tasks', [])) for okr in okrs_list for kr in okr.get('key_results', []))
348
+ high_priority_tasks = sum(1 for okr in okrs_list for kr in okr.get('key_results', []) for task in kr.get('tasks', []) if task.get('priority', '').lower() == 'high')
349
+
350
+ # Start HTML with the debugger div
351
+ html_parts = ["""<div id="theme-debug"></div>"""]
352
+
353
+ # --- The rest of the HTML generation logic remains identical ---
354
+ html_parts.append(f"""
 
 
 
 
 
355
  <div class="okr-container">
356
  <div class="okr-header">
357
  <div class="okr-title">
 
367
  <div class="stat-card"><div class="stat-number">{high_priority_tasks}</div><div class="stat-label">High Priority</div></div>
368
  </div>
369
  <div class="okr-content">
370
+ """)
371
 
372
  for okr_idx, okr_data in enumerate(okrs_list):
373
  if not isinstance(okr_data, dict):
 
399
  html_parts.append('<div class="empty-state">No key results defined for this objective.</div>')
400
  else:
401
  for kr_idx, kr_data in enumerate(key_results):
402
+ if not isinstance(kr_data, dict): continue
 
 
403
 
404
  kr_desc = kr_data.get('description', f"Unnamed Key Result {kr_idx + 1}")
 
 
 
 
 
405
  html_parts.append(f"""
406
  <div class="key-result">
407
  <div class="kr-header">
408
  <div class="kr-title">Key Result {kr_idx + 1}: {kr_desc}</div>
409
  <div class="kr-metrics">
410
  """)
411
+ if kr_data.get('target_metric') and kr_data.get('target_value'):
412
+ html_parts.append(f'<div class="kr-metric">Target: {kr_data.get("target_metric")} → {kr_data.get("target_value")}</div>')
413
+ if kr_data.get('key_result_type'):
414
+ html_parts.append(f'<div class="kr-metric">Type: {kr_data.get("key_result_type")}</div>')
415
+ if kr_data.get('data_subject'):
416
+ html_parts.append(f'<div class="kr-metric">Data Subject: {kr_data.get("data_subject")}</div>')
417
+ html_parts.append('</div></div>')
418
 
419
  tasks = kr_data.get('tasks', [])
420
  if tasks and isinstance(tasks, list):
 
424
  task_desc = task_data.get('description', f"Unnamed Task {task_idx + 1}")
425
  priority = task_data.get('priority', 'Medium').lower()
426
  priority_class = f"priority-{priority}" if priority in ['high', 'medium', 'low'] else 'priority-medium'
 
427
  html_parts.append(f"""
428
  <div class="task-item">
429
  <div class="task-header">
 
437
  <div class="task-detail-item"><span class="task-detail-label">Responsible:</span><span>{task_data.get('responsible_party', 'N/A')}</span></div>
438
  </div>
439
  """)
440
+ detail_lines = [f'<strong>{k.replace("_", " ").title()}:</strong> {v}' for k, v in task_data.items() if k in ['deliverable', 'success_criteria_metrics', 'why', 'priority_justification', 'dependencies'] and v]
 
 
 
 
 
 
 
441
  if detail_lines:
442
+ html_parts.append(f'<div class="task-description">{"<br>".join(detail_lines)}</div>')
443
+ html_parts.append('</div>')
444
+ html_parts.append('</div>')
 
 
 
445
  else:
446
+ html_parts.append('<div class="tasks-section"><div class="empty-state-small">No tasks defined.</div></div>')
447
+ html_parts.append('</div>')
448
+ html_parts.append('</div></div>')
449
+ html_parts.append('</div></div>')
450
  return ''.join(html_parts)
451
 
 
452
  def get_empty_okr_state() -> str:
453
+ """Returns empty state HTML for when no OKRs are available."""
 
 
 
 
 
454
  return """
455
+ <div id="theme-debug"></div>
456
  <div class="okr-container">
457
  <div class="okr-header">
458
  <div class="okr-title">
 
461
  </div>
462
  <div class="okr-subtitle">Intelligent objectives and key results based on your LinkedIn analytics</div>
463
  </div>
 
464
  <div class="okr-stats-bar">
465
  <div class="stat-card"><div class="stat-number">0</div><div class="stat-label">Objectives</div></div>
466
  <div class="stat-card"><div class="stat-number">0</div><div class="stat-label">Key Results</div></div>
467
  <div class="stat-card"><div class="stat-number">0</div><div class="stat-label">Tasks</div></div>
468
  <div class="stat-card"><div class="stat-number">0</div><div class="stat-label">High Priority</div></div>
469
  </div>
 
470
  <div class="okr-content">
471
  <div class="empty-state">
472
  <div class="empty-state-icon">📋</div>