File size: 8,245 Bytes
9e9b194
e7d96ab
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9e9b194
e7d96ab
9e9b194
e7d96ab
 
 
 
9e9b194
e7d96ab
 
 
 
9e9b194
e7d96ab
 
 
 
5c70d5b
e7d96ab
 
 
 
0f1355e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5c70d5b
0f1355e
5c70d5b
0f1355e
127229f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4c91160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127229f
0f1355e
 
 
 
 
e7d96ab
0f1355e
e7d96ab
5c70d5b
e7d96ab
 
 
 
0f1355e
e7d96ab
5c70d5b
9e9b194
 
d01fcae
 
 
 
 
 
 
 
 
 
 
9e9b194
0f1355e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
import gradio as gr
import os
import logging
import base64
import tempfile
import subprocess
from mermaid_renderer import MermaidRenderer

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# Initialize the renderer
renderer = None
try:
    renderer = MermaidRenderer()
    logging.info("MermaidRenderer initialized successfully.")
except RuntimeError as e:
    logging.critical(f"Failed to initialize MermaidRenderer: {e}")
    renderer = None

def render_mermaid(mermaid_code, output_format="png", theme="default"):
    """
    Render Mermaid code and return the image file path.
    """
    if renderer is None:
        return None, "Error: Mermaid rendering service is unavailable."
    
    if not mermaid_code.strip():
        return None, "Mermaid code cannot be empty."
    
    try:
        output_path, input_path = renderer.render(mermaid_code, output_format, theme)
        
        # Clean up input file immediately
        if input_path and os.path.exists(input_path):
            try:
                os.unlink(input_path)
            except OSError:
                pass
        
        return output_path, f"Successfully rendered {output_format.upper()} with {theme} theme"
        
    except Exception as e:
        logging.error(f"Rendering failed: {e}")
        return None, f"Error: {str(e)}"

# Default Mermaid code
default_mermaid_code = """graph TD
    A[Start] --> B{Is it?};
    B -- Yes --> C[OK];
    C --> D[End];
    B -- No --> E[Really?];
    E --> C;"""

# Create Gradio interface
with gr.Blocks(title="Mermaid Diagram Renderer", theme=gr.themes.Soft()) as demo:
    gr.Markdown("# 🧜‍♀️ Mermaid Diagram Renderer")
    gr.Markdown("Enter Mermaid code below and generate beautiful diagrams!")
    
    with gr.Row():
        with gr.Column():
            mermaid_input = gr.Textbox(
                label="Mermaid Code",
                placeholder="Enter your Mermaid diagram code here...",
                lines=15,
                value=default_mermaid_code
            )
            
            with gr.Row():
                format_dropdown = gr.Dropdown(
                    choices=["png", "svg", "pdf"],
                    value="png",
                    label="Output Format"
                )
                theme_dropdown = gr.Dropdown(
                    choices=["default", "forest", "dark", "neutral"],
                    value="default",
                    label="Theme"
                )
            
            render_btn = gr.Button("Render Diagram", variant="primary")
        
        with gr.Column():
            rendered_image = gr.Image(label="Diagram Preview", show_label=True, show_download_button=False)
            output_image = gr.File(label="Download Diagram")
            status_text = gr.Textbox(label="Status", interactive=False)
    
    # Connect the function
    def render_mermaid_with_preview(mermaid_code, output_format="png", theme="default"):
        """
        Render Mermaid code and return the image for preview and download.
        """
        if renderer is None:
            return None, None, "Error: Mermaid rendering service is unavailable."
        
        if not mermaid_code.strip():
            return None, None, "Mermaid code cannot be empty."
        
        try:
            output_path, input_path = renderer.render(mermaid_code, output_format, theme)
            
            # Clean up input file immediately
            if input_path and os.path.exists(input_path):
                try:
                    os.unlink(input_path)
                except OSError:
                    pass

            # Only preview PNG or SVG
            if output_format in ["png", "svg"]:
                preview_path = output_path
            else:
                preview_path = None

            # Check for Mermaid syntax errors in SVG or PNG output
            status_message = f"Successfully rendered {output_format.upper()} with {theme} theme"
            if output_format == "svg" and output_path and os.path.exists(output_path):
                try:
                    with open(output_path, "r", encoding="utf-8") as f:
                        svg_content = f.read()
                    # Look for common error indicators in the SVG output
                    if "SyntaxError" in svg_content or "Parse error" in svg_content or "Error:" in svg_content:
                        # Extract the error message (simple heuristic)
                        import re
                        error_lines = re.findall(r'>([^<>]*Error[^<>]*)<', svg_content)
                        error_text = error_lines[0] if error_lines else "Mermaid syntax error"
                        status_message = f"Error: {error_text}"
                except Exception as svg_err:
                    logging.warning(f"Could not check SVG for errors: {svg_err}")
            elif output_format == "png" and output_path and os.path.exists(output_path):
                try:
                    from PIL import Image
                    import pytesseract
                    img = Image.open(output_path)
                    ocr_text = pytesseract.image_to_string(img)
                    if "Syntax error" in ocr_text or "Parse error" in ocr_text or "Error" in ocr_text:
                        # Extract the error message (simple heuristic)
                        lines = [line for line in ocr_text.splitlines() if "error" in line.lower()]
                        error_text = lines[0] if lines else "Mermaid syntax error"
                        status_message = f"Error: {error_text}"
                except Exception as png_err:
                    logging.warning(f"Could not OCR PNG for errors: {png_err}")

            # Gather dependency versions for debugging
            import platform
            dep_versions = []
            try:
                import gradio
                dep_versions.append(f"gradio {gradio.__version__}")
            except Exception:
                dep_versions.append("gradio N/A")
            try:
                import pytesseract
                dep_versions.append(f"pytesseract {pytesseract.__version__}")
            except Exception:
                dep_versions.append("pytesseract N/A")
            try:
                from PIL import __version__ as pillow_version
                dep_versions.append(f"Pillow {pillow_version}")
            except Exception:
                dep_versions.append("Pillow N/A")
            try:
                node_ver = subprocess.run(["node", "--version"], capture_output=True, text=True)
                dep_versions.append(f"Node.js {node_ver.stdout.strip()}")
            except Exception:
                dep_versions.append("Node.js N/A")
            try:
                mmdc_ver = subprocess.run(["mmdc", "--version"], capture_output=True, text=True)
                dep_versions.append(f"mmdc {mmdc_ver.stdout.strip()}")
            except Exception:
                dep_versions.append("mmdc N/A")
            dep_versions.append(f"Python {platform.python_version()}")
            dep_info = " | ".join(dep_versions)
            status_message = f"{status_message}\n\nDependencies: {dep_info}"

            return preview_path, output_path, status_message
            
        except Exception as e:
            logging.error(f"Rendering failed: {e}")
            return None, None, f"Error: {str(e)}"

    render_btn.click(
        fn=render_mermaid_with_preview,
        inputs=[mermaid_input, format_dropdown, theme_dropdown],
        outputs=[rendered_image, output_image, status_text]
    )
    
    # Auto-render on load
    demo.load(
        fn=render_mermaid_with_preview,
        inputs=[mermaid_input, format_dropdown, theme_dropdown],
        outputs=[rendered_image, output_image, status_text]
    )

    gr.Markdown(
        """
---
<sub>
This version is a simplified version of <a href="https://github.com/chunhualiao/mermaid-rendering" target="_blank">https://github.com/chunhualiao/mermaid-rendering</a>.<br>
The full version has more control of previewed diagrams, such as zoom and pan operations.
</sub>
""",
        elem_id="footnote",
    )

if __name__ == "__main__":
    demo.launch()