MOUSE-Workflow / app-backup2.py
fantaxy's picture
Rename app.py to app-backup2.py
e3cf867 verified
raw
history blame
9.24 kB
"""
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"]):
@gr.render(inputs=[loaded_data, trigger_update])
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)