Spaces:
Running
Running
""" | |
app.py ββββββββββββββββββββββββββββββββββββββββββββββββ | |
β Preview Β· Save Β· Load κΈ°λ₯ ν¬ν¨ | |
β Load μ λ Έλ/μ’ν/μ£μ§ μλ 볡μ | |
β λμ λ λλ§μΌλ‘ κΈ°μ‘΄ WorkflowBuilder μ λ°μ΄νΈ | |
""" | |
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_json_file(file_obj): | |
"""JSON νμΌ μ½κΈ°""" | |
if file_obj is None: | |
return None, "No file selected" | |
try: | |
with open(file_obj.name, "r", encoding="utf-8") as f: | |
data = json.load(f) | |
# λ°μ΄ν° κ²μ¦ | |
if not isinstance(data, dict): | |
return None, "Invalid format: not a dictionary" | |
# νμ νλ νμΈ | |
if 'nodes' not in data: | |
data['nodes'] = [] | |
if 'edges' not in data: | |
data['edges'] = [] | |
nodes_count = len(data.get('nodes', [])) | |
edges_count = len(data.get('edges', [])) | |
return data, f"β Loaded: {nodes_count} nodes, {edges_count} edges" | |
except Exception as e: | |
return None, f"β Error: {str(e)}" | |
def create_sample_workflow(): | |
"""μν μν¬νλ‘μ° μμ±""" | |
return { | |
"nodes": [ | |
{ | |
"id": "node_1", | |
"type": "default", | |
"position": {"x": 100, "y": 100}, | |
"data": {"label": "Start Node"} | |
}, | |
{ | |
"id": "node_2", | |
"type": "default", | |
"position": {"x": 300, "y": 100}, | |
"data": {"label": "Process"} | |
}, | |
{ | |
"id": "node_3", | |
"type": "default", | |
"position": {"x": 500, "y": 100}, | |
"data": {"label": "End Node"} | |
} | |
], | |
"edges": [ | |
{ | |
"id": "edge_1", | |
"source": "node_1", | |
"target": "node_2" | |
}, | |
{ | |
"id": "edge_2", | |
"source": "node_2", | |
"target": "node_3" | |
} | |
] | |
} | |
# ------------------------------------------------------------------- | |
# π¨ CSS | |
# ------------------------------------------------------------------- | |
CSS = """ | |
.main-container{max-width:1600px;margin:0 auto;} | |
.workflow-section{margin-bottom:2rem;min-height:500px;} | |
.button-row{display:flex;gap:1rem;justify-content:center;margin:1rem 0;} | |
.status-box{ | |
padding:10px;border-radius:5px;margin-top:10px; | |
background:#f0f9ff;border:1px solid #3b82f6;color:#1e40af; | |
} | |
.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; | |
} | |
.workflow-container{position:relative;} | |
""" | |
# ------------------------------------------------------------------- | |
# π₯οΈ Gradio μ± | |
# ------------------------------------------------------------------- | |
with gr.Blocks(title="π¨ Visual Workflow Builder", theme=gr.themes.Soft(), css=CSS) as demo: | |
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 style="font-size:16px;margin:0;">Powered by <strong>svelteflow</strong> β’ Create custom workflows</p> | |
</div> | |
""" | |
) | |
# State for storing loaded data | |
loaded_data = gr.State(None) | |
trigger_update = gr.State(False) | |
# βββ Dynamic Workflow Container βββ | |
with gr.Column(elem_classes=["workflow-container"]): | |
def render_workflow(data, trigger): | |
"""λμ μΌλ‘ WorkflowBuilder λ λλ§""" | |
workflow_value = data if data else {"nodes": [], "edges": []} | |
return WorkflowBuilder( | |
label="π¨ Visual Workflow Designer", | |
info="Drag from output β input β’ Click nodes to edit properties", | |
value=workflow_value, | |
elem_id="main_workflow" | |
) | |
# βββ Control Panel βββ | |
gr.Markdown("## πΎ Export Β· π Import") | |
with gr.Row(elem_classes=["button-row"]): | |
with gr.Column(scale=2): | |
file_upload = gr.File( | |
label="π Select JSON file", | |
file_types=[".json"], | |
type="filepath" | |
) | |
with gr.Column(scale=1): | |
btn_load = gr.Button("π₯ Load Workflow", variant="primary", size="lg") | |
btn_sample = gr.Button("π― Load Sample", variant="secondary") | |
with gr.Row(elem_classes=["button-row"]): | |
btn_preview = gr.Button("ποΈ Preview JSON") | |
btn_download = gr.DownloadButton("πΎ Download JSON") | |
btn_reset = gr.Button("π Reset", variant="stop") | |
# Status | |
status_text = gr.Textbox( | |
label="Status", | |
value="Ready", | |
elem_classes=["status-box"], | |
interactive=False | |
) | |
# Code View | |
code_view = gr.Code( | |
language="json", | |
label="JSON Preview", | |
lines=10, | |
visible=False | |
) | |
btn_toggle_code = gr.Button("π Toggle Code View", size="sm") | |
# βββ Event Handlers βββ | |
# File upload β Store data | |
file_upload.change( | |
fn=load_json_file, | |
inputs=file_upload, | |
outputs=[loaded_data, status_text] | |
) | |
# Load button β Trigger render update | |
btn_load.click( | |
fn=lambda current_trigger: not current_trigger, | |
inputs=trigger_update, | |
outputs=trigger_update | |
) | |
# Sample workflow | |
btn_sample.click( | |
fn=lambda: (create_sample_workflow(), "β Sample loaded"), | |
outputs=[loaded_data, status_text] | |
).then( | |
fn=lambda current_trigger: not current_trigger, | |
inputs=trigger_update, | |
outputs=trigger_update | |
) | |
# Reset | |
btn_reset.click( | |
fn=lambda: ({"nodes": [], "edges": []}, "β Reset complete"), | |
outputs=[loaded_data, status_text] | |
).then( | |
fn=lambda current_trigger: not current_trigger, | |
inputs=trigger_update, | |
outputs=trigger_update | |
) | |
# Toggle code view | |
btn_toggle_code.click( | |
fn=lambda visible: gr.update(visible=not visible), | |
inputs=code_view, | |
outputs=code_view | |
) | |
# Preview - νμ¬ μν¬νλ‘μ°λ₯Ό κ°μ Έμμ νμ | |
btn_preview.click( | |
fn=lambda data: (gr.update(visible=True), export_pretty(data)), | |
inputs=loaded_data, | |
outputs=[code_view, code_view] | |
) | |
# Download - loaded_dataλ₯Ό νμΌλ‘ λ΄λ³΄λ΄κΈ° | |
btn_download.click( | |
fn=export_file, | |
inputs=loaded_data | |
) | |
# βββ Instructions βββ | |
with gr.Accordion("π How to Use", open=False): | |
gr.Markdown( | |
""" | |
### π Quick Start | |
1. **Create** β Add nodes and connect them in the workflow designer | |
2. **Save** β Click "Download JSON" to save your workflow | |
3. **Load** β Select a JSON file and click "Load Workflow" | |
### π Workflow Format | |
```json | |
{ | |
"nodes": [ | |
{ | |
"id": "unique_id", | |
"type": "default", | |
"position": {"x": 100, "y": 100}, | |
"data": {"label": "Node Name"} | |
} | |
], | |
"edges": [ | |
{ | |
"id": "edge_id", | |
"source": "source_node_id", | |
"target": "target_node_id" | |
} | |
] | |
} | |
``` | |
### π‘ Tips | |
- The workflow will automatically update when you load a file | |
- Use "Load Sample" to see an example workflow | |
- Toggle code view to see the JSON structure | |
""" | |
) | |
# ------------------------------------------------------------------- | |
# π μ€ν | |
# ------------------------------------------------------------------- | |
if __name__ == "__main__": | |
demo.launch(server_name="0.0.0.0", show_error=True) |