Spaces:
Running
Running
""" | |
app.py ββββββββββββββββββββββββββββββββββββββββββββββββ | |
β Preview Β· Save Β· Load κΈ°λ₯ ν¬ν¨ | |
β Load μ λ Έλ/μ’ν/μ£μ§ μλ 볡μ | |
β Stateλ₯Ό μ¬μ©ν μμ μ μΈ JSON λ‘λ | |
""" | |
import os, json, typing, tempfile | |
import gradio as gr | |
from gradio_workflowbuilder import WorkflowBuilder | |
# ------------------------------------------------------------------- | |
# π οΈ ν¬νΌ | |
# ------------------------------------------------------------------- | |
def export_pretty(data: typing.Dict[str, typing.Any]) -> str: | |
return json.dumps(data, indent=2, ensure_ascii=False) if data else "No workflow to export" | |
def export_file(data: typing.Dict[str, typing.Any]) -> typing.Optional[str]: | |
if not data: | |
return None | |
fd, path = tempfile.mkstemp(suffix=".json") | |
with os.fdopen(fd, "w", encoding="utf-8") as f: | |
json.dump(data, f, ensure_ascii=False, indent=2) | |
return path | |
def load_workflow_from_file(file_obj): | |
"""JSON νμΌμ μ½μ΄μ λ°μ΄ν°λ§ λ°ν""" | |
if file_obj is None: | |
return None | |
try: | |
with open(file_obj.name, "r", encoding="utf-8") as f: | |
data = json.load(f) | |
return data | |
except Exception as e: | |
print(f"Error loading file: {e}") | |
return None | |
def update_workflow_display(workflow_data): | |
"""μν¬νλ‘μ° λ°μ΄ν°λ₯Ό μ½λλ·°μ νμ""" | |
if workflow_data: | |
return json.dumps(workflow_data, indent=2, ensure_ascii=False) | |
return "No workflow loaded" | |
def debug_workflow(data): | |
"""νμ¬ μν¬νλ‘μ° μν λλ²κΉ """ | |
if not data: | |
return "No workflow data" | |
nodes_count = len(data.get('nodes', [])) | |
edges_count = len(data.get('edges', [])) | |
return f"Nodes: {nodes_count}, Edges: {edges_count}" | |
# ------------------------------------------------------------------- | |
# π¨ CSS | |
# ------------------------------------------------------------------- | |
CSS = """ | |
.main-container{max-width:1600px;margin:0 auto;} | |
.workflow-section{margin-bottom:2rem;} | |
.button-row{display:flex;gap:1rem;justify-content:center;margin:1rem 0;} | |
.component-description{ | |
padding:24px;background:linear-gradient(135deg,#f8fafc 0%,#e2e8f0 100%); | |
border-left:4px solid #3b82f6;border-radius:12px; | |
box-shadow:0 2px 8px rgba(0,0,0,.05);margin:16px 0; | |
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; | |
} | |
.component-description p{margin:10px 0;line-height:1.6;color:#374151;} | |
.base-description{font-size:17px;font-weight:600;color:#1e293b;} | |
.base-description strong{color:#3b82f6;font-weight:700;} | |
.feature-description{font-size:16px;font-weight:500;color:#1e293b;} | |
.customization-note{font-size:15px;font-style:italic;color:#64748b;} | |
.sample-intro{font-size:15px;font-weight:600;color:#1e293b;margin-top:16px; | |
border-top:1px solid #e2e8f0;padding-top:16px;} | |
.load-button{background:#10b981 !important;} | |
""" | |
# ------------------------------------------------------------------- | |
# π₯οΈ Gradio μ± | |
# ------------------------------------------------------------------- | |
with gr.Blocks(title="π¨ Visual Workflow Builder", theme=gr.themes.Soft(), css=CSS) as demo: | |
# Stateλ‘ μν¬νλ‘μ° λ°μ΄ν° κ΄λ¦¬ | |
workflow_state = gr.State(value={"nodes": [], "edges": []}) | |
with gr.Column(elem_classes=["main-container"]): | |
gr.Markdown("# π¨ Visual Workflow Builder\n**Create sophisticated workflows with drag-and-drop simplicity.**") | |
gr.HTML( | |
""" | |
<div class="component-description"> | |
<p class="base-description">Base custom component powered by <strong>svelteflow</strong>.</p> | |
<p class="feature-description">Create custom workflows.</p> | |
<p class="customization-note">You can customise the nodes as well as the design of the workflow.</p> | |
<p class="sample-intro">Here is a sample:</p> | |
</div> | |
""" | |
) | |
# βββ Workflow Builder βββ | |
with gr.Column(elem_classes=["workflow-section"]): | |
workflow_builder = WorkflowBuilder( | |
label="π¨ Visual Workflow Designer", | |
info="Drag from output β input β’ Click nodes to edit properties", | |
value={"nodes": [], "edges": []} | |
) | |
# βββ Export / Import βββ | |
gr.Markdown("## πΎ Export Β· π Import") | |
with gr.Row(elem_classes=["button-row"]): | |
btn_preview = gr.Button("π Preview JSON") | |
btn_download = gr.DownloadButton("πΎ Save JSON file") | |
with gr.Row(elem_classes=["button-row"]): | |
file_upload = gr.File(label="π Select JSON file", file_types=[".json"]) | |
btn_load = gr.Button("π₯ Load into Workflow", variant="primary", elem_classes=["load-button"]) | |
code_view = gr.Code(language="json", label="Workflow Configuration", lines=14) | |
# λλ²κΉ μΉμ | |
with gr.Row(): | |
btn_debug = gr.Button("π Debug Current State", variant="secondary") | |
debug_output = gr.Textbox(label="Debug Info", lines=1) | |
# βββ μ΄λ²€νΈ μ°κ²° βββ | |
# Preview λ²νΌ - νμ¬ μν¬νλ‘μ°λ₯Ό JSONμΌλ‘ νμ | |
btn_preview.click( | |
fn=export_pretty, | |
inputs=workflow_builder, | |
outputs=code_view | |
) | |
# Download λ²νΌ | |
btn_download.click( | |
fn=export_file, | |
inputs=workflow_builder | |
) | |
# νμΌ μ λ‘λ -> Stateμ μ μ₯ | |
file_upload.change( | |
fn=load_workflow_from_file, | |
inputs=file_upload, | |
outputs=workflow_state | |
).then( | |
fn=update_workflow_display, | |
inputs=workflow_state, | |
outputs=code_view | |
) | |
# Load λ²νΌ ν΄λ¦ -> Stateμμ WorkflowBuilderλ‘ μ λ¬ | |
btn_load.click( | |
fn=lambda x: x, # State κ°μ κ·Έλλ‘ μ λ¬ | |
inputs=workflow_state, | |
outputs=workflow_builder | |
) | |
# Debug λ²νΌ | |
btn_debug.click( | |
fn=debug_workflow, | |
inputs=workflow_builder, | |
outputs=debug_output | |
) | |
# WorkflowBuilder κ° λ³κ²½ μ State μ λ°μ΄νΈ | |
workflow_builder.change( | |
fn=lambda x: x, | |
inputs=workflow_builder, | |
outputs=workflow_state | |
) | |
# βββ μν μν¬νλ‘μ° βββ | |
gr.Markdown("### π― Sample Workflows") | |
def create_sample_workflow(): | |
return { | |
"nodes": [ | |
{ | |
"id": "node_1", | |
"type": "custom", | |
"position": {"x": 100, "y": 100}, | |
"data": {"label": "Input Node", "value": "Start"} | |
}, | |
{ | |
"id": "node_2", | |
"type": "custom", | |
"position": {"x": 300, "y": 100}, | |
"data": {"label": "Process Node", "value": "Transform"} | |
}, | |
{ | |
"id": "node_3", | |
"type": "custom", | |
"position": {"x": 500, "y": 100}, | |
"data": {"label": "Output Node", "value": "Result"} | |
} | |
], | |
"edges": [ | |
{ | |
"id": "edge_1", | |
"source": "node_1", | |
"target": "node_2", | |
"type": "default" | |
}, | |
{ | |
"id": "edge_2", | |
"source": "node_2", | |
"target": "node_3", | |
"type": "default" | |
} | |
] | |
} | |
with gr.Row(): | |
btn_sample1 = gr.Button("π Load Linear Workflow", variant="primary") | |
btn_sample2 = gr.Button("π Load Branching Workflow", variant="primary") | |
def create_branching_workflow(): | |
return { | |
"nodes": [ | |
{ | |
"id": "start", | |
"type": "custom", | |
"position": {"x": 100, "y": 200}, | |
"data": {"label": "Start", "value": "Input Data"} | |
}, | |
{ | |
"id": "branch1", | |
"type": "custom", | |
"position": {"x": 300, "y": 100}, | |
"data": {"label": "Branch A", "value": "Process A"} | |
}, | |
{ | |
"id": "branch2", | |
"type": "custom", | |
"position": {"x": 300, "y": 300}, | |
"data": {"label": "Branch B", "value": "Process B"} | |
}, | |
{ | |
"id": "merge", | |
"type": "custom", | |
"position": {"x": 500, "y": 200}, | |
"data": {"label": "Merge", "value": "Combine Results"} | |
} | |
], | |
"edges": [ | |
{"id": "e1", "source": "start", "target": "branch1", "type": "default"}, | |
{"id": "e2", "source": "start", "target": "branch2", "type": "default"}, | |
{"id": "e3", "source": "branch1", "target": "merge", "type": "default"}, | |
{"id": "e4", "source": "branch2", "target": "merge", "type": "default"} | |
] | |
} | |
# μν λ‘λ μ΄λ²€νΈ | |
btn_sample1.click( | |
fn=create_sample_workflow, | |
outputs=workflow_state | |
).then( | |
fn=lambda x: x, | |
inputs=workflow_state, | |
outputs=workflow_builder | |
).then( | |
fn=update_workflow_display, | |
inputs=workflow_state, | |
outputs=code_view | |
) | |
btn_sample2.click( | |
fn=create_branching_workflow, | |
outputs=workflow_state | |
).then( | |
fn=lambda x: x, | |
inputs=workflow_state, | |
outputs=workflow_builder | |
).then( | |
fn=update_workflow_display, | |
inputs=workflow_state, | |
outputs=code_view | |
) | |
# βββ μ¬μ© λ°©λ² βββ | |
with gr.Accordion("π How to Use", open=False): | |
gr.Markdown( | |
""" | |
### π Quick Start | |
1. **Create Workflow** β Add nodes and connect them | |
2. **Save** β Click "Save JSON file" to download | |
3. **Load** β Select file β Click "Load into Workflow" | |
### π Loading Workflows | |
1. Click "Select JSON file" and choose your workflow file | |
2. Click the green "Load into Workflow" button | |
3. Your workflow will appear in the visual designer | |
### π― Try Sample Workflows | |
- Click sample buttons to see example workflows | |
- Modify them to understand the structure | |
### π JSON Structure | |
```json | |
{ | |
"nodes": [ | |
{ | |
"id": "unique_id", | |
"type": "custom", | |
"position": {"x": 100, "y": 100}, | |
"data": {"label": "Node Name"} | |
} | |
], | |
"edges": [ | |
{ | |
"id": "edge_id", | |
"source": "node_id_1", | |
"target": "node_id_2" | |
} | |
] | |
} | |
``` | |
""" | |
) | |
# ------------------------------------------------------------------- | |
# π μ€ν | |
# ------------------------------------------------------------------- | |
if __name__ == "__main__": | |
demo.launch(server_name="0.0.0.0", show_error=True) |