File size: 8,876 Bytes
f5bdf65
 
44471e2
 
f5bdf65
37e5a6d
 
 
 
 
 
 
 
 
 
 
f5bdf65
 
 
37e5a6d
44471e2
37e5a6d
 
 
 
44471e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37e5a6d
44471e2
 
 
 
 
 
 
 
 
 
 
 
 
 
37e5a6d
44471e2
 
 
 
37e5a6d
44471e2
 
 
 
 
 
 
 
 
37e5a6d
44471e2
f5bdf65
44471e2
 
 
 
 
 
 
 
 
 
 
 
37e5a6d
44471e2
37e5a6d
44471e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37e5a6d
44471e2
 
f5bdf65
44471e2
 
 
f5bdf65
44471e2
 
f5bdf65
 
 
44471e2
37e5a6d
 
 
 
 
 
 
 
 
f5bdf65
 
 
 
37e5a6d
f5bdf65
44471e2
f5bdf65
 
44471e2
f5bdf65
44471e2
f5bdf65
 
 
44471e2
f5bdf65
 
44471e2
f5bdf65
44471e2
f5bdf65
 
44471e2
f5bdf65
 
 
44471e2
f5bdf65
 
37e5a6d
f5bdf65
44471e2
37e5a6d
f5bdf65
44471e2
 
 
 
 
 
37e5a6d
f5bdf65
37e5a6d
f5bdf65
 
44471e2
 
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
import gradio as gr
import requests
import spacy
import re
import time
import os  # Import the 'os' module
import base64  # Import base64

# Try to load the spaCy model, download if it's not available
try:
    nlp = spacy.load("en_core_web_sm")
except OSError:
    print("Downloading en_core_web_sm model...")
    import spacy.cli
    spacy.cli.download("en_core_web_sm")
    nlp = spacy.load("en_core_web_sm")


def text_to_diagram(text_description):
    """Convert process description into Mermaid.js flowchart."""
    doc = nlp(text_description.lower())
    steps = []
    conditions = []
    loops = []
    complex_flow = []
    sentences = [sent.text.strip() for sent in doc.sents]

    for sentence in sentences:
        sentence = sentence.replace("2, conditional branch", "").replace("3 loop start", "").replace("4 complex flow", "").strip()

        if "then" in sentence:
            tokens = sentence.split("then")
            for token in tokens:
                token = token.strip()
                if token:
                    step = token.strip().replace(",", "").replace(".", "")
                    if step and step not in ["conditional branch", "loop start", "complex flow"]:
                        if "flowchart that shows" in step:
                            continue
                        steps.append(step)

        cond_match = re.search(r"if ([a-z\s]+), (?:go to|proceed to) ([a-z\s]+); otherwise, (?:go to|proceed to) ([a-z\s]+)", sentence)
        if not cond_match:
            cond_match = re.search(r"if ([a-z\s]+), ([a-z\s]+); otherwise, ([a-z\s]+)", sentence)
        if cond_match:
            condition, true_action, false_action = cond_match.groups()
            conditions.append((condition.strip(), true_action.strip(), false_action.strip()))

        loop_match = re.search(r"(?:do|perform) (?:action )?([a-z]), repeat ([a-z]) until ([a-z]) is true", sentence)
        if not loop_match:
            loop_match = re.search(r"loop: ([a-z\s]+), repeat ([a-z\s]+) until ([a-z\s]+)", sentence)
        if loop_match:
            action, repeat_action, condition = loop_match.groups()
            loops.append((action.strip(), condition.strip()))

        if "flowchart that shows" in sentence:
            flow_part = sentence.split("flowchart that shows")[1]
            flow_steps = flow_part.split("then")
            for step in flow_steps:
                step = step.replace("and finally", "").replace(".", "").replace(",", "").strip()
                if step:
                    complex_flow.append(step)
        elif "then" in sentence and not steps:
            tokens = sentence.split("then")
            for token in tokens:
                token = token.strip()
                if token and token not in steps:
                    step = token.replace(",", "").replace(".", "").strip()
                    if step:
                        complex_flow.append(step)

    if not complex_flow:
        complex_flow = steps[1:] if steps else []
        steps = steps[:1] if steps else []

    diagram_code = "graph TD\n"
    node_counter = 0
    node_map = {}

    def get_node_id(label):
        nonlocal node_counter
        if label not in node_map:
            node_map[label] = chr(65 + node_counter)
            node_counter += 1
        return node_map[label]

    if steps:
        for i in range(len(steps)):
            step = steps[i]
            node_id = get_node_id(step)
            diagram_code += f"{node_id}[{step.capitalize()}]\n"
            if i > 0:
                prev_node_id = get_node_id(steps[i - 1])
                diagram_code += f"{prev_node_id} --> {node_id}\n"
    else:
        start_node = get_node_id("start")
        diagram_code += f"{start_node}[Start]\n"

    last_node = steps[-1] if steps else "start"
    last_node_id = get_node_id(last_node)
    for condition, true_action, false_action in conditions:
        cond_node = get_node_id(f"cond_{condition}")
        true_node = get_node_id(true_action)
        false_node = get_node_id(false_action)
        diagram_code += f"{last_node_id} --> {cond_node}{{{condition.capitalize()}?}}\n"
        diagram_code += f"{cond_node} -->|Yes| {true_node}[{true_action.capitalize()}]\n"
        diagram_code += f"{cond_node} -->|No| {false_node}[{false_action.capitalize()}]\n"
        last_node = true_action
        last_node_id = true_node
        false_node_id = false_node

    for action, condition in loops:
        loop_node = get_node_id(f"loop_{action}")
        cond_node = get_node_id(f"cond_{condition}")
        finish_node = get_node_id(f"finish_{action}")
        diagram_code += f"{last_node_id} --> {loop_node}[Loop: Perform {action.capitalize()}]\n"
        diagram_code += f"{loop_node} --> {cond_node}{{{condition.capitalize()} is True?}}\n"
        diagram_code += f"{cond_node} -->|No| {loop_node}\n"
        diagram_code += f"{cond_node} -->|Yes| {finish_node}[Finish Loop]\n"
        last_node_id = finish_node

    for i in range(len(complex_flow)):
        step = complex_flow[i]
        node_id = get_node_id(f"flow_{i}_{step}")
        diagram_code += f"{node_id}[{step.capitalize()}]\n"
        if i == 0:
            diagram_code += f"{last_node_id} --> {node_id}\n"
        else:
            prev_node_id = get_node_id(f"flow_{i - 1}_{complex_flow[i - 1]}")
            diagram_code += f"{prev_node_id} --> {node_id}\n"
        last_node_id = node_id

    end_node = get_node_id("end")
    diagram_code += f"{end_node}[End Process]\n"
    diagram_code += f"{last_node_id} --> {end_node}\n"

    if conditions:
        diagram_code += f"{false_node_id} --> {end_node}\n"

    try:
        img_url = render_mermaid_to_url(diagram_code)
        response = requests.get(img_url, timeout=10)
        response.raise_for_status()  # Raise HTTPError for bad responses (4xx or 5xx)
        image_data = response.content
        temp_img_path = f"temp_diagram_{int(time.time())}.png"
        with open(temp_img_path, "wb") as f:
            f.write(image_data)
        return diagram_code, temp_img_path
    except requests.exceptions.RequestException as e:
        print(f"Error rendering diagram (network): {e}")
        return diagram_code, None
    except Exception as e:
        print(f"Error rendering diagram: {e}")
        return diagram_code, None


def render_mermaid_to_url(mermaid_code):
    """Render Mermaid code to an image URL using Mermaid.ink."""
    try:
        encoded_code = base64.urlsafe_b64encode(mermaid_code.encode()).decode()
        return f"https://mermaid.ink/img/{encoded_code}"
    except Exception as e:
        print(f"Error encoding Mermaid code: {e}")
        return None

def gradio_interface(text_input):
    """Process user input and return diagram output for Gradio."""
    try:
        diagram_code, img_path = text_to_diagram(text_input)
        print("Generated Mermaid Code:")
        print(diagram_code)
        return diagram_code, img_path if img_path else None
    except Exception as e:
        print(f"Error in Gradio interface: {e}")
        return f"Error: {str(e)}", None

iface = gr.Interface(
    fn=gradio_interface,
    inputs=gr.Textbox(lines=5, placeholder="Describe your process (e.g., 'User logs in, then enters details...')"),
    outputs=[
        gr.Textbox(label="Generated Mermaid Code"),
        gr.Image(label="Diagram Visualization", type="filepath"),
    ],
    title="Text-to-Diagram Converter (Napkin AI Style)",
    description="Convert process descriptions into structured flowcharts like Napkin AI.",
    examples=[
        ["Developer writes code, then tests code. If tests pass, proceed to deploy; otherwise, debug code. Then release software to production."],
        ["Customer browses products, then adds to cart, then proceeds to checkout. If payment is verified, confirm order; otherwise, request payment again. Loop: Validate payment, repeat until successful. Then ship order and notify customer."],
        ["HR posts job opening, then reviews applications, then conducts interviews. If candidate is selected, offer job; otherwise, reject candidate. Then onboard new employee."],
        ["Student registers for exam, then studies material. If prepared, proceed to take exam; otherwise, study more. If exam is passed, receive certificate; otherwise, retake exam. Then celebrate success."],
        ["Customer places order, then kitchen prepares food. Loop: Check food quality, repeat until perfect. Then serve food and collect payment."],
        ["Traveler searches for flights, then selects flight, then books ticket. If booking is confirmed, proceed to payment; otherwise, search again. If payment is successful, receive itinerary; otherwise, cancel booking. Then plan trip."],
        ["User signs up, then verifies email, then logs in."],
    ],
    allow_flagging="never",
)

if __name__ == "__main__":
    iface.launch(share=True)