aamanlamba Claude commited on
Commit
ff97939
·
1 Parent(s): 0510038

Fix Mermaid visualization rendering and SSR mode

Browse files

- Fixed Mermaid.js rendering to properly display graphs (not just text)
- Added unique IDs for each diagram to prevent conflicts
- Disabled SSR mode to fix Spaces hot-reloading error
- Updated tests to match new HTML structure

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +47 -23
  2. tests/test_app.py +4 -4
app.py CHANGED
@@ -45,23 +45,54 @@ EXPORT_FORMATS = ["OpenLineage", "Collibra", "Purview", "Alation"]
45
  # ============================================================================
46
 
47
  def render_mermaid(viz_code: str) -> str:
48
- """Wrap mermaid source in HTML and initialize mermaid when the HTML is inserted."""
 
49
  safe_viz = viz_code.replace("<", "&lt;").replace(">", "&gt;")
50
- init_script = (
51
- "<script>"
52
- "(function(){"
53
- "function run(){"
54
- " if(window.mermaid){ mermaid.init(undefined, document.querySelectorAll('.mermaid')); }"
55
- " else { setTimeout(run,50); }"
56
- " } run();})();"
57
- "</script>"
58
- )
59
- return f"""
60
- <div style="background: white; padding: 20px; border-radius: 8px; overflow: auto;">
61
- <div class="mermaid">{safe_viz}</div>
62
  </div>
63
- {init_script}
64
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
 
67
  # ============================================================================
@@ -505,13 +536,6 @@ with gr.Blocks(
505
  mcp_status = gr.Textbox(label="Connection Status", interactive=False)
506
  test_btn.click(fn=test_mcp_connection, inputs=[mcp_server, mcp_api_key], outputs=[mcp_status])
507
 
508
- # Mermaid.js loader
509
- gr.HTML(
510
- value='<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>'
511
- '<script>mermaid.initialize({startOnLoad:false, theme:"default"});</script>',
512
- visible=False
513
- )
514
-
515
  # Main Tabs
516
  with gr.Tabs():
517
  # Tab 1: Text/File Input
@@ -718,4 +742,4 @@ with gr.Blocks(
718
 
719
  # Launch
720
  if __name__ == "__main__":
721
- demo.launch()
 
45
  # ============================================================================
46
 
47
  def render_mermaid(viz_code: str) -> str:
48
+ """Render mermaid diagram using embedded script with proper initialization."""
49
+ # Escape HTML-sensitive characters but preserve newlines for mermaid
50
  safe_viz = viz_code.replace("<", "&lt;").replace(">", "&gt;")
51
+
52
+ # Generate unique ID for this diagram
53
+ import random
54
+ diagram_id = f"mermaid-{random.randint(10000, 99999)}"
55
+
56
+ # Complete HTML with embedded Mermaid.js and proper rendering
57
+ html = f'''
58
+ <div id="{diagram_id}-container" style="background: white; padding: 20px; border-radius: 8px; min-height: 200px;">
59
+ <div id="{diagram_id}" class="mermaid">
60
+ {safe_viz}
61
+ </div>
 
62
  </div>
63
+ <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
64
+ <script>
65
+ (function() {{
66
+ // Initialize mermaid with configuration
67
+ if (typeof mermaid !== 'undefined') {{
68
+ mermaid.initialize({{
69
+ startOnLoad: false,
70
+ theme: 'default',
71
+ securityLevel: 'loose',
72
+ flowchart: {{
73
+ useMaxWidth: true,
74
+ htmlLabels: true,
75
+ curve: 'basis'
76
+ }}
77
+ }});
78
+
79
+ // Render the specific diagram
80
+ try {{
81
+ const element = document.getElementById('{diagram_id}');
82
+ if (element) {{
83
+ mermaid.init(undefined, element);
84
+ }}
85
+ }} catch (e) {{
86
+ console.error('Mermaid rendering error:', e);
87
+ }}
88
+ }} else {{
89
+ // Retry if mermaid not loaded yet
90
+ setTimeout(arguments.callee, 100);
91
+ }}
92
+ }})();
93
+ </script>
94
+ '''
95
+ return html
96
 
97
 
98
  # ============================================================================
 
536
  mcp_status = gr.Textbox(label="Connection Status", interactive=False)
537
  test_btn.click(fn=test_mcp_connection, inputs=[mcp_server, mcp_api_key], outputs=[mcp_status])
538
 
 
 
 
 
 
 
 
539
  # Main Tabs
540
  with gr.Tabs():
541
  # Tab 1: Text/File Input
 
742
 
743
  # Launch
744
  if __name__ == "__main__":
745
+ demo.launch(ssr_mode=False)
tests/test_app.py CHANGED
@@ -12,7 +12,7 @@ class TestLineageExtractors(unittest.TestCase):
12
  def test_render_mermaid_wraps_and_inits(self):
13
  viz = "graph TD\n A --> B"
14
  html = render_mermaid(viz)
15
- self.assertIn('<div class="mermaid">', html)
16
  self.assertIn('graph TD', html)
17
  self.assertIn('mermaid.init', html)
18
 
@@ -22,7 +22,7 @@ class TestLineageExtractors(unittest.TestCase):
22
  html, summary = extract_lineage_from_text(sample_json, "Custom JSON", "Mermaid")
23
  self.assertIsInstance(html, str)
24
  self.assertIsInstance(summary, str)
25
- self.assertIn('<div class="mermaid">', html)
26
  self.assertIn('Parsed', summary)
27
 
28
  def test_extract_lineage_from_text_empty_input(self):
@@ -34,12 +34,12 @@ class TestLineageExtractors(unittest.TestCase):
34
 
35
  def test_extract_lineage_from_bigquery_returns_html_and_summary(self):
36
  html, summary = extract_lineage_from_bigquery("proj", "SELECT 1", "key", "Mermaid")
37
- self.assertIn('<div class="mermaid">', html)
38
  self.assertIn('BigQuery', summary)
39
 
40
  def test_extract_lineage_from_url_returns_html_and_summary(self):
41
  html, summary = extract_lineage_from_url("https://example.com", "Mermaid")
42
- self.assertIn('<div class="mermaid">', html)
43
  # Summary can be either 'Lineage' or 'Parsed' depending on response
44
  self.assertTrue('Lineage' in summary or 'Parsed' in summary)
45
 
 
12
  def test_render_mermaid_wraps_and_inits(self):
13
  viz = "graph TD\n A --> B"
14
  html = render_mermaid(viz)
15
+ self.assertIn('class="mermaid"', html)
16
  self.assertIn('graph TD', html)
17
  self.assertIn('mermaid.init', html)
18
 
 
22
  html, summary = extract_lineage_from_text(sample_json, "Custom JSON", "Mermaid")
23
  self.assertIsInstance(html, str)
24
  self.assertIsInstance(summary, str)
25
+ self.assertIn('class="mermaid"', html)
26
  self.assertIn('Parsed', summary)
27
 
28
  def test_extract_lineage_from_text_empty_input(self):
 
34
 
35
  def test_extract_lineage_from_bigquery_returns_html_and_summary(self):
36
  html, summary = extract_lineage_from_bigquery("proj", "SELECT 1", "key", "Mermaid")
37
+ self.assertIn('class="mermaid"', html)
38
  self.assertIn('BigQuery', summary)
39
 
40
  def test_extract_lineage_from_url_returns_html_and_summary(self):
41
  html, summary = extract_lineage_from_url("https://example.com", "Mermaid")
42
+ self.assertIn('class="mermaid"', html)
43
  # Summary can be either 'Lineage' or 'Parsed' depending on response
44
  self.assertTrue('Lineage' in summary or 'Parsed' in summary)
45