fantaxy commited on
Commit
1e9411d
·
verified ·
1 Parent(s): 43f50cc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +427 -246
app.py CHANGED
@@ -2,7 +2,7 @@
2
  app.py ――――――――――――――――――――――――――――――――――――――――――――――――
3
  ✓ Preview · Save · Load 기능 포함
4
  ✓ Load 시 노드/좌표/엣지 자동 복원
5
- State를 사용한 안정적인 JSON 로드
6
  """
7
 
8
  import os, json, typing, tempfile
@@ -13,6 +13,7 @@ from gradio_workflowbuilder import WorkflowBuilder
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]:
@@ -23,301 +24,481 @@ def export_file(data: typing.Dict[str, typing.Any]) -> typing.Optional[str]:
23
  json.dump(data, f, ensure_ascii=False, indent=2)
24
  return path
25
 
26
- def load_workflow_from_file(file_obj):
27
- """JSON 파일을 읽어서 데이터만 반환"""
28
  if file_obj is None:
29
- return None
30
 
31
  try:
32
  with open(file_obj.name, "r", encoding="utf-8") as f:
33
  data = json.load(f)
34
- return data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  except Exception as e:
36
- print(f"Error loading file: {e}")
37
- return None
38
 
39
- def update_workflow_display(workflow_data):
40
- """워크플로우 데이터를 코드뷰에 표시"""
41
- if workflow_data:
42
- return json.dumps(workflow_data, indent=2, ensure_ascii=False)
43
- return "No workflow loaded"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
- def debug_workflow(data):
46
- """현재 워크플로우 상태 디버깅"""
47
  if not data:
48
- return "No workflow data"
49
- nodes_count = len(data.get('nodes', []))
50
- edges_count = len(data.get('edges', []))
51
- return f"Nodes: {nodes_count}, Edges: {edges_count}"
 
 
 
 
 
 
52
 
53
  # -------------------------------------------------------------------
54
  # 🎨 CSS
55
  # -------------------------------------------------------------------
56
  CSS = """
57
  .main-container{max-width:1600px;margin:0 auto;}
58
- .workflow-section{margin-bottom:2rem;}
59
  .button-row{display:flex;gap:1rem;justify-content:center;margin:1rem 0;}
 
 
 
 
60
  .component-description{
61
  padding:24px;background:linear-gradient(135deg,#f8fafc 0%,#e2e8f0 100%);
62
  border-left:4px solid #3b82f6;border-radius:12px;
63
  box-shadow:0 2px 8px rgba(0,0,0,.05);margin:16px 0;
64
- font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
65
  }
66
- .component-description p{margin:10px 0;line-height:1.6;color:#374151;}
67
- .base-description{font-size:17px;font-weight:600;color:#1e293b;}
68
- .base-description strong{color:#3b82f6;font-weight:700;}
69
- .feature-description{font-size:16px;font-weight:500;color:#1e293b;}
70
- .customization-note{font-size:15px;font-style:italic;color:#64748b;}
71
- .sample-intro{font-size:15px;font-weight:600;color:#1e293b;margin-top:16px;
72
- border-top:1px solid #e2e8f0;padding-top:16px;}
73
- .load-button{background:#10b981 !important;}
74
  """
75
 
76
  # -------------------------------------------------------------------
77
  # 🖥️ Gradio 앱
78
  # -------------------------------------------------------------------
79
  with gr.Blocks(title="🎨 Visual Workflow Builder", theme=gr.themes.Soft(), css=CSS) as demo:
80
- # State로 워크플로우 데이터 관리
81
- workflow_state = gr.State(value={"nodes": [], "edges": []})
82
-
83
- with gr.Column(elem_classes=["main-container"]):
84
- gr.Markdown("# 🎨 Visual Workflow Builder\n**Create sophisticated workflows with drag-and-drop simplicity.**")
 
 
 
 
 
85
 
86
- gr.HTML(
87
- """
88
- <div class="component-description">
89
- <p class="base-description">Base custom component powered by <strong>svelteflow</strong>.</p>
90
- <p class="feature-description">Create custom workflows.</p>
91
- <p class="customization-note">You can customise the nodes as well as the design of the workflow.</p>
92
- <p class="sample-intro">Here is a sample:</p>
93
- </div>
94
- """
95
  )
96
 
97
- # ─── Workflow Builder ───
98
- with gr.Column(elem_classes=["workflow-section"]):
99
- workflow_builder = WorkflowBuilder(
100
- label="🎨 Visual Workflow Designer",
101
- info="Drag from output input • Click nodes to edit properties",
102
- value={"nodes": [], "edges": []}
 
 
 
 
 
 
 
 
103
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
 
105
- # ─── Export / Import ───
106
- gr.Markdown("## 💾 Export · 📂 Import")
 
 
 
 
107
 
108
- with gr.Row(elem_classes=["button-row"]):
109
- btn_preview = gr.Button("🔄 Preview JSON")
110
- btn_download = gr.DownloadButton("💾 Save JSON file")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
- with gr.Row(elem_classes=["button-row"]):
113
- file_upload = gr.File(label="📂 Select JSON file", file_types=[".json"])
114
- btn_load = gr.Button("📥 Load into Workflow", variant="primary", elem_classes=["load-button"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
- code_view = gr.Code(language="json", label="Workflow Configuration", lines=14)
 
 
 
 
117
 
118
- # 디버깅 섹션
119
- with gr.Row():
120
- btn_debug = gr.Button("🔍 Debug Current State", variant="secondary")
121
- debug_output = gr.Textbox(label="Debug Info", lines=1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
 
123
- # ─── 이벤트 연결 ───
124
-
125
- # Preview 버튼 - 현재 워크플로우를 JSON으로 표시
126
- btn_preview.click(
127
- fn=export_pretty,
128
- inputs=workflow_builder,
129
- outputs=code_view
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  )
131
 
132
- # Download 버튼
133
- btn_download.click(
134
- fn=export_file,
135
- inputs=workflow_builder
136
- )
 
 
 
137
 
138
- # 파일 업로드 -> State에 저장
139
- file_upload.change(
140
- fn=load_workflow_from_file,
141
- inputs=file_upload,
142
- outputs=workflow_state
143
- ).then(
144
- fn=update_workflow_display,
145
- inputs=workflow_state,
146
- outputs=code_view
147
- )
148
 
149
- # Load 버튼 클릭 -> State에서 WorkflowBuilder로 전달
150
- btn_load.click(
151
- fn=lambda x: x, # State 값을 그대로 전달
152
- inputs=workflow_state,
153
- outputs=workflow_builder
154
- )
 
 
 
 
 
 
 
 
 
 
 
155
 
156
- # Debug 버튼
157
- btn_debug.click(
158
- fn=debug_workflow,
159
- inputs=workflow_builder,
160
- outputs=debug_output
161
- )
162
 
163
- # WorkflowBuilder 값 변경 시 State 업데이트
164
- workflow_builder.change(
165
- fn=lambda x: x,
166
- inputs=workflow_builder,
167
- outputs=workflow_state
168
- )
169
-
170
- # ─── 샘플 워크플로우 ───
171
- gr.Markdown("### 🎯 Sample Workflows")
172
 
173
- def create_sample_workflow():
174
- return {
175
- "nodes": [
176
- {
177
- "id": "node_1",
178
- "type": "custom",
179
- "position": {"x": 100, "y": 100},
180
- "data": {"label": "Input Node", "value": "Start"}
181
- },
182
- {
183
- "id": "node_2",
184
- "type": "custom",
185
- "position": {"x": 300, "y": 100},
186
- "data": {"label": "Process Node", "value": "Transform"}
187
- },
188
- {
189
- "id": "node_3",
190
- "type": "custom",
191
- "position": {"x": 500, "y": 100},
192
- "data": {"label": "Output Node", "value": "Result"}
193
- }
194
- ],
195
- "edges": [
196
- {
197
- "id": "edge_1",
198
- "source": "node_1",
199
- "target": "node_2",
200
- "type": "default"
201
- },
202
- {
203
- "id": "edge_2",
204
- "source": "node_2",
205
- "target": "node_3",
206
- "type": "default"
207
- }
208
- ]
209
- }
210
 
211
- with gr.Row():
212
- btn_sample1 = gr.Button("📊 Load Linear Workflow", variant="primary")
213
- btn_sample2 = gr.Button("🔀 Load Branching Workflow", variant="primary")
214
 
215
- def create_branching_workflow():
216
- return {
217
- "nodes": [
218
- {
219
- "id": "start",
220
- "type": "custom",
221
- "position": {"x": 100, "y": 200},
222
- "data": {"label": "Start", "value": "Input Data"}
223
- },
224
- {
225
- "id": "branch1",
226
- "type": "custom",
227
- "position": {"x": 300, "y": 100},
228
- "data": {"label": "Branch A", "value": "Process A"}
229
- },
230
- {
231
- "id": "branch2",
232
- "type": "custom",
233
- "position": {"x": 300, "y": 300},
234
- "data": {"label": "Branch B", "value": "Process B"}
235
- },
236
- {
237
- "id": "merge",
238
- "type": "custom",
239
- "position": {"x": 500, "y": 200},
240
- "data": {"label": "Merge", "value": "Combine Results"}
241
- }
242
- ],
243
- "edges": [
244
- {"id": "e1", "source": "start", "target": "branch1", "type": "default"},
245
- {"id": "e2", "source": "start", "target": "branch2", "type": "default"},
246
- {"id": "e3", "source": "branch1", "target": "merge", "type": "default"},
247
- {"id": "e4", "source": "branch2", "target": "merge", "type": "default"}
248
- ]
249
- }
250
-
251
- # 샘플 로드 이벤트
252
- btn_sample1.click(
253
- fn=create_sample_workflow,
254
- outputs=workflow_state
255
- ).then(
256
- fn=lambda x: x,
257
- inputs=workflow_state,
258
- outputs=workflow_builder
259
- ).then(
260
- fn=update_workflow_display,
261
- inputs=workflow_state,
262
- outputs=code_view
263
- )
264
 
265
- btn_sample2.click(
266
- fn=create_branching_workflow,
267
- outputs=workflow_state
268
- ).then(
269
- fn=lambda x: x,
270
- inputs=workflow_state,
271
- outputs=workflow_builder
272
- ).then(
273
- fn=update_workflow_display,
274
- inputs=workflow_state,
275
- outputs=code_view
276
  )
277
 
278
- # ─── 사용 방법 ───
279
- with gr.Accordion("📖 How to Use", open=False):
280
- gr.Markdown(
281
- """
282
- ### 🚀 Quick Start
283
- 1. **Create Workflow** — Add nodes and connect them
284
- 2. **Save** — Click "Save JSON file" to download
285
- 3. **Load** — Select file → Click "Load into Workflow"
286
-
287
- ### 📝 Loading Workflows
288
- 1. Click "Select JSON file" and choose your workflow file
289
- 2. Click the green "Load into Workflow" button
290
- 3. Your workflow will appear in the visual designer
291
-
292
- ### 🎯 Try Sample Workflows
293
- - Click sample buttons to see example workflows
294
- - Modify them to understand the structure
295
-
296
- ### 📋 JSON Structure
297
- ```json
298
- {
299
- "nodes": [
300
- {
301
- "id": "unique_id",
302
- "type": "custom",
303
- "position": {"x": 100, "y": 100},
304
- "data": {"label": "Node Name"}
305
- }
306
- ],
307
- "edges": [
308
- {
309
- "id": "edge_id",
310
- "source": "node_id_1",
311
- "target": "node_id_2"
312
- }
313
- ]
314
- }
315
- ```
316
- """
317
- )
318
-
319
  # -------------------------------------------------------------------
320
  # 🚀 실행
321
  # -------------------------------------------------------------------
322
  if __name__ == "__main__":
323
- demo.launch(server_name="0.0.0.0", show_error=True)
 
 
 
 
 
2
  app.py ――――――――――――――――――――――――――――――――――――――――――――――――
3
  ✓ Preview · Save · Load 기능 포함
4
  ✓ Load 시 노드/좌표/엣지 자동 복원
5
+ gr.update()를 사용한 강제 업데이트
6
  """
7
 
8
  import os, json, typing, tempfile
 
13
  # 🛠️ 헬퍼
14
  # -------------------------------------------------------------------
15
  def export_pretty(data: typing.Dict[str, typing.Any]) -> str:
16
+ print(f"[Export] Current workflow data: {json.dumps(data, indent=2)}")
17
  return json.dumps(data, indent=2, ensure_ascii=False) if data else "No workflow to export"
18
 
19
  def export_file(data: typing.Dict[str, typing.Any]) -> typing.Optional[str]:
 
24
  json.dump(data, f, ensure_ascii=False, indent=2)
25
  return path
26
 
27
+ def import_workflow(file_obj):
28
+ """파일에서 JSON 읽고 WorkflowBuilder를 업데이트"""
29
  if file_obj is None:
30
+ return None, "No file selected", "Status: No file", gr.update(visible=False)
31
 
32
  try:
33
  with open(file_obj.name, "r", encoding="utf-8") as f:
34
  data = json.load(f)
35
+
36
+ print(f"[Import] Loaded data: {json.dumps(data, indent=2)}")
37
+
38
+ # 데이터 검증
39
+ if not isinstance(data, dict):
40
+ return None, "Invalid format: not a dictionary", "Status: Invalid format", gr.update(visible=False)
41
+
42
+ # 필수 필드 확인
43
+ if 'nodes' not in data:
44
+ data['nodes'] = []
45
+ if 'edges' not in data:
46
+ data['edges'] = []
47
+
48
+ nodes_count = len(data.get('nodes', []))
49
+ edges_count = len(data.get('edges', []))
50
+
51
+ # 직접 데이터 반환
52
+ return (
53
+ data, # 직접 데이터 반환
54
+ json.dumps(data, indent=2, ensure_ascii=False),
55
+ f"✅ Loaded: {nodes_count} nodes, {edges_count} edges",
56
+ gr.update(visible=True) # JavaScript 실행 버튼 표시
57
+ )
58
+
59
  except Exception as e:
60
+ print(f"[Import] Error: {str(e)}")
61
+ return None, f"Error: {str(e)}", f"❌ Error: {str(e)}", gr.update(visible=False)
62
 
63
+ def reset_workflow():
64
+ """워크플로우 초기화"""
65
+ empty_workflow = {"nodes": [], "edges": []}
66
+ return empty_workflow, "Reset complete"
67
+
68
+ def set_sample_workflow():
69
+ """샘플 워크플로우 설정"""
70
+ sample = {
71
+ "nodes": [
72
+ {
73
+ "id": "1",
74
+ "type": "default",
75
+ "position": {"x": 100, "y": 100},
76
+ "data": {"label": "Start Node"}
77
+ },
78
+ {
79
+ "id": "2",
80
+ "type": "default",
81
+ "position": {"x": 300, "y": 100},
82
+ "data": {"label": "Process"}
83
+ },
84
+ {
85
+ "id": "3",
86
+ "type": "default",
87
+ "position": {"x": 500, "y": 100},
88
+ "data": {"label": "End Node"}
89
+ }
90
+ ],
91
+ "edges": [
92
+ {
93
+ "id": "e1-2",
94
+ "source": "1",
95
+ "target": "2"
96
+ },
97
+ {
98
+ "id": "e2-3",
99
+ "source": "2",
100
+ "target": "3"
101
+ }
102
+ ]
103
+ }
104
+
105
+ print(f"[Sample] Setting workflow: {json.dumps(sample, indent=2)}")
106
+ return sample, json.dumps(sample, indent=2, ensure_ascii=False), "✅ Sample loaded"
107
 
108
+ def debug_current_state(data):
109
+ """현재 상태 디버깅"""
110
  if not data:
111
+ return "Empty workflow", "{}"
112
+
113
+ nodes = data.get('nodes', [])
114
+ edges = data.get('edges', [])
115
+
116
+ debug_info = f"Nodes: {len(nodes)}, Edges: {len(edges)}"
117
+ if nodes:
118
+ debug_info += f"\nFirst node: {nodes[0].get('id', 'unknown')}"
119
+
120
+ return debug_info, json.dumps(data, indent=2)
121
 
122
  # -------------------------------------------------------------------
123
  # 🎨 CSS
124
  # -------------------------------------------------------------------
125
  CSS = """
126
  .main-container{max-width:1600px;margin:0 auto;}
127
+ .workflow-section{margin-bottom:2rem;min-height:500px;}
128
  .button-row{display:flex;gap:1rem;justify-content:center;margin:1rem 0;}
129
+ .status-box{
130
+ padding:10px;border-radius:5px;margin-top:10px;
131
+ background:#f0f9ff;border:1px solid #3b82f6;color:#1e40af;
132
+ }
133
  .component-description{
134
  padding:24px;background:linear-gradient(135deg,#f8fafc 0%,#e2e8f0 100%);
135
  border-left:4px solid #3b82f6;border-radius:12px;
136
  box-shadow:0 2px 8px rgba(0,0,0,.05);margin:16px 0;
 
137
  }
 
 
 
 
 
 
 
 
138
  """
139
 
140
  # -------------------------------------------------------------------
141
  # 🖥️ Gradio 앱
142
  # -------------------------------------------------------------------
143
  with gr.Blocks(title="🎨 Visual Workflow Builder", theme=gr.themes.Soft(), css=CSS) as demo:
144
+
145
+ gr.Markdown("# 🎨 Visual Workflow Builder\n**Debug Version - Testing JSON Load**")
146
+
147
+ # 상태 표시
148
+ status_text = gr.Textbox(
149
+ label="Status",
150
+ value="Ready",
151
+ elem_classes=["status-box"],
152
+ interactive=False
153
+ )
154
 
155
+ # ─── Workflow Builder ───
156
+ with gr.Column(elem_classes=["workflow-section"]):
157
+ workflow_builder = WorkflowBuilder(
158
+ label="🎨 Visual Workflow Designer",
159
+ value={"nodes": [], "edges": []},
160
+ elem_id="workflow_builder"
 
 
 
161
  )
162
 
163
+ # ─── Control Panel ───
164
+ gr.Markdown("## 🎮 Control Panel")
165
+
166
+ # JavaScript force update button (hidden by default)
167
+ btn_force_update = gr.Button(" Force Update (JavaScript)", variant="warning", visible=False)
168
+ loaded_data_store = gr.State(None)
169
+
170
+ with gr.Row():
171
+ with gr.Column(scale=1):
172
+ gr.Markdown("### 📥 Import")
173
+ file_upload = gr.File(
174
+ label="Select JSON file",
175
+ file_types=[".json"],
176
+ type="filepath"
177
  )
178
+ btn_load = gr.Button("🚀 Load File", variant="primary", size="lg")
179
+
180
+ with gr.Column(scale=1):
181
+ gr.Markdown("### 🧪 Test")
182
+ btn_sample = gr.Button("📊 Load Sample", variant="primary", size="lg")
183
+ btn_reset = gr.Button("🔄 Reset", variant="stop", size="lg")
184
+
185
+ with gr.Column(scale=1):
186
+ gr.Markdown("### 💾 Export")
187
+ btn_preview = gr.Button("👁️ Preview JSON", size="lg")
188
+ btn_download = gr.DownloadButton("💾 Download JSON", size="lg")
189
+
190
+ # ─── Alternative Render Method ───
191
+ with gr.Accordion("🔬 Alternative Load Method", open=False):
192
+ gr.Markdown("If normal loading fails, try this dynamic rendering approach:")
193
+ btn_render = gr.Button("🎯 Render with Data", variant="primary")
194
+ render_container = gr.Column()
195
+
196
+ @gr.render(inputs=[loaded_data_store], triggers=[btn_render.click])
197
+ def render_workflow_dynamic(data):
198
+ if data:
199
+ with gr.Column():
200
+ gr.Markdown("### Dynamically Rendered Workflow")
201
+ return WorkflowBuilder(
202
+ label="Dynamic Workflow",
203
+ value=data
204
+ )
205
+ else:
206
+ return gr.Markdown("No data loaded yet")
207
 
208
+ # ─── Debug Section ──���
209
+ with gr.Accordion("🔍 Debug Info", open=True):
210
+ with gr.Row():
211
+ debug_text = gr.Textbox(label="State Info", lines=2)
212
+ debug_json = gr.Code(language="json", label="Current JSON", lines=10)
213
+ btn_debug = gr.Button("🔍 Update Debug Info", variant="secondary")
214
 
215
+ # ─── Code View ───
216
+ code_view = gr.Code(language="json", label="JSON Preview", lines=15)
217
+
218
+ # ─── Event Handlers ───
219
+
220
+ # Load file - 데이터를 State에 저장
221
+ file_upload.change(
222
+ fn=import_workflow,
223
+ inputs=file_upload,
224
+ outputs=[loaded_data_store, code_view, status_text, btn_force_update]
225
+ )
226
+
227
+ # Load button - State에서 WorkflowBuilder로 전송
228
+ btn_load.click(
229
+ fn=lambda data: data if data else {"nodes": [], "edges": []},
230
+ inputs=loaded_data_store,
231
+ outputs=workflow_builder
232
+ )
233
+
234
+ # Force update with JavaScript
235
+ btn_force_update.click(
236
+ fn=None,
237
+ inputs=loaded_data_store,
238
+ outputs=None,
239
+ js="""
240
+ (data) => {
241
+ console.log('[Force Update] Attempting to update WorkflowBuilder with:', data);
242
+
243
+ // WorkflowBuilder 엘리먼트 찾기
244
+ const workflowElement = document.querySelector('#workflow_builder');
245
+ if (workflowElement) {
246
+ // 여러 방법 시도
247
+
248
+ // 방법 1: 직접 value 설정
249
+ if (workflowElement.__vue__) {
250
+ workflowElement.__vue__.value = data;
251
+ workflowElement.__vue__.$forceUpdate();
252
+ }
253
+
254
+ // 방법 2: 이벤트 발생
255
+ const event = new CustomEvent('input', {
256
+ detail: data,
257
+ bubbles: true
258
+ });
259
+ workflowElement.dispatchEvent(event);
260
+
261
+ // 방법 3: Gradio 이벤트
262
+ if (window.gradio_config && window.gradio_config.components) {
263
+ const component = Object.values(window.gradio_config.components).find(
264
+ c => c.props && c.props.elem_id === 'workflow_builder'
265
+ );
266
+ if (component) {
267
+ component.props.value = data;
268
+ }
269
+ }
270
+ }
271
 
272
+ return data;
273
+ }
274
+ """
275
+ )
276
+
277
+ # Sample workflow
278
+ btn_sample.click(
279
+ fn=set_sample_workflow,
280
+ outputs=[workflow_builder, code_view, status_text]
281
+ )
282
+
283
+ # Reset
284
+ btn_reset.click(
285
+ fn=reset_workflow,
286
+ outputs=[workflow_builder, status_text]
287
+ )
288
+
289
+ # Preview
290
+ btn_preview.click(
291
+ fn=export_pretty,
292
+ inputs=workflow_builder,
293
+ outputs=code_view
294
+ )
295
+
296
+ # Download
297
+ btn_download.click(
298
+ fn=export_file,
299
+ inputs=workflow_builder
300
+ )
301
+
302
+ # Debug
303
+ btn_debug.click(
304
+ fn=debug_current_state,
305
+ inputs=workflow_builder,
306
+ outputs=[debug_text, debug_json]
307
+ )
308
+
309
+ # Auto-debug on change
310
+ workflow_builder.change(
311
+ fn=lambda x: f"Changed - Nodes: {len(x.get('nodes', []))}, Edges: {len(x.get('edges', []))}",
312
+ inputs=workflow_builder,
313
+ outputs=status_text
314
+ )
315
+
316
+ # HTML viewer 업데이트
317
+ workflow_builder.change(
318
+ fn=create_html_view,
319
+ inputs=workflow_builder,
320
+ outputs=html_viewer
321
+ )
322
+
323
+ # Also update HTML view when loading
324
+ loaded_data_store.change(
325
+ fn=create_html_view,
326
+ inputs=loaded_data_store,
327
+ outputs=html_viewer
328
+ )
329
 
330
+ # ─── Instructions ───
331
+ with gr.Accordion("📖 Troubleshooting", open=True):
332
+ gr.Markdown(
333
+ """
334
+ ### 🔧 If JSON doesn't load visually:
335
 
336
+ 1. **Try the Sample first** - Click "Load Sample" to test if the component works
337
+ 2. **Check Console** - Open browser DevTools (F12) for errors
338
+ 3. **File Format** - Ensure your JSON has this structure:
339
+ ```json
340
+ {
341
+ "nodes": [{
342
+ "id": "1",
343
+ "type": "default",
344
+ "position": {"x": 100, "y": 100},
345
+ "data": {"label": "Node"}
346
+ }],
347
+ "edges": [{
348
+ "id": "e1",
349
+ "source": "1",
350
+ "target": "2"
351
+ }]
352
+ }
353
+ ```
354
 
355
+ 4. **Alternative Methods**:
356
+ - Click file → Click "Load File" button
357
+ - Try "Reset" then load again
358
+ - Refresh page (F5) after loading
359
+ - Use the "Force Update" button if it appears
360
+
361
+ 5. **Component Limitations**:
362
+ - Some custom Gradio components may have bugs with dynamic updates
363
+ - The WorkflowBuilder might require specific node types or data formats
364
+
365
+ ### 🚨 Known Issues with gradio_workflowbuilder:
366
+
367
+ This appears to be a bug in the gradio_workflowbuilder component where it doesn't properly respond to value updates. Possible workarounds:
368
+
369
+ 1. **Manual Recreation**: Copy the JSON and manually recreate nodes
370
+ 2. **Component Update**: Check if there's a newer version: `pip install --upgrade gradio_workflowbuilder`
371
+ 3. **Alternative Components**: Consider using other workflow builders or diagram libraries
372
+ 4. **Report Bug**: Report this issue to the component maintainer
373
+
374
+ ### 💡 Alternative Solution:
375
+
376
+ If loading continues to fail, you might need to:
377
+ ```python
378
+ # Option 1: Recreate the component on each load
379
+ @gr.render(inputs=[loaded_data_store])
380
+ def render_workflow(data):
381
+ return WorkflowBuilder(value=data)
382
+
383
+ # Option 2: Use a different workflow library
384
+ # Consider react-flow, vis.js, or cytoscape.js wrapped in Gradio HTML
385
+ ```
386
+
387
+ ### 🎨 HTML Fallback Viewer:
388
+
389
+ If the WorkflowBuilder component is broken, use the HTML viewer below to at least see your workflow structure:
390
+ """
391
  )
392
 
393
+ # HTML Fallback Viewer
394
+ gr.Markdown("### 📊 HTML Workflow Viewer (Fallback)")
395
+ html_viewer = gr.HTML(label="Workflow HTML View")
396
+
397
+ def create_html_view(data):
398
+ """JSON을 HTML로 시각화"""
399
+ if not data:
400
+ return "<p>No workflow data</p>"
401
 
402
+ html = """
403
+ <div style='font-family: Arial, sans-serif; padding: 20px; background: #f5f5f5; border-radius: 8px;'>
404
+ <h3>Workflow Visualization</h3>
405
+ <div style='display: flex; gap: 20px; flex-wrap: wrap;'>
406
+ """
 
 
 
 
 
407
 
408
+ # 노드 표시
409
+ nodes = data.get('nodes', [])
410
+ for node in nodes:
411
+ node_id = node.get('id', 'unknown')
412
+ label = node.get('data', {}).get('label', 'Node')
413
+ x = node.get('position', {}).get('x', 0)
414
+ y = node.get('position', {}).get('y', 0)
415
+
416
+ html += f"""
417
+ <div style='background: white; border: 2px solid #3b82f6; border-radius: 8px;
418
+ padding: 15px; margin: 10px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);
419
+ min-width: 150px;'>
420
+ <strong>ID:</strong> {node_id}<br>
421
+ <strong>Label:</strong> {label}<br>
422
+ <strong>Position:</strong> ({x}, {y})
423
+ </div>
424
+ """
425
 
426
+ html += "</div>"
 
 
 
 
 
427
 
428
+ # 엣지 표시
429
+ edges = data.get('edges', [])
430
+ if edges:
431
+ html += "<h4>Connections:</h4><ul>"
432
+ for edge in edges:
433
+ source = edge.get('source', '?')
434
+ target = edge.get('target', '?')
435
+ html += f"<li>{source} {target}</li>"
436
+ html += "</ul>"
437
 
438
+ html += "</div>"
439
+ return html
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
440
 
441
+ # HTML Fallback Viewer
442
+ gr.Markdown("### 📊 HTML Workflow Viewer (Fallback)")
443
+ html_viewer = gr.HTML(label="Workflow HTML View")
444
 
445
+ def create_html_view(data):
446
+ """JSON을 HTML로 시각화"""
447
+ if not data:
448
+ return "<p>No workflow data</p>"
449
+
450
+ html = """
451
+ <div style='font-family: Arial, sans-serif; padding: 20px; background: #f5f5f5; border-radius: 8px;'>
452
+ <h3>Workflow Visualization</h3>
453
+ <div style='display: flex; gap: 20px; flex-wrap: wrap;'>
454
+ """
455
+
456
+ # 노드 표시
457
+ nodes = data.get('nodes', [])
458
+ for node in nodes:
459
+ node_id = node.get('id', 'unknown')
460
+ label = node.get('data', {}).get('label', 'Node')
461
+ x = node.get('position', {}).get('x', 0)
462
+ y = node.get('position', {}).get('y', 0)
463
+
464
+ html += f"""
465
+ <div style='background: white; border: 2px solid #3b82f6; border-radius: 8px;
466
+ padding: 15px; margin: 10px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);
467
+ min-width: 150px;'>
468
+ <strong>ID:</strong> {node_id}<br>
469
+ <strong>Label:</strong> {label}<br>
470
+ <strong>Position:</strong> ({x}, {y})
471
+ </div>
472
+ """
473
+
474
+ html += "</div>"
475
+
476
+ # 엣지 표시
477
+ edges = data.get('edges', [])
478
+ if edges:
479
+ html += "<h4>Connections:</h4><ul>"
480
+ for edge in edges:
481
+ source = edge.get('source', '?')
482
+ target = edge.get('target', '?')
483
+ html += f"<li>{source} → {target}</li>"
484
+ html += "</ul>"
485
+
486
+ html += "</div>"
487
+ return html
 
 
 
 
 
 
488
 
489
+ # HTML viewer 업데이트
490
+ workflow_builder.change(
491
+ fn=create_html_view,
492
+ inputs=workflow_builder,
493
+ outputs=html_viewer
 
 
 
 
 
 
494
  )
495
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
496
  # -------------------------------------------------------------------
497
  # 🚀 실행
498
  # -------------------------------------------------------------------
499
  if __name__ == "__main__":
500
+ demo.launch(
501
+ server_name="0.0.0.0",
502
+ show_error=True,
503
+ debug=True # 디버그 모드 활성화
504
+ )