MOUSE-Workflow / app.py
fantaxy's picture
Update app.py
43f50cc verified
raw
history blame
11.8 kB
"""
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)