openfree commited on
Commit
2a02b1f
ยท
verified ยท
1 Parent(s): 52e094b

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +738 -0
app.py ADDED
@@ -0,0 +1,738 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import sys
4
+ import json
5
+ import requests
6
+
7
+ # ํ˜„์žฌ ๋””๋ ‰ํ† ๋ฆฌ์™€ ํฐํŠธ ํŒŒ์ผ ๊ฒฝ๋กœ ์„ค์ •
8
+ CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
9
+ FONT_PATH = os.path.join(CURRENT_DIR, 'NanumGothic-Regular.ttf')
10
+ FONTS_CONF_PATH = os.path.join(CURRENT_DIR, 'fonts.conf')
11
+
12
+ # fonts.conf ํŒŒ์ผ ์ƒ์„ฑ ํ•จ์ˆ˜
13
+ def create_fonts_conf():
14
+ """Graphviz๊ฐ€ ๋กœ์ปฌ ํฐํŠธ๋ฅผ ์ธ์‹ํ•  ์ˆ˜ ์žˆ๋„๋ก fonts.conf ํŒŒ์ผ ์ƒ์„ฑ"""
15
+ fonts_conf_content = f"""<?xml version="1.0"?>
16
+ <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
17
+ <fontconfig>
18
+ <!-- ๋กœ์ปฌ ํฐํŠธ ๋””๋ ‰ํ† ๋ฆฌ ์ถ”๊ฐ€ -->
19
+ <dir>{CURRENT_DIR}</dir>
20
+
21
+ <!-- NanumGothic ํฐํŠธ ๋ณ„์นญ ์„ค์ • -->
22
+ <alias>
23
+ <family>NanumGothic</family>
24
+ <prefer>
25
+ <family>NanumGothic-Regular</family>
26
+ </prefer>
27
+ </alias>
28
+
29
+ <alias>
30
+ <family>NanumGothic-Regular</family>
31
+ <default>
32
+ <family>NanumGothic-Regular</family>
33
+ </default>
34
+ </alias>
35
+
36
+ <!-- ํ•œ๊ธ€ ํฐํŠธ ๋งคํ•‘ -->
37
+ <match target="pattern">
38
+ <test name="family">
39
+ <string>NanumGothic</string>
40
+ </test>
41
+ <edit name="family" mode="assign">
42
+ <string>NanumGothic-Regular</string>
43
+ </edit>
44
+ </match>
45
+ </fontconfig>
46
+ """
47
+
48
+ with open(FONTS_CONF_PATH, 'w', encoding='utf-8') as f:
49
+ f.write(fonts_conf_content)
50
+ print(f"fonts.conf ํŒŒ์ผ ์ƒ์„ฑ๋จ: {FONTS_CONF_PATH}")
51
+
52
+ # ํฐํŠธ ์„ค์ •
53
+ if os.path.exists(FONT_PATH):
54
+ print(f"ํ•œ๊ธ€ ํฐํŠธ ํŒŒ์ผ ๋ฐœ๊ฒฌ: {FONT_PATH}")
55
+
56
+ # fonts.conf ํŒŒ์ผ ์ƒ์„ฑ
57
+ create_fonts_conf()
58
+
59
+ # Graphviz๊ฐ€ ํฐํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ๋„๋ก ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •
60
+ os.environ['GDFONTPATH'] = CURRENT_DIR
61
+ os.environ['FONTCONFIG_PATH'] = CURRENT_DIR
62
+ os.environ['FONTCONFIG_FILE'] = FONTS_CONF_PATH
63
+
64
+ print(f"GDFONTPATH ์„ค์ •: {CURRENT_DIR}")
65
+ print(f"FONTCONFIG_FILE ์„ค์ •: {FONTS_CONF_PATH}")
66
+ else:
67
+ print(f"๊ฒฝ๊ณ : ํ•œ๊ธ€ ํฐํŠธ ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: {FONT_PATH}")
68
+
69
+ # ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ํฐํŠธ ๊ฒฝ๋กœ ์ €์žฅ (generator๋“ค์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก)
70
+ os.environ['KOREAN_FONT_PATH'] = FONT_PATH
71
+
72
+ from concept_map_generator import generate_concept_map
73
+ from synoptic_chart_generator import generate_synoptic_chart
74
+ from radial_diagram_generator import generate_radial_diagram
75
+ from process_flow_generator import generate_process_flow_diagram
76
+ from wbs_diagram_generator import generate_wbs_diagram
77
+
78
+ from sample_data import CONCEPT_MAP_JSON, SYNOPTIC_CHART_JSON, RADIAL_DIAGRAM_JSON, PROCESS_FLOW_JSON, WBS_DIAGRAM_JSON
79
+
80
+ # LLM API ํ•จ์ˆ˜
81
+ def call_llm_api(prompt, diagram_type):
82
+ """Friendli AI API๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ JSON ์ƒ์„ฑ"""
83
+ token = os.environ.get("FRIENDLI_TOKEN") or "YOUR_FRIENDLI_TOKEN"
84
+ url = "https://api.friendli.ai/dedicated/v1/chat/completions"
85
+ headers = {
86
+ "Authorization": "Bearer " + token,
87
+ "Content-Type": "application/json"
88
+ }
89
+
90
+ # ๋‹ค์ด์–ด๊ทธ๋žจ ํƒ€์ž…๋ณ„ JSON ๊ตฌ์กฐ ๊ฐ€์ด๋“œ - ์‹ค์ œ ์ƒ์„ฑ๊ธฐ๊ฐ€ ์š”๊ตฌํ•˜๋Š” ์ •ํ™•ํ•œ ํ˜•์‹
91
+ json_guides = {
92
+ "Concept Map": """Generate a JSON for a concept map with the EXACT following structure:
93
+ {
94
+ "central_node": "Main Topic",
95
+ "nodes": [
96
+ {
97
+ "id": "node1",
98
+ "label": "First Concept",
99
+ "relationship": "is part of",
100
+ "subnodes": [
101
+ {
102
+ "id": "node1_1",
103
+ "label": "Sub Concept 1",
104
+ "relationship": "includes",
105
+ "subnodes": []
106
+ }
107
+ ]
108
+ },
109
+ {
110
+ "id": "node2",
111
+ "label": "Second Concept",
112
+ "relationship": "relates to",
113
+ "subnodes": []
114
+ }
115
+ ]
116
+ }""",
117
+ "Synoptic Chart": """Generate a JSON for a synoptic chart with the EXACT following structure:
118
+ {
119
+ "central_node": "Chart Title",
120
+ "nodes": [
121
+ {
122
+ "id": "phase1",
123
+ "label": "Phase 1 Name",
124
+ "relationship": "starts with",
125
+ "subnodes": [
126
+ {
127
+ "id": "sub1_1",
128
+ "label": "Sub Item 1",
129
+ "relationship": "includes",
130
+ "subnodes": []
131
+ }
132
+ ]
133
+ }
134
+ ]
135
+ }""",
136
+ "Radial Diagram": """Generate a JSON for a radial diagram with the EXACT following structure:
137
+ {
138
+ "central_node": "Central Concept",
139
+ "nodes": [
140
+ {
141
+ "id": "branch1",
142
+ "label": "Branch 1",
143
+ "relationship": "connected to",
144
+ "subnodes": [
145
+ {
146
+ "id": "item1",
147
+ "label": "Item 1",
148
+ "relationship": "example",
149
+ "subnodes": []
150
+ }
151
+ ]
152
+ }
153
+ ]
154
+ }""",
155
+ "Process Flow": """Generate a JSON for a process flow diagram with the EXACT following structure:
156
+ {
157
+ "start_node": "Start Process",
158
+ "nodes": [
159
+ {"id": "step1", "label": "First Step", "type": "process"},
160
+ {"id": "step2", "label": "Decision Point", "type": "decision"},
161
+ {"id": "step3", "label": "Another Step", "type": "process"},
162
+ {"id": "end", "label": "End Process", "type": "end"}
163
+ ],
164
+ "connections": [
165
+ {"from": "start_node", "to": "step1", "label": "Begin"},
166
+ {"from": "step1", "to": "step2", "label": "Next"},
167
+ {"from": "step2", "to": "step3", "label": "Yes"},
168
+ {"from": "step3", "to": "end", "label": "Complete"}
169
+ ]
170
+ }""",
171
+ "WBS Diagram": """Generate a JSON for a WBS diagram with the EXACT following structure:
172
+ {
173
+ "project_title": "Project Name",
174
+ "phases": [
175
+ {
176
+ "id": "phase1",
177
+ "label": "Phase 1",
178
+ "tasks": [
179
+ {
180
+ "id": "task1_1",
181
+ "label": "Task 1.1",
182
+ "subtasks": [
183
+ {
184
+ "id": "subtask1_1_1",
185
+ "label": "Subtask 1.1.1",
186
+ "sub_subtasks": []
187
+ }
188
+ ]
189
+ }
190
+ ]
191
+ }
192
+ ]
193
+ }"""
194
+ }
195
+
196
+ system_prompt = f"""You are a helpful assistant that generates JSON structures for diagrams.
197
+ {json_guides.get(diagram_type, '')}
198
+
199
+ Important rules:
200
+ 1. Generate ONLY valid JSON without any explanation or markdown formatting
201
+ 2. The JSON must follow the EXACT structure shown above - do not change field names
202
+ 3. Make the content relevant to the user's prompt
203
+ 4. Use the user's language (Korean or English) for the content values
204
+ 5. For IDs, use simple alphanumeric strings without spaces (e.g., "node1", "task1_1")
205
+ 6. Ensure all connections reference existing node IDs
206
+ 7. For Process Flow: 'type' can be: "process", "decision", "start", "end", "io"
207
+ 8. For nested structures (Concept Map, Synoptic Chart, Radial, WBS), include empty 'subnodes' or 'subtasks' arrays when there are no children
208
+ 9. Do not add any additional fields not shown in the example structure"""
209
+
210
+ payload = {
211
+ "model": "dep89a2fld32mcm",
212
+ "messages": [
213
+ {"role": "system", "content": system_prompt},
214
+ {"role": "user", "content": f"Create a {diagram_type} JSON for: {prompt}"}
215
+ ],
216
+ "max_tokens": 16384,
217
+ "top_p": 0.8,
218
+ "stream": False # ๊ฐ„๋‹จํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด stream์„ False๋กœ ์„ค์ •
219
+ }
220
+
221
+ try:
222
+ response = requests.post(url, json=payload, headers=headers, timeout=30)
223
+
224
+ # API ์‘๋‹ต ์ƒํƒœ ํ™•์ธ
225
+ if response.status_code != 200:
226
+ return json.dumps({"error": f"API returned status code {response.status_code}: {response.text}"})
227
+
228
+ response_data = response.json()
229
+
230
+ if 'choices' in response_data and len(response_data['choices']) > 0:
231
+ content = response_data['choices'][0]['message']['content']
232
+ # JSON ๋ถ€๋ถ„๋งŒ ์ถ”์ถœ (๋งˆํฌ๋‹ค์šด ์ฝ”๋“œ ๋ธ”๋ก ์ œ๊ฑฐ)
233
+ content = content.strip()
234
+ if content.startswith("```json"):
235
+ content = content[7:]
236
+ if content.startswith("```"):
237
+ content = content[3:]
238
+ if content.endswith("```"):
239
+ content = content[:-3]
240
+
241
+ # ์ถ”๊ฐ€์ ์ธ ํ…์ŠคํŠธ ์ œ๊ฑฐ (JSON ์™ธ์˜ ์„ค๋ช…์ด ์žˆ์„ ๊ฒฝ์šฐ)
242
+ content = content.strip()
243
+ # JSON ์‹œ์ž‘ ์œ„์น˜ ์ฐพ๊ธฐ
244
+ json_start = content.find('{')
245
+ if json_start != -1:
246
+ content = content[json_start:]
247
+ # JSON ๋ ์œ„์น˜ ์ฐพ๊ธฐ
248
+ bracket_count = 0
249
+ json_end = -1
250
+ for i, char in enumerate(content):
251
+ if char == '{':
252
+ bracket_count += 1
253
+ elif char == '}':
254
+ bracket_count -= 1
255
+ if bracket_count == 0:
256
+ json_end = i
257
+ break
258
+ if json_end != -1:
259
+ content = content[:json_end + 1]
260
+
261
+ return content.strip()
262
+ else:
263
+ return json.dumps({"error": "No response from LLM"})
264
+ except requests.exceptions.Timeout:
265
+ return json.dumps({"error": "Request timed out"})
266
+ except requests.exceptions.RequestException as e:
267
+ print(f"LLM API Request Error: {str(e)}")
268
+ return json.dumps({"error": f"Request failed: {str(e)}"})
269
+ except Exception as e:
270
+ print(f"LLM API Error: {str(e)}")
271
+ return json.dumps({"error": str(e)})
272
+
273
+ def generate_with_llm(prompt, diagram_type, output_format):
274
+ """LLM์œผ๋กœ JSON์„ ์ƒ์„ฑํ•˜๊ณ  ๋‹ค์ด์–ด๊ทธ๋žจ์„ ์ƒ์„ฑ"""
275
+ if not prompt:
276
+ return None, "Please enter a prompt"
277
+
278
+ # LLM์œผ๋กœ JSON ์ƒ์„ฑ
279
+ generated_json = call_llm_api(prompt, diagram_type)
280
+
281
+ try:
282
+ # JSON ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
283
+ json_data = json.loads(generated_json)
284
+ json_str = json.dumps(json_data, indent=2, ensure_ascii=False)
285
+
286
+ # JSON ๊ตฌ์กฐ ๊ฒ€์ฆ
287
+ if diagram_type in ["Concept Map", "Synoptic Chart", "Radial Diagram"]:
288
+ if "central_node" not in json_data or "nodes" not in json_data:
289
+ return None, f"Invalid JSON structure for {diagram_type}. Missing 'central_node' or 'nodes'. Generated JSON:\n{json_str}"
290
+ elif diagram_type == "Process Flow":
291
+ if "start_node" not in json_data or "nodes" not in json_data or "connections" not in json_data:
292
+ return None, f"Invalid JSON structure for Process Flow. Missing 'start_node', 'nodes', or 'connections'. Generated JSON:\n{json_str}"
293
+ elif diagram_type == "WBS Diagram":
294
+ if "project_title" not in json_data or "phases" not in json_data:
295
+ return None, f"Invalid JSON structure for WBS. Missing 'project_title' or 'phases'. Generated JSON:\n{json_str}"
296
+
297
+ # ๋‹ค์ด์–ด๊ทธ๋žจ ์ƒ์„ฑ
298
+ try:
299
+ if diagram_type == "Concept Map":
300
+ diagram = generate_concept_map(json_str, output_format)
301
+ elif diagram_type == "Synoptic Chart":
302
+ diagram = generate_synoptic_chart(json_str, output_format)
303
+ elif diagram_type == "Radial Diagram":
304
+ diagram = generate_radial_diagram(json_str, output_format)
305
+ elif diagram_type == "Process Flow":
306
+ diagram = generate_process_flow_diagram(json_str, output_format)
307
+ elif diagram_type == "WBS Diagram":
308
+ diagram = generate_wbs_diagram(json_str, output_format)
309
+ else:
310
+ return None, "Invalid diagram type"
311
+
312
+ # ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๋ฐ˜ํ™˜๋œ ๊ฒฝ์šฐ
313
+ if isinstance(diagram, str) and diagram.startswith("Error:"):
314
+ return None, f"Diagram generation error: {diagram}\n\nGenerated JSON:\n{json_str}"
315
+
316
+ return diagram, json_str
317
+
318
+ except Exception as e:
319
+ return None, f"Error generating diagram: {str(e)}\n\nGenerated JSON:\n{json_str}"
320
+
321
+ except json.JSONDecodeError as e:
322
+ return None, f"Invalid JSON generated: {str(e)}\n\nGenerated content:\n{generated_json}"
323
+ except Exception as e:
324
+ return None, f"Unexpected error: {str(e)}\n\nGenerated content:\n{generated_json}"
325
+
326
+ if __name__ == "__main__":
327
+ DEFAULT_BASE_COLOR = '#19191a'
328
+
329
+ with gr.Blocks(
330
+ title="Advanced Graph Generator",
331
+ theme=gr.themes.Soft(
332
+ primary_hue="violet",
333
+ secondary_hue="purple",
334
+ ),
335
+ css="""
336
+ /* ๊ทธ๋ผ๋””์–ธํŠธ ๋ฐฐ๊ฒฝ */
337
+ .gradio-container {
338
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;
339
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
340
+ min-height: 100vh;
341
+ }
342
+
343
+ /* ๋ฉ”์ธ ์ปจํ…Œ์ด๋„ˆ ์Šคํƒ€์ผ */
344
+ .main-container {
345
+ background: rgba(255, 255, 255, 0.95);
346
+ backdrop-filter: blur(10px);
347
+ border-radius: 20px;
348
+ padding: 30px;
349
+ margin: 20px auto;
350
+ max-width: 1400px;
351
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
352
+ }
353
+
354
+ /* ํ—ค๋” ์Šคํƒ€์ผ */
355
+ .header-section {
356
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
357
+ color: white;
358
+ padding: 40px;
359
+ border-radius: 15px;
360
+ margin-bottom: 30px;
361
+ text-align: center;
362
+ box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
363
+ }
364
+
365
+ .header-section h1 {
366
+ font-size: 2.5em;
367
+ font-weight: 700;
368
+ margin-bottom: 10px;
369
+ }
370
+
371
+ .header-section p {
372
+ font-size: 1.2em;
373
+ opacity: 0.9;
374
+ }
375
+
376
+ /* ํƒญ ์Šคํƒ€์ผ */
377
+ .gr-tab-item {
378
+ padding: 15px 30px;
379
+ font-size: 1.1em;
380
+ font-weight: 600;
381
+ background: white;
382
+ border-radius: 10px 10px 0 0;
383
+ transition: all 0.3s ease;
384
+ margin-right: 5px;
385
+ }
386
+
387
+ .gr-tab-item:hover {
388
+ background: linear-gradient(135deg, #f5f7fa 0%, #e9ecef 100%);
389
+ }
390
+
391
+ .gr-tab-item.selected {
392
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
393
+ color: white !important;
394
+ }
395
+
396
+ /* ๋ฒ„ํŠผ ์Šคํƒ€์ผ */
397
+ .gr-button {
398
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
399
+ color: white;
400
+ border: none;
401
+ padding: 12px 30px;
402
+ font-size: 1.1em;
403
+ font-weight: 600;
404
+ border-radius: 10px;
405
+ transition: all 0.3s ease;
406
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
407
+ }
408
+
409
+ .gr-button:hover {
410
+ transform: translateY(-2px);
411
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
412
+ }
413
+
414
+ .gr-button.primary {
415
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
416
+ }
417
+
418
+ /* ์ž…๋ ฅ ํ•„๋“œ ์Šคํƒ€์ผ */
419
+ .gr-textbox, .gr-dropdown {
420
+ border: 2px solid #e9ecef;
421
+ border-radius: 10px;
422
+ padding: 12px;
423
+ font-size: 1em;
424
+ transition: all 0.3s ease;
425
+ background: white;
426
+ }
427
+
428
+ .gr-textbox:focus, .gr-dropdown:focus {
429
+ border-color: #667eea;
430
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
431
+ }
432
+
433
+ /* ํŒจ๋„ ์Šคํƒ€์ผ */
434
+ .panel-section {
435
+ background: white;
436
+ border-radius: 15px;
437
+ padding: 25px;
438
+ margin-bottom: 20px;
439
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
440
+ }
441
+
442
+ /* ์ด๋ฏธ์ง€ ์ปจํ…Œ์ด๋„ˆ ์Šคํƒ€์ผ */
443
+ .gr-image {
444
+ border-radius: 15px;
445
+ overflow: hidden;
446
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
447
+ }
448
+
449
+ /* ์˜ˆ์ œ ์ด๋ฏธ์ง€ ์Šคํƒ€์ผ */
450
+ .example-images {
451
+ gap: 20px;
452
+ }
453
+
454
+ .example-images .gr-image {
455
+ transition: transform 0.3s ease;
456
+ }
457
+
458
+ .example-images .gr-image:hover {
459
+ transform: scale(1.02);
460
+ }
461
+
462
+ /* ๋ผ๋””์˜ค ๋ฒ„ํŠผ ์Šคํƒ€์ผ */
463
+ .gr-radio {
464
+ background: white;
465
+ padding: 15px;
466
+ border-radius: 10px;
467
+ border: 2px solid #e9ecef;
468
+ }
469
+
470
+ /* LLM ํƒญ ํŠน๋ณ„ ์Šคํƒ€์ผ */
471
+ .llm-tab {
472
+ background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%);
473
+ padding: 30px;
474
+ border-radius: 15px;
475
+ }
476
+
477
+ .llm-input-section {
478
+ background: white;
479
+ padding: 25px;
480
+ border-radius: 15px;
481
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
482
+ margin-bottom: 20px;
483
+ }
484
+
485
+ /* ๋ฐ˜์‘ํ˜• ๋””์ž์ธ */
486
+ @media (max-width: 768px) {
487
+ .main-container {
488
+ padding: 15px;
489
+ margin: 10px;
490
+ }
491
+
492
+ .header-section h1 {
493
+ font-size: 2em;
494
+ }
495
+
496
+ .gr-tab-item {
497
+ padding: 10px 15px;
498
+ font-size: 1em;
499
+ }
500
+ }
501
+ """
502
+ ) as demo:
503
+ with gr.Column(elem_classes=["main-container"]):
504
+ with gr.Column(elem_classes=["header-section"]):
505
+ gr.Markdown(
506
+ """
507
+ # ๐ŸŽจ Graphify: AI-Powered Diagram Generator
508
+ ### Transform your ideas into beautiful diagrams instantly with AI โšก
509
+ """
510
+ )
511
+
512
+ with gr.Row(variant="panel", elem_classes=["panel-section"]):
513
+ output_format_radio = gr.Radio(
514
+ choices=["png", "svg"],
515
+ value="png",
516
+ label="๐Ÿ“ Output Format",
517
+ interactive=True
518
+ )
519
+
520
+ with gr.Tabs():
521
+ # AI ์–ด์‹œ์Šคํ„ดํŠธ ํƒญ (์ฒซ ๋ฒˆ์งธ)
522
+ with gr.TabItem("๐Ÿค– AI Assistant", elem_classes=["llm-tab"]):
523
+ gr.Markdown(
524
+ """
525
+ ### ๐Ÿ’ก Describe your diagram in Korean or English, and AI will create it for you!
526
+ """
527
+ )
528
+
529
+ with gr.Row():
530
+ with gr.Column(scale=1, elem_classes=["llm-input-section"]):
531
+ prompt_input = gr.Textbox(
532
+ placeholder="์˜ˆ: '๋จธ์‹ ๋Ÿฌ๋‹ ํ”„๋กœ์„ธ์Šค๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ํ”Œ๋กœ์šฐ์ฐจํŠธ๋ฅผ ๋งŒ๋“ค์–ด์ค˜' or 'Create a concept map about climate change'",
533
+ label="๐Ÿ“ Enter your prompt",
534
+ lines=3
535
+ )
536
+
537
+ diagram_type_select = gr.Dropdown(
538
+ choices=["Concept Map", "Synoptic Chart", "Radial Diagram", "Process Flow", "WBS Diagram"],
539
+ value="Concept Map",
540
+ label="๐Ÿ“Š Select Diagram Type",
541
+ interactive=True
542
+ )
543
+
544
+ generate_btn = gr.Button("โœจ Generate with AI", variant="primary", size="lg")
545
+
546
+ generated_json_output = gr.Textbox(
547
+ label="๐Ÿ“„ Generated JSON",
548
+ lines=15,
549
+ interactive=True,
550
+ visible=True
551
+ )
552
+
553
+ with gr.Column(scale=2):
554
+ ai_output_image = gr.Image(
555
+ label="๐ŸŽจ Generated Diagram",
556
+ type="filepath",
557
+ show_download_button=True,
558
+ height=600
559
+ )
560
+
561
+ generate_btn.click(
562
+ fn=generate_with_llm,
563
+ inputs=[prompt_input, diagram_type_select, output_format_radio],
564
+ outputs=[ai_output_image, generated_json_output]
565
+ )
566
+
567
+ with gr.Row(elem_classes=["panel-section"]):
568
+ gr.Examples(
569
+ examples=[
570
+ ["์†Œํ”„ํŠธ์›จ์–ด ๊ฐœ๋ฐœ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ํ”„๋กœ์„ธ์Šค ํ”Œ๋กœ์šฐ๋ฅผ ๋งŒ๋“ค์–ด์ค˜", "Process Flow"],
571
+ ["์ธ๊ณต์ง€๋Šฅ์˜ ์ข…๋ฅ˜์™€ ์‘์šฉ ๋ถ„์•ผ์— ๋Œ€ํ•œ ์ปจ์…‰๋งต์„ ๋งŒ๋“ค์–ด์ค˜", "Concept Map"],
572
+ ["์˜จ๋ผ์ธ ์‡ผํ•‘๋ชฐ ๊ตฌ์ถ• ํ”„๋กœ์ ํŠธ์˜ WBS๋ฅผ ๋งŒ๋“ค์–ด์ค˜", "WBS Diagram"],
573
+ ["์žฌ์ƒ ๊ฐ€๋Šฅ ์—๋„ˆ์ง€์˜ ์ข…๋ฅ˜๋ฅผ ์ค‘์‹ฌ์œผ๋กœ ๋ฐฉ์‚ฌํ˜• ๋‹ค์ด์–ด๊ทธ๋žจ์„ ๋งŒ๋“ค์–ด์ค˜", "Radial Diagram"],
574
+ ["๋จธ์‹ ๋Ÿฌ๋‹ ํŒŒ์ดํ”„๋ผ์ธ์˜ ๋‹จ๊ณ„๋ณ„ ๊ตฌ์„ฑ์„ ์‹œ๋†‰ํ‹ฑ ์ฐจํŠธ๋กœ ๋ณด์—ฌ์ค˜", "Synoptic Chart"]
575
+ ],
576
+ inputs=[prompt_input, diagram_type_select],
577
+ label="๐Ÿ’ญ Example Prompts"
578
+ )
579
+
580
+ # ๊ธฐ์กด ํƒญ๋“ค
581
+ with gr.TabItem("๐Ÿ—บ๏ธ Concept Map"):
582
+ with gr.Row():
583
+ with gr.Column(scale=1, elem_classes=["panel-section"]):
584
+ json_input_cm = gr.Textbox(
585
+ value=CONCEPT_MAP_JSON,
586
+ placeholder="Paste JSON following the documented format",
587
+ label="JSON Input",
588
+ lines=20
589
+ )
590
+ submit_btn_cm = gr.Button("Generate Concept Map", variant="primary")
591
+
592
+ with gr.Column(scale=2):
593
+ output_cm = gr.Image(
594
+ label="Generated Diagram",
595
+ type="filepath",
596
+ show_download_button=True,
597
+ height=500
598
+ )
599
+
600
+ submit_btn_cm.click(
601
+ fn=generate_concept_map,
602
+ inputs=[json_input_cm, output_format_radio],
603
+ outputs=output_cm
604
+ )
605
+
606
+ gr.Markdown("## ๐Ÿ“ธ Examples")
607
+ with gr.Row(elem_classes=["example-images"]):
608
+ gr.Image(value="./images/cm1.svg", label="Sample 1", show_label=True, interactive=False)
609
+ gr.Image(value="./images/cm2.svg", label="Sample 2", show_label=True, interactive=False)
610
+
611
+ with gr.TabItem("๐Ÿ“Š Synoptic Chart"):
612
+ with gr.Row():
613
+ with gr.Column(scale=1, elem_classes=["panel-section"]):
614
+ json_input_sc = gr.Textbox(
615
+ value=SYNOPTIC_CHART_JSON,
616
+ placeholder="Paste JSON following the documented format",
617
+ label="JSON Input",
618
+ lines=20
619
+ )
620
+ submit_btn_sc = gr.Button("Generate Synoptic Chart", variant="primary")
621
+
622
+ with gr.Column(scale=2):
623
+ output_sc = gr.Image(
624
+ label="Generated Diagram",
625
+ type="filepath",
626
+ show_download_button=True,
627
+ height=500
628
+ )
629
+
630
+ submit_btn_sc.click(
631
+ fn=generate_synoptic_chart,
632
+ inputs=[json_input_sc, output_format_radio],
633
+ outputs=output_sc
634
+ )
635
+
636
+ gr.Markdown("## ๐Ÿ“ธ Examples")
637
+ with gr.Row(elem_classes=["example-images"]):
638
+ gr.Image(value="./images/sc1.svg", label="Sample 1", show_label=True, interactive=False)
639
+ gr.Image(value="./images/sc2.svg", label="Sample 2", show_label=True, interactive=False)
640
+
641
+ with gr.TabItem("โ˜€๏ธ Radial Diagram"):
642
+ with gr.Row():
643
+ with gr.Column(scale=1, elem_classes=["panel-section"]):
644
+ json_input_rd = gr.Textbox(
645
+ value=RADIAL_DIAGRAM_JSON,
646
+ placeholder="Paste JSON following the documented format",
647
+ label="JSON Input",
648
+ lines=20
649
+ )
650
+ submit_btn_rd = gr.Button("Generate Radial Diagram", variant="primary")
651
+
652
+ with gr.Column(scale=2):
653
+ output_rd = gr.Image(
654
+ label="Generated Diagram",
655
+ type="filepath",
656
+ show_download_button=True,
657
+ height=500
658
+ )
659
+
660
+ submit_btn_rd.click(
661
+ fn=generate_radial_diagram,
662
+ inputs=[json_input_rd, output_format_radio],
663
+ outputs=output_rd
664
+ )
665
+
666
+ gr.Markdown("## ๐Ÿ“ธ Examples")
667
+ with gr.Row(elem_classes=["example-images"]):
668
+ gr.Image(value="./images/rd1.svg", label="Sample 1", show_label=True, interactive=False)
669
+ gr.Image(value="./images/rd2.svg", label="Sample 2", show_label=True, interactive=False)
670
+ gr.Image(value="./images/rd3.svg", label="Sample 3", show_label=True, interactive=False)
671
+ gr.Image(value="./images/rd4.svg", label="Sample 4", show_label=True, interactive=False)
672
+
673
+ with gr.TabItem("๐Ÿ”„ Process Flow"):
674
+ with gr.Row():
675
+ with gr.Column(scale=1, elem_classes=["panel-section"]):
676
+ json_input_pf = gr.Textbox(
677
+ value=PROCESS_FLOW_JSON,
678
+ placeholder="Paste JSON following the documented format",
679
+ label="JSON Input",
680
+ lines=20
681
+ )
682
+ submit_btn_pf = gr.Button("Generate Process Flow", variant="primary")
683
+
684
+ with gr.Column(scale=2):
685
+ output_pf = gr.Image(
686
+ label="Generated Diagram",
687
+ type="filepath",
688
+ show_download_button=True,
689
+ height=500
690
+ )
691
+
692
+ submit_btn_pf.click(
693
+ fn=generate_process_flow_diagram,
694
+ inputs=[json_input_pf, output_format_radio],
695
+ outputs=output_pf
696
+ )
697
+
698
+ gr.Markdown("## ๐Ÿ“ธ Examples")
699
+ with gr.Row(elem_classes=["example-images"]):
700
+ gr.Image(value="./images/pf1.svg", label="Sample 1", show_label=True, interactive=False)
701
+ gr.Image(value="./images/pf2.svg", label="Sample 2", show_label=True, interactive=False)
702
+
703
+ with gr.TabItem("๐Ÿ“‹ WBS Diagram"):
704
+ with gr.Row():
705
+ with gr.Column(scale=1, elem_classes=["panel-section"]):
706
+ json_input_wbs = gr.Textbox(
707
+ value=WBS_DIAGRAM_JSON,
708
+ placeholder="Paste JSON following the documented format",
709
+ label="JSON Input",
710
+ lines=20
711
+ )
712
+ submit_btn_wbs = gr.Button("Generate WBS Diagram", variant="primary")
713
+
714
+ with gr.Column(scale=2):
715
+ output_wbs = gr.Image(
716
+ label="Generated Diagram",
717
+ type="filepath",
718
+ show_download_button=True,
719
+ height=500
720
+ )
721
+
722
+ submit_btn_wbs.click(
723
+ fn=generate_wbs_diagram,
724
+ inputs=[json_input_wbs, output_format_radio],
725
+ outputs=output_wbs
726
+ )
727
+
728
+ gr.Markdown("## ๐Ÿ“ธ Examples")
729
+ with gr.Row(elem_classes=["example-images"]):
730
+ gr.Image(value="./images/wd1.svg", label="Sample 1", show_label=True, interactive=False)
731
+ gr.Image(value="./images/wd2.svg", label="Sample 2", show_label=True, interactive=False)
732
+
733
+ demo.launch(
734
+ mcp_server=True,
735
+ share=False,
736
+ server_port=7860,
737
+ server_name="0.0.0.0"
738
+ )