fantaxy commited on
Commit
77cd6aa
Β·
verified Β·
1 Parent(s): cca51e2

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +277 -0
app.py ADDED
@@ -0,0 +1,277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ app.py ――――――――――――――――――――――――――――――――――――――――――――――――
3
+ βœ“ Preview Β· Save Β· Load κΈ°λŠ₯ 포함
4
+ βœ“ Load μ‹œ λ…Έλ“œ/μ’Œν‘œ/μ—£μ§€ μžλ™ 볡원
5
+ βœ“ 동적 λ Œλ”λ§μœΌλ‘œ κΈ°μ‘΄ WorkflowBuilder μ—…λ°μ΄νŠΈ
6
+ """
7
+
8
+ import os, json, typing, tempfile
9
+ import gradio as gr
10
+ from gradio_workflowbuilder import WorkflowBuilder
11
+
12
+ # -------------------------------------------------------------------
13
+ # πŸ› οΈ 헬퍼
14
+ # -------------------------------------------------------------------
15
+ def export_pretty(data: typing.Dict[str, typing.Any]) -> str:
16
+ return json.dumps(data, indent=2, ensure_ascii=False) if data else "No workflow to export"
17
+
18
+ def export_file(data: typing.Dict[str, typing.Any]) -> typing.Optional[str]:
19
+ if not data:
20
+ return None
21
+ fd, path = tempfile.mkstemp(suffix=".json")
22
+ with os.fdopen(fd, "w", encoding="utf-8") as f:
23
+ json.dump(data, f, ensure_ascii=False, indent=2)
24
+ return path
25
+
26
+ def load_json_file(file_obj):
27
+ """JSON 파일 읽기"""
28
+ if file_obj is None:
29
+ return None, "No file selected"
30
+
31
+ try:
32
+ with open(file_obj.name, "r", encoding="utf-8") as f:
33
+ data = json.load(f)
34
+
35
+ # 데이터 검증
36
+ if not isinstance(data, dict):
37
+ return None, "Invalid format: not a dictionary"
38
+
39
+ # ν•„μˆ˜ ν•„λ“œ 확인
40
+ if 'nodes' not in data:
41
+ data['nodes'] = []
42
+ if 'edges' not in data:
43
+ data['edges'] = []
44
+
45
+ nodes_count = len(data.get('nodes', []))
46
+ edges_count = len(data.get('edges', []))
47
+
48
+ return data, f"βœ… Loaded: {nodes_count} nodes, {edges_count} edges"
49
+
50
+ except Exception as e:
51
+ return None, f"❌ Error: {str(e)}"
52
+
53
+ def create_sample_workflow():
54
+ """μƒ˜ν”Œ μ›Œν¬ν”Œλ‘œμš° 생성"""
55
+ return {
56
+ "nodes": [
57
+ {
58
+ "id": "node_1",
59
+ "type": "default",
60
+ "position": {"x": 100, "y": 100},
61
+ "data": {"label": "Start Node"}
62
+ },
63
+ {
64
+ "id": "node_2",
65
+ "type": "default",
66
+ "position": {"x": 300, "y": 100},
67
+ "data": {"label": "Process"}
68
+ },
69
+ {
70
+ "id": "node_3",
71
+ "type": "default",
72
+ "position": {"x": 500, "y": 100},
73
+ "data": {"label": "End Node"}
74
+ }
75
+ ],
76
+ "edges": [
77
+ {
78
+ "id": "edge_1",
79
+ "source": "node_1",
80
+ "target": "node_2"
81
+ },
82
+ {
83
+ "id": "edge_2",
84
+ "source": "node_2",
85
+ "target": "node_3"
86
+ }
87
+ ]
88
+ }
89
+
90
+ # -------------------------------------------------------------------
91
+ # 🎨 CSS
92
+ # -------------------------------------------------------------------
93
+ CSS = """
94
+ .main-container{max-width:1600px;margin:0 auto;}
95
+ .workflow-section{margin-bottom:2rem;min-height:500px;}
96
+ .button-row{display:flex;gap:1rem;justify-content:center;margin:1rem 0;}
97
+ .status-box{
98
+ padding:10px;border-radius:5px;margin-top:10px;
99
+ background:#f0f9ff;border:1px solid #3b82f6;color:#1e40af;
100
+ }
101
+ .component-description{
102
+ padding:24px;background:linear-gradient(135deg,#f8fafc 0%,#e2e8f0 100%);
103
+ border-left:4px solid #3b82f6;border-radius:12px;
104
+ box-shadow:0 2px 8px rgba(0,0,0,.05);margin:16px 0;
105
+ }
106
+ .workflow-container{position:relative;}
107
+ """
108
+
109
+ # -------------------------------------------------------------------
110
+ # πŸ–₯️ Gradio μ•±
111
+ # -------------------------------------------------------------------
112
+ with gr.Blocks(title="🎨 Visual Workflow Builder", theme=gr.themes.Soft(), css=CSS) as demo:
113
+
114
+ with gr.Column(elem_classes=["main-container"]):
115
+ gr.Markdown("# 🎨 Visual Workflow Builder\n**Create sophisticated workflows with drag-and-drop simplicity.**")
116
+
117
+ gr.HTML(
118
+ """
119
+ <div class="component-description">
120
+ <p style="font-size:16px;margin:0;">Powered by <strong>svelteflow</strong> β€’ Create custom workflows</p>
121
+ </div>
122
+ """
123
+ )
124
+
125
+ # State for storing loaded data
126
+ loaded_data = gr.State(None)
127
+ trigger_update = gr.State(False)
128
+
129
+ # ─── Dynamic Workflow Container ───
130
+ with gr.Column(elem_classes=["workflow-container"]):
131
+ @gr.render(inputs=[loaded_data, trigger_update])
132
+ def render_workflow(data, trigger):
133
+ """λ™μ μœΌλ‘œ WorkflowBuilder λ Œλ”λ§"""
134
+ workflow_value = data if data else {"nodes": [], "edges": []}
135
+
136
+ return WorkflowBuilder(
137
+ label="🎨 Visual Workflow Designer",
138
+ info="Drag from output ➜ input β€’ Click nodes to edit properties",
139
+ value=workflow_value,
140
+ elem_id="main_workflow"
141
+ )
142
+
143
+ # ─── Control Panel ───
144
+ gr.Markdown("## πŸ’Ύ Export Β· πŸ“‚ Import")
145
+
146
+ with gr.Row(elem_classes=["button-row"]):
147
+ with gr.Column(scale=2):
148
+ file_upload = gr.File(
149
+ label="πŸ“‚ Select JSON file",
150
+ file_types=[".json"],
151
+ type="filepath"
152
+ )
153
+ with gr.Column(scale=1):
154
+ btn_load = gr.Button("πŸ“₯ Load Workflow", variant="primary", size="lg")
155
+ btn_sample = gr.Button("🎯 Load Sample", variant="secondary")
156
+
157
+ with gr.Row(elem_classes=["button-row"]):
158
+ btn_preview = gr.Button("πŸ‘οΈ Preview JSON")
159
+ btn_download = gr.DownloadButton("πŸ’Ύ Download JSON")
160
+ btn_reset = gr.Button("πŸ”„ Reset", variant="stop")
161
+
162
+ # Status
163
+ status_text = gr.Textbox(
164
+ label="Status",
165
+ value="Ready",
166
+ elem_classes=["status-box"],
167
+ interactive=False
168
+ )
169
+
170
+ # Code View
171
+ code_view = gr.Code(
172
+ language="json",
173
+ label="JSON Preview",
174
+ lines=10,
175
+ visible=False
176
+ )
177
+
178
+ btn_toggle_code = gr.Button("πŸ“‹ Toggle Code View", size="sm")
179
+
180
+ # ─── Event Handlers ───
181
+
182
+ # File upload β†’ Store data
183
+ file_upload.change(
184
+ fn=load_json_file,
185
+ inputs=file_upload,
186
+ outputs=[loaded_data, status_text]
187
+ )
188
+
189
+ # Load button β†’ Trigger render update
190
+ btn_load.click(
191
+ fn=lambda current_trigger: not current_trigger,
192
+ inputs=trigger_update,
193
+ outputs=trigger_update
194
+ )
195
+
196
+ # Sample workflow
197
+ btn_sample.click(
198
+ fn=lambda: (create_sample_workflow(), "βœ… Sample loaded"),
199
+ outputs=[loaded_data, status_text]
200
+ ).then(
201
+ fn=lambda current_trigger: not current_trigger,
202
+ inputs=trigger_update,
203
+ outputs=trigger_update
204
+ )
205
+
206
+ # Reset
207
+ btn_reset.click(
208
+ fn=lambda: ({"nodes": [], "edges": []}, "βœ… Reset complete"),
209
+ outputs=[loaded_data, status_text]
210
+ ).then(
211
+ fn=lambda current_trigger: not current_trigger,
212
+ inputs=trigger_update,
213
+ outputs=trigger_update
214
+ )
215
+
216
+ # Toggle code view
217
+ btn_toggle_code.click(
218
+ fn=lambda visible: gr.update(visible=not visible),
219
+ inputs=code_view,
220
+ outputs=code_view
221
+ )
222
+
223
+ # Preview - ν˜„μž¬ μ›Œν¬ν”Œλ‘œμš°λ₯Ό κ°€μ Έμ™€μ„œ ν‘œμ‹œ
224
+ btn_preview.click(
225
+ fn=lambda data: (gr.update(visible=True), export_pretty(data)),
226
+ inputs=loaded_data,
227
+ outputs=[code_view, code_view]
228
+ )
229
+
230
+ # Download - loaded_dataλ₯Ό 파일둜 내보내기
231
+ btn_download.click(
232
+ fn=export_file,
233
+ inputs=loaded_data
234
+ )
235
+
236
+ # ─── Instructions ───
237
+ with gr.Accordion("πŸ“– How to Use", open=False):
238
+ gr.Markdown(
239
+ """
240
+ ### πŸš€ Quick Start
241
+ 1. **Create** β€” Add nodes and connect them in the workflow designer
242
+ 2. **Save** β€” Click "Download JSON" to save your workflow
243
+ 3. **Load** β€” Select a JSON file and click "Load Workflow"
244
+
245
+ ### πŸ“ Workflow Format
246
+ ```json
247
+ {
248
+ "nodes": [
249
+ {
250
+ "id": "unique_id",
251
+ "type": "default",
252
+ "position": {"x": 100, "y": 100},
253
+ "data": {"label": "Node Name"}
254
+ }
255
+ ],
256
+ "edges": [
257
+ {
258
+ "id": "edge_id",
259
+ "source": "source_node_id",
260
+ "target": "target_node_id"
261
+ }
262
+ ]
263
+ }
264
+ ```
265
+
266
+ ### πŸ’‘ Tips
267
+ - The workflow will automatically update when you load a file
268
+ - Use "Load Sample" to see an example workflow
269
+ - Toggle code view to see the JSON structure
270
+ """
271
+ )
272
+
273
+ # -------------------------------------------------------------------
274
+ # πŸš€ μ‹€ν–‰
275
+ # -------------------------------------------------------------------
276
+ if __name__ == "__main__":
277
+ demo.launch(server_name="0.0.0.0", show_error=True)