openfree commited on
Commit
94bb672
ยท
verified ยท
1 Parent(s): 92c0e29

Upload 5 files

Browse files
.gitattributes CHANGED
@@ -41,3 +41,4 @@ after.pdf filter=lfs diff=lfs merge=lfs -text
41
  before.pdf filter=lfs diff=lfs merge=lfs -text
42
  NanumGothic-Regular[[:space:]](1).ttf filter=lfs diff=lfs merge=lfs -text
43
  NanumGothic-Regular.ttf filter=lfs diff=lfs merge=lfs -text
 
 
41
  before.pdf filter=lfs diff=lfs merge=lfs -text
42
  NanumGothic-Regular[[:space:]](1).ttf filter=lfs diff=lfs merge=lfs -text
43
  NanumGothic-Regular.ttf filter=lfs diff=lfs merge=lfs -text
44
+ NanumGothic-Regular[[:space:]](2).ttf filter=lfs diff=lfs merge=lfs -text
NanumGothic-Regular (2).ttf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cf050025dcce823de644153981ff8b171d5b78d7d0ddd6e3c9f39e814fad3564
3
+ size 2053328
app (39).py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import streamlit as st
4
+ from tempfile import NamedTemporaryFile
5
+
6
+ def main():
7
+ try:
8
+ # Get the code from secrets
9
+ code = os.environ.get("MAIN_CODE")
10
+
11
+ if not code:
12
+ st.error("โš ๏ธ The application code wasn't found in secrets. Please add the MAIN_CODE secret.")
13
+ return
14
+
15
+ # Create a temporary Python file
16
+ with NamedTemporaryFile(suffix='.py', delete=False, mode='w') as tmp:
17
+ tmp.write(code)
18
+ tmp_path = tmp.name
19
+
20
+ # Execute the code
21
+ exec(compile(code, tmp_path, 'exec'), globals())
22
+
23
+ # Clean up the temporary file
24
+ try:
25
+ os.unlink(tmp_path)
26
+ except:
27
+ pass
28
+
29
+ except Exception as e:
30
+ st.error(f"โš ๏ธ Error loading or executing the application: {str(e)}")
31
+ import traceback
32
+ st.code(traceback.format_exc())
33
+
34
+ if __name__ == "__main__":
35
+ main()
packages (8).txt ADDED
@@ -0,0 +1 @@
 
 
1
+ graphviz
process_flow_generator (1).py ADDED
@@ -0,0 +1,354 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
requirements (18).txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio
2
+ replicate
3
+ requests
4
+ pillow
5
+ python-pptx
6
+ PyPDF2
7
+ pandas
8
+ chardet
9
+ graphviz
10
+ cairosvg
11
+ streamlit