brian25 commited on
Commit
67a88e3
·
verified ·
1 Parent(s): df29882

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +216 -0
app.py ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import io
3
+ from collections import defaultdict
4
+ from PIL import Image
5
+ from graphviz import Digraph
6
+ import gradio as gr
7
+
8
+ def parse_process_description(description):
9
+
10
+ text = description.strip()
11
+
12
+ # Split on commas, "then", "and"
13
+ segments = re.split(r',|\band\b|\bthen\b', text, flags=re.IGNORECASE)
14
+ segments = [s.strip() for s in segments if s.strip()]
15
+
16
+ edges = []
17
+ recycle_edges = []
18
+ extra_inputs = set()
19
+ final_output = None
20
+
21
+ # We'll track the last_block for normal flows (so we can connect 'then to X')
22
+ last_block = None
23
+
24
+ # Regex patterns
25
+ re_first_to = re.compile(r'^first\s+(.*?)\s+to\s+(.*)$', re.IGNORECASE)
26
+ re_to_with = re.compile(r'^to\s+(.*?)\s+with\s+(.*)$', re.IGNORECASE)
27
+ re_from_to_with = re.compile(r'^from\s+(.*?)\s+to\s+(.*?)\s+with\s+(.*)$', re.IGNORECASE)
28
+ re_to_only = re.compile(r'^to\s+(.*)$', re.IGNORECASE)
29
+ re_from_to_only = re.compile(r'^from\s+(.*?)\s+to\s+(.*)$', re.IGNORECASE)
30
+ re_final_output = re.compile(r'^final\s+output\s+(.*)$', re.IGNORECASE)
31
+ re_recycle = re.compile(r'^recycle\s+from\s+(.*?)\s+to\s+(.*)$', re.IGNORECASE)
32
+
33
+ for seg in segments:
34
+ # 1) final output
35
+ m_final = re_final_output.search(seg)
36
+ if m_final:
37
+ final_output = m_final.group(1).strip()
38
+ continue
39
+
40
+ # 2) recycle from X to Y
41
+ m_rec = re_recycle.search(seg)
42
+ if m_rec:
43
+ src = m_rec.group(1).strip()
44
+ dst = m_rec.group(2).strip()
45
+ # We'll add this to recycle_edges
46
+ recycle_edges.append((src, dst))
47
+ # Importantly, we do NOT update last_block,
48
+ # because the user wants final output to remain from the prior block.
49
+ continue
50
+
51
+ # 3) "First X to Y"
52
+ m_first = re_first_to.search(seg)
53
+ if m_first:
54
+ inp = m_first.group(1).strip()
55
+ blk = m_first.group(2).strip()
56
+ edges.append((inp, blk))
57
+ extra_inputs.add(inp)
58
+ last_block = blk
59
+ continue
60
+
61
+ # 4) "from X to Y with Z"
62
+ m_ftw = re_from_to_with.search(seg)
63
+ if m_ftw:
64
+ src = m_ftw.group(1).strip()
65
+ dst = m_ftw.group(2).strip()
66
+ extra_inp = m_ftw.group(3).strip()
67
+ edges.append((src, dst))
68
+ edges.append((extra_inp, dst))
69
+ extra_inputs.add(extra_inp)
70
+ last_block = dst
71
+ continue
72
+
73
+ # 5) "to Y with Z" (no 'from')
74
+ m_tw = re_to_with.search(seg)
75
+ if m_tw:
76
+ block_candidate = m_tw.group(1).strip()
77
+ extra_inp = m_tw.group(2).strip()
78
+ if last_block:
79
+ edges.append((last_block, block_candidate))
80
+ edges.append((extra_inp, block_candidate))
81
+ extra_inputs.add(extra_inp)
82
+ last_block = block_candidate
83
+ continue
84
+
85
+ # 6) "from X to Y" (no 'with')
86
+ m_ft = re_from_to_only.search(seg)
87
+ if m_ft:
88
+ src = m_ft.group(1).strip()
89
+ dst = m_ft.group(2).strip()
90
+ edges.append((src, dst))
91
+ last_block = dst
92
+ continue
93
+
94
+ # 7) "to X" (no 'from', no 'with')
95
+ m_t = re_to_only.search(seg)
96
+ if m_t:
97
+ blk = m_t.group(1).strip()
98
+ if last_block:
99
+ edges.append((last_block, blk))
100
+ last_block = blk
101
+ continue
102
+
103
+ # If unmatched, ignore or debug:
104
+ # print("Unrecognized segment:", seg)
105
+
106
+ # If there's a final output & we have a last_block, connect them:
107
+ if final_output and last_block:
108
+ edges.append((last_block, final_output))
109
+
110
+ return edges, recycle_edges, extra_inputs, final_output
111
+
112
+
113
+ def build_flowchart(edges, recycle_edges, extra_inputs, final_output):
114
+
115
+ all_nodes = set()
116
+ for s, t in edges:
117
+ all_nodes.add(s)
118
+ all_nodes.add(t)
119
+ for s, t in recycle_edges:
120
+ all_nodes.add(s)
121
+ all_nodes.add(t)
122
+
123
+ if final_output and final_output not in all_nodes:
124
+ all_nodes.add(final_output)
125
+
126
+ # Build in/out degrees for normal edges + recycle edges
127
+ in_degree = defaultdict(int)
128
+ out_degree = defaultdict(int)
129
+
130
+ for s, t in edges:
131
+ out_degree[s] += 1
132
+ in_degree[t] += 1
133
+
134
+ for s, t in recycle_edges:
135
+ out_degree[s] += 1
136
+ in_degree[t] += 1
137
+
138
+ dot = Digraph(name="Flowchart", format="png")
139
+ dot.attr(rankdir='LR')
140
+
141
+ # Create each node
142
+ for node in all_nodes:
143
+ shape = "box"
144
+ style = "rounded,filled"
145
+ fillcolor = "lightgoldenrod1"
146
+
147
+ # Circle if it's an extra input
148
+ if node in extra_inputs:
149
+ shape = "circle"
150
+ style = "filled"
151
+ fillcolor = "lightblue"
152
+
153
+ # Double circle if final output
154
+ if final_output and node == final_output:
155
+ shape = "doublecircle"
156
+ fillcolor = "lightgreen"
157
+ style = "filled"
158
+
159
+ # Tee/Mixer logic (skip if final output)
160
+ indeg = in_degree[node]
161
+ outdeg = out_degree[node]
162
+ if node != final_output:
163
+ if indeg > 1 and outdeg > 1:
164
+ shape = "box"
165
+ fillcolor = "lightgoldenrod1"
166
+ elif indeg > 1:
167
+ shape = "box"
168
+ fillcolor = "lightgoldenrod1"
169
+ elif outdeg > 1:
170
+ shape = "box"
171
+ fillcolor = "lightgoldenrod1"
172
+
173
+ dot.node(node, label=node, shape=shape, style=style, fillcolor=fillcolor)
174
+
175
+ # Add normal edges
176
+ for s, t in edges:
177
+ dot.edge(s, t)
178
+
179
+ # Add recycle edges with dashed style or different color
180
+ for s, t in recycle_edges:
181
+ dot.edge(s, t, style="dashed", color="gray", label="recycle")
182
+
183
+ return dot.pipe(format='png')
184
+
185
+
186
+ def flowchart_to_image(text_input):
187
+ if not text_input.strip():
188
+ return None
189
+
190
+ edges, recycle_edges, extra_inputs, final_output = parse_process_description(text_input)
191
+ png_data = build_flowchart(edges, recycle_edges, extra_inputs, final_output)
192
+ return Image.open(io.BytesIO(png_data))
193
+
194
+ # Gradio interface
195
+ iface = gr.Interface(
196
+ fn=flowchart_to_image,
197
+ inputs=gr.Textbox(lines=7),
198
+ outputs="image",
199
+ title="Process Flow with Recycle Lines",
200
+ description=(
201
+ "Example:\n"
202
+ "First Solid Sulfur to Heater,\n"
203
+ "then to Primary Oxidizer with Air,\n"
204
+ "then from Primary Oxidizer to Reactor,\n"
205
+ "then from Primary Oxidizer to Secondary Oxidizer,\n"
206
+ "from Secondary Oxidizer to Heater,\n"
207
+ "then from Secondary Oxidizer to Absorber with Water,\n"
208
+ "recycle from Absorber to Secondary Oxidizer,\n"
209
+ "and final output Sulfuric Acid.\n\n"
210
+ "This example text only shows how to add or divide more connections and recycle back stream."
211
+ ),
212
+ flagging_dir="/tmp/flagged_data"
213
+ )
214
+
215
+ if __name__ == "__main__":
216
+ iface.launch()