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

Fix Mermaid rendering with mermaid.ink SVG service

Browse files

- Use mermaid.ink external service to render diagrams as SVG images
- This avoids JavaScript execution issues in Gradio/HuggingFace Spaces
- Add link to Mermaid Live Editor for interactive editing
- Add spaces>=0.44.0 to fix hot-reloading startup warning
- Update tests to match new HTML structure

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

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

Files changed (3) hide show
  1. app.py +21 -41
  2. requirements.txt +1 -0
  3. tests/test_app.py +7 -6
app.py CHANGED
@@ -44,57 +44,37 @@ EXPORT_FORMATS = ["OpenLineage", "Collibra", "Purview", "Alation"]
44
  # Mermaid Rendering
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
  # ============================================================================
99
  # Lineage Parsing and Visualization Generation
100
  # ============================================================================
 
44
  # Mermaid Rendering
45
  # ============================================================================
46
 
47
+ import base64
48
+ import urllib.parse
49
+
50
  def render_mermaid(viz_code: str) -> str:
51
+ """Render mermaid diagram using mermaid.ink service (renders as SVG image)."""
52
+ # Encode the mermaid code for the URL
53
+ # mermaid.ink accepts base64 encoded diagram
54
+ encoded = base64.urlsafe_b64encode(viz_code.encode('utf-8')).decode('utf-8')
55
+
56
+ # Create the mermaid.ink URL for SVG rendering
57
+ img_url = f"https://mermaid.ink/svg/{encoded}"
58
 
59
+ # Also create a link to the live editor for users who want to modify
60
+ editor_url = f"https://mermaid.live/edit#base64:{base64.b64encode(viz_code.encode('utf-8')).decode('utf-8')}"
 
61
 
 
62
  html = f'''
63
+ <div style="background: white; padding: 20px; border-radius: 8px; min-height: 200px;">
64
+ <img src="{img_url}" alt="Lineage Graph" style="max-width: 100%; height: auto;" />
65
+ <div style="margin-top: 10px; font-size: 12px; color: #666;">
66
+ <a href="{editor_url}" target="_blank" style="color: #7c3aed;">Open in Mermaid Live Editor</a>
67
  </div>
68
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  '''
70
  return html
71
 
72
 
73
+ def render_mermaid_code(viz_code: str) -> str:
74
+ """Return the raw mermaid code for display."""
75
+ return viz_code
76
+
77
+
78
  # ============================================================================
79
  # Lineage Parsing and Visualization Generation
80
  # ============================================================================
requirements.txt CHANGED
@@ -1,4 +1,5 @@
1
  gradio>=6.0.0
 
2
  anthropic>=0.25.0
3
  google-cloud-bigquery>=3.10.0
4
  requests>=2.31.0
 
1
  gradio>=6.0.0
2
+ spaces>=0.44.0
3
  anthropic>=0.25.0
4
  google-cloud-bigquery>=3.10.0
5
  requests>=2.31.0
tests/test_app.py CHANGED
@@ -12,9 +12,10 @@ 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('class="mermaid"', html)
16
- self.assertIn('graph TD', html)
17
- self.assertIn('mermaid.init', html)
 
18
 
19
  def test_extract_lineage_from_text_returns_html_and_summary(self):
20
  # Test with valid JSON input
@@ -22,7 +23,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('class="mermaid"', html)
26
  self.assertIn('Parsed', summary)
27
 
28
  def test_extract_lineage_from_text_empty_input(self):
@@ -34,12 +35,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('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
 
 
12
  def test_render_mermaid_wraps_and_inits(self):
13
  viz = "graph TD\n A --> B"
14
  html = render_mermaid(viz)
15
+ # Using mermaid.ink service for SVG rendering
16
+ self.assertIn('mermaid.ink/svg/', html)
17
+ self.assertIn('<img', html)
18
+ self.assertIn('mermaid.live/edit', html)
19
 
20
  def test_extract_lineage_from_text_returns_html_and_summary(self):
21
  # Test with valid JSON input
 
23
  html, summary = extract_lineage_from_text(sample_json, "Custom JSON", "Mermaid")
24
  self.assertIsInstance(html, str)
25
  self.assertIsInstance(summary, str)
26
+ self.assertIn('mermaid.ink/svg/', html)
27
  self.assertIn('Parsed', summary)
28
 
29
  def test_extract_lineage_from_text_empty_input(self):
 
35
 
36
  def test_extract_lineage_from_bigquery_returns_html_and_summary(self):
37
  html, summary = extract_lineage_from_bigquery("proj", "SELECT 1", "key", "Mermaid")
38
+ self.assertIn('mermaid.ink/svg/', html)
39
  self.assertIn('BigQuery', summary)
40
 
41
  def test_extract_lineage_from_url_returns_html_and_summary(self):
42
  html, summary = extract_lineage_from_url("https://example.com", "Mermaid")
43
+ self.assertIn('mermaid.ink/svg/', html)
44
  # Summary can be either 'Lineage' or 'Parsed' depending on response
45
  self.assertTrue('Lineage' in summary or 'Parsed' in summary)
46