openfree commited on
Commit
a735365
ยท
verified ยท
1 Parent(s): 8918e78

Delete process_flow_generator.py

Browse files
Files changed (1) hide show
  1. process_flow_generator.py +0 -354
process_flow_generator.py DELETED
@@ -1,354 +0,0 @@
1
- import graphviz
2
- import json
3
- from tempfile import NamedTemporaryFile
4
- import os
5
- from PIL import Image
6
- import platform
7
- import subprocess
8
-
9
- def setup_korean_font_env():
10
- """ํ•œ๊ธ€ ํฐํŠธ ํ™˜๊ฒฝ ์„ค์ •"""
11
- CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
12
- FONT_PATH = os.path.join(CURRENT_DIR, 'NanumGothic-Regular.ttf')
13
-
14
- # ํฐํŠธ ํŒŒ์ผ ์กด์žฌ ํ™•์ธ
15
- if not os.path.exists(FONT_PATH):
16
- print(f"[๊ฒฝ๊ณ ] ํ•œ๊ธ€ ํฐํŠธ ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: {FONT_PATH}")
17
- return None
18
-
19
- # ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •
20
- os.environ['GDFONTPATH'] = CURRENT_DIR
21
-
22
- # fonts.conf ์ƒ์„ฑ
23
- fonts_conf_path = os.path.join(CURRENT_DIR, 'fonts.conf')
24
- fonts_conf_content = f"""<?xml version="1.0"?>
25
- <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
26
- <fontconfig>
27
- <dir>{CURRENT_DIR}</dir>
28
- <cachedir>/tmp/fontconfig-cache</cachedir>
29
-
30
- <match target="pattern">
31
- <test name="family">
32
- <string>NanumGothic</string>
33
- </test>
34
- <edit name="family" mode="assign" binding="same">
35
- <string>NanumGothic-Regular</string>
36
- </edit>
37
- </match>
38
-
39
- <match target="pattern">
40
- <test name="family">
41
- <string>NanumGothic-Regular</string>
42
- </test>
43
- <edit name="file" mode="assign" binding="same">
44
- <string>{FONT_PATH}</string>
45
- </edit>
46
- </match>
47
-
48
- <alias binding="same">
49
- <family>NanumGothic</family>
50
- <default>
51
- <family>NanumGothic-Regular</family>
52
- </default>
53
- </alias>
54
- </fontconfig>"""
55
-
56
- with open(fonts_conf_path, 'w', encoding='utf-8') as f:
57
- f.write(fonts_conf_content)
58
-
59
- os.environ['FONTCONFIG_FILE'] = fonts_conf_path
60
- os.environ['FONTCONFIG_PATH'] = CURRENT_DIR
61
-
62
- return FONT_PATH
63
-
64
- # ํฐํŠธ ์„ค์ • ์ดˆ๊ธฐํ™”
65
- FONT_PATH = setup_korean_font_env()
66
-
67
- def generate_process_flow_diagram(json_input: str, output_format: str) -> str:
68
- """
69
- Generates a Process Flow Diagram (Flowchart) from JSON input.
70
- """
71
- try:
72
- if not json_input.strip():
73
- return "Error: Empty input"
74
-
75
- data = json.loads(json_input)
76
-
77
- # Validate required top-level keys for a flowchart
78
- if 'start_node' not in data or 'nodes' not in data or 'connections' not in data:
79
- raise ValueError("Missing required fields: 'start_node', 'nodes', or 'connections'")
80
-
81
- # Define specific node shapes for flowchart types
82
- node_shapes = {
83
- "process": "box", # Rectangle for processes
84
- "decision": "diamond", # Diamond for decisions
85
- "start": "oval", # Oval for start
86
- "end": "oval", # Oval for end
87
- "io": "parallelogram", # Input/Output
88
- "document": "note", # Document symbol
89
- "default": "box" # Fallback
90
- }
91
-
92
- # Graphviz ๋‹ค์ด์–ด๊ทธ๋žจ ์ƒ์„ฑ - ํฌ๊ธฐ์™€ ์—ฌ๋ฐฑ ์กฐ์ •
93
- dot = graphviz.Digraph(
94
- name='ProcessFlowDiagram',
95
- format='png',
96
- encoding='utf-8',
97
- graph_attr={
98
- 'rankdir': 'TB', # Top-to-Bottom flow
99
- 'splines': 'ortho', # Straight lines with 90-degree bends
100
- 'bgcolor': 'white',
101
- 'pad': '0.2', # ์ „์ฒด ํŒจ๋”ฉ ์ค„์ž„ (0.5 -> 0.2)
102
- 'margin': '0.2', # ๋งˆ์ง„ ์ถ”๊ฐ€
103
- 'nodesep': '0.4', # ๋…ธ๋“œ ๊ฐ„ ๊ฐ„๊ฒฉ ์ค„์ž„ (0.6 -> 0.4)
104
- 'ranksep': '0.6', # ๋žญํฌ ๊ฐ„ ๊ฐ„๊ฒฉ ์ค„์ž„ (0.8 -> 0.6)
105
- 'fontname': 'NanumGothic-Regular',
106
- 'charset': 'UTF-8',
107
- 'dpi': '96', # DPI ์ค„์ž„ (150 -> 96)
108
- 'size': '10,7.5', # ์ „์ฒด ๊ทธ๋ž˜ํ”„ ํฌ๊ธฐ ์ œํ•œ (์ธ์น˜ ๋‹จ์œ„)
109
- 'ratio': 'compress' # ๋น„์œจ ์••์ถ•
110
- },
111
- node_attr={
112
- 'fontname': 'NanumGothic-Regular',
113
- 'fontsize': '12', # ํฐํŠธ ํฌ๊ธฐ ์ค„์ž„ (14 -> 12)
114
- 'charset': 'UTF-8',
115
- 'height': '0.6', # ๋…ธ๋“œ ๋†’์ด ์ง€์ •
116
- 'width': '2.0', # ๋…ธ๋“œ ๋„ˆ๋น„ ์ง€์ •
117
- 'margin': '0.2,0.1' # ๋…ธ๋“œ ๋‚ด๋ถ€ ๋งˆ์ง„
118
- },
119
- edge_attr={
120
- 'fontname': 'NanumGothic-Regular',
121
- 'fontsize': '9', # ์—ฃ์ง€ ํฐํŠธ ํฌ๊ธฐ ์ค„์ž„ (10 -> 9)
122
- 'charset': 'UTF-8'
123
- }
124
- )
125
-
126
- base_color = '#19191a'
127
- fill_color_for_nodes = base_color
128
- font_color_for_nodes = 'white' if base_color == '#19191a' or base_color.lower() in ['#000000', '#19191a'] else 'black'
129
-
130
- # Store all nodes by ID for easy lookup
131
- all_defined_nodes = {node['id']: node for node in data['nodes']}
132
-
133
- # Add start node explicitly
134
- start_node_id = data['start_node']
135
- dot.node(
136
- start_node_id,
137
- start_node_id,
138
- shape=node_shapes['start'],
139
- style='filled,rounded',
140
- fillcolor='#2196F3',
141
- fontcolor='white',
142
- fontsize='12',
143
- height='0.5',
144
- width='1.8'
145
- )
146
-
147
- # Add all other nodes
148
- for node_id, node_info in all_defined_nodes.items():
149
- if node_id == start_node_id:
150
- continue
151
-
152
- node_type = node_info.get("type", "default")
153
- shape = node_shapes.get(node_type, "box")
154
- node_label = node_info['label']
155
-
156
- # ๋…ธ๋“œ ํƒ€์ž…์— ๋”ฐ๋ฅธ ํฌ๊ธฐ ์กฐ์ •
157
- if node_type == 'decision':
158
- height = '0.8'
159
- width = '1.5'
160
- else:
161
- height = '0.6'
162
- width = '2.0'
163
-
164
- if node_type == 'end':
165
- dot.node(
166
- node_id,
167
- node_label,
168
- shape=shape,
169
- style='filled,rounded',
170
- fillcolor='#F44336',
171
- fontcolor='white',
172
- fontsize='12',
173
- height='0.5',
174
- width='1.8'
175
- )
176
- else:
177
- dot.node(
178
- node_id,
179
- node_label,
180
- shape=shape,
181
- style='filled,rounded',
182
- fillcolor=fill_color_for_nodes,
183
- fontcolor=font_color_for_nodes,
184
- fontsize='12',
185
- height=height,
186
- width=width
187
- )
188
-
189
- # Add connections (edges)
190
- for connection in data['connections']:
191
- dot.edge(
192
- connection['from'],
193
- connection['to'],
194
- label=connection.get('label', ''),
195
- color='#4a4a4a',
196
- fontcolor='#4a4a4a',
197
- fontsize='9'
198
- )
199
-
200
- # PNG๋กœ ์ง์ ‘ ๋ Œ๋”๋ง (ํฌ๊ธฐ ์กฐ์ •๋จ)
201
- with NamedTemporaryFile(delete=False, suffix=f'.{output_format}') as tmp:
202
- dot.render(tmp.name, format=output_format, cleanup=True)
203
- png_path = f"{tmp.name}.{output_format}"
204
-
205
- # ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€๋ฅผ PIL๋กœ ์—ด์–ด์„œ ํฌ๊ธฐ ํ™•์ธ ๋ฐ ์กฐ์ •
206
- from PIL import Image
207
- with Image.open(png_path) as img:
208
- width, height = img.size
209
- print(f"[ํ”„๋กœ์„ธ์Šค ํ”Œ๋กœ์šฐ] ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€ ํฌ๊ธฐ: {width}x{height}")
210
-
211
- # ์ด๋ฏธ์ง€๊ฐ€ ๋„ˆ๋ฌด ํฌ๋ฉด ๋ฆฌ์‚ฌ์ด์ฆˆ
212
- max_width = 1200
213
- max_height = 900
214
-
215
- if width > max_width or height > max_height:
216
- # ๋น„์œจ ์œ ์ง€ํ•˜๋ฉด์„œ ๋ฆฌ์‚ฌ์ด์ฆˆ
217
- ratio = min(max_width/width, max_height/height)
218
- new_width = int(width * ratio)
219
- new_height = int(height * ratio)
220
-
221
- img_resized = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
222
-
223
- # ์—ฌ๋ฐฑ ์ถ”๊ฐ€ํ•˜์—ฌ ์ค‘์•™ ์ •๋ ฌ
224
- final_img = Image.new('RGB', (max_width, max_height), 'white')
225
- x = (max_width - new_width) // 2
226
- y = (max_height - new_height) // 2
227
- final_img.paste(img_resized, (x, y))
228
-
229
- # ์ƒˆ๋กœ์šด ํŒŒ์ผ๋กœ ์ €์žฅ
230
- new_path = png_path.replace('.png', '_resized.png')
231
- final_img.save(new_path)
232
- os.unlink(png_path) # ์›๋ณธ ์‚ญ์ œ
233
- return new_path
234
-
235
- return png_path
236
-
237
- except json.JSONDecodeError:
238
- return "Error: Invalid JSON format"
239
- except Exception as e:
240
- return f"Error: {str(e)}"
241
-
242
- def generate_process_flow_for_ppt(topic: str, context: str, style: str = "Business Workflow") -> Image.Image:
243
- """
244
- PPT ์ƒ์„ฑ๊ธฐ์—์„œ ์‚ฌ์šฉํ•  ํ”„๋กœ์„ธ์Šค ํ”Œ๋กœ์šฐ ๋‹ค์ด์–ด๊ทธ๋žจ ์ƒ์„ฑ
245
- """
246
- print(f"[ํ”„๋กœ์„ธ์Šค ํ”Œ๋กœ์šฐ] ์ƒ์„ฑ ์‹œ์ž‘ - ์ฃผ์ œ: {topic}, ์ปจํ…์ŠคํŠธ: {context}")
247
-
248
- # ํ•œ๊ธ€ ํฐํŠธ ์žฌ์„ค์ • (๋งค๋ฒˆ ํ™•์ธ)
249
- setup_korean_font_env()
250
-
251
- # ์ปจํ…์ŠคํŠธ ๋ถ„์„ํ•˜์—ฌ ์ ์ ˆํ•œ ํ”„๋กœ์„ธ์Šค ํ”Œ๋กœ์šฐ JSON ์ƒ์„ฑ
252
- context_lower = context.lower()
253
-
254
- # ๋…ธ๋“œ ์ˆ˜๋ฅผ ์ค„์ด๊ณ  ๋ ˆ์ด๋ธ”์„ ์งง๊ฒŒ ๋งŒ๋“ค์–ด ๊ณต๊ฐ„ ์ ˆ์•ฝ
255
- if "ํ”„๋กœ์ ํŠธ" in context or "project" in context_lower:
256
- flow_json = {
257
- "start_node": "์‹œ์ž‘",
258
- "nodes": [
259
- {"id": "plan", "label": "๊ธฐํš", "type": "process"},
260
- {"id": "design", "label": "์„ค๊ณ„", "type": "process"},
261
- {"id": "develop", "label": "๊ฐœ๋ฐœ", "type": "process"},
262
- {"id": "test", "label": "ํ…Œ์ŠคํŠธ", "type": "decision"},
263
- {"id": "deploy", "label": "๋ฐฐํฌ", "type": "process"},
264
- {"id": "end", "label": "์™„๋ฃŒ", "type": "end"}
265
- ],
266
- "connections": [
267
- {"from": "์‹œ์ž‘", "to": "plan", "label": ""},
268
- {"from": "plan", "to": "design", "label": ""},
269
- {"from": "design", "to": "develop", "label": ""},
270
- {"from": "develop", "to": "test", "label": ""},
271
- {"from": "test", "to": "deploy", "label": "ํ†ต๊ณผ"},
272
- {"from": "test", "to": "develop", "label": "์ˆ˜์ •"},
273
- {"from": "deploy", "to": "end", "label": ""}
274
- ]
275
- }
276
- elif "์ž‘๋™" in context or "๊ธฐ๋Šฅ" in context:
277
- flow_json = {
278
- "start_node": "์‹œ์ž‘",
279
- "nodes": [
280
- {"id": "input", "label": "์ž…๋ ฅ", "type": "io"},
281
- {"id": "validate", "label": "๊ฒ€์ฆ", "type": "decision"},
282
- {"id": "process", "label": "์ฒ˜๋ฆฌ", "type": "process"},
283
- {"id": "output", "label": "์ถœ๋ ฅ", "type": "io"},
284
- {"id": "end", "label": "์ข…๋ฃŒ", "type": "end"}
285
- ],
286
- "connections": [
287
- {"from": "์‹œ์ž‘", "to": "input", "label": ""},
288
- {"from": "input", "to": "validate", "label": ""},
289
- {"from": "validate", "to": "process", "label": "์œ ํšจ"},
290
- {"from": "validate", "to": "input", "label": "์žฌ์ž…๋ ฅ"},
291
- {"from": "process", "to": "output", "label": ""},
292
- {"from": "output", "to": "end", "label": ""}
293
- ]
294
- }
295
- else:
296
- # ๊ธฐ๋ณธ ํ”„๋กœ์„ธ์Šค ํ”Œ๋กœ์šฐ (๋” ๊ฐ„๋‹จํ•˜๊ฒŒ)
297
- flow_json = {
298
- "start_node": "์‹œ์ž‘",
299
- "nodes": [
300
- {"id": "analyze", "label": "๋ถ„์„", "type": "process"},
301
- {"id": "plan", "label": "๊ณ„ํš", "type": "process"},
302
- {"id": "execute", "label": "์‹คํ–‰", "type": "process"},
303
- {"id": "check", "label": "๊ฒ€ํ† ", "type": "decision"},
304
- {"id": "complete", "label": "์™„๋ฃŒ", "type": "end"}
305
- ],
306
- "connections": [
307
- {"from": "์‹œ์ž‘", "to": "analyze", "label": ""},
308
- {"from": "analyze", "to": "plan", "label": ""},
309
- {"from": "plan", "to": "execute", "label": ""},
310
- {"from": "execute", "to": "check", "label": ""},
311
- {"from": "check", "to": "complete", "label": "์Šน์ธ"},
312
- {"from": "check", "to": "plan", "label": "์ˆ˜์ •"}
313
- ]
314
- }
315
-
316
- # JSON์„ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜
317
- json_str = json.dumps(flow_json, ensure_ascii=False)
318
-
319
- print(f"[ํ”„๋กœ์„ธ์Šค ํ”Œ๋กœ์šฐ] JSON ์ƒ์„ฑ ์™„๋ฃŒ")
320
-
321
- # ํ”„๋กœ์„ธ์Šค ํ”Œ๋กœ์šฐ ๋‹ค์ด์–ด๊ทธ๋žจ ์ƒ์„ฑ
322
- png_path = generate_process_flow_diagram(json_str, 'png')
323
-
324
- if png_path.startswith("Error:"):
325
- print(f"[ํ”„๋กœ์„ธ์Šค ํ”Œ๋กœ์šฐ] ์ƒ์„ฑ ์‹คํŒจ: {png_path}")
326
- # ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ๊ธฐ๋ณธ ์ด๋ฏธ์ง€ ๋ฐ˜ํ™˜
327
- from PIL import ImageDraw, ImageFont
328
- img = Image.new('RGB', (1200, 900), 'white')
329
- draw = ImageDraw.Draw(img)
330
-
331
- try:
332
- if FONT_PATH and os.path.exists(FONT_PATH):
333
- font = ImageFont.truetype(FONT_PATH, 20)
334
- else:
335
- font = ImageFont.load_default()
336
- except:
337
- font = ImageFont.load_default()
338
-
339
- draw.text((600, 450), "ํ”„๋กœ์„ธ์Šค ํ”Œ๋กœ์šฐ ์ƒ์„ฑ ์‹คํŒจ", fill='red', anchor='mm', font=font)
340
- return img
341
-
342
- print(f"[ํ”„๋กœ์„ธ์Šค ํ”Œ๋กœ์šฐ] PNG ์ƒ์„ฑ ์„ฑ๊ณต: {png_path}")
343
-
344
- # PNG ํŒŒ์ผ์„ PIL Image๋กœ ๋ณ€ํ™˜
345
- with Image.open(png_path) as img:
346
- img_copy = img.copy()
347
-
348
- # ์ž„์‹œ ํŒŒ์ผ ์‚ญ์ œ
349
- try:
350
- os.unlink(png_path)
351
- except:
352
- pass
353
-
354
- return img_copy