Spaces:
Running
Running
| """ | |
| LifeFlow AI - Progress Stepper Component | |
| ✅ 顯示用戶當前所在步驟 | |
| ✅ 視覺化流程進度 | |
| """ | |
| import gradio as gr | |
| def create_progress_stepper(current_step: int = 1): | |
| """ | |
| 創建流程進度條 | |
| Args: | |
| current_step: 當前步驟 (1-4) | |
| 1: Analyze | |
| 2: Confirm | |
| 3: Optimize | |
| 4: Review | |
| Returns: | |
| HTML 組件 | |
| """ | |
| steps = [ | |
| {'num': 1, 'label': 'Analyze', 'icon': '📋', 'desc': 'Extract tasks'}, | |
| {'num': 2, 'label': 'Confirm', 'icon': '✅', 'desc': 'Review & modify'}, | |
| {'num': 3, 'label': 'Optimize', 'icon': '⚡', 'desc': 'Route planning'}, | |
| {'num': 4, 'label': 'Review', 'icon': '🎉', 'desc': 'Check results'} | |
| ] | |
| html = '<div class="progress-stepper">' | |
| for i, step in enumerate(steps): | |
| is_active = step['num'] == current_step | |
| is_complete = step['num'] < current_step | |
| state_class = '' | |
| if is_complete: | |
| state_class = 'complete' | |
| elif is_active: | |
| state_class = 'active' | |
| html += f""" | |
| <div class="step {state_class}"> | |
| <div class="step-number">{step['icon']}</div> | |
| <div class="step-label">{step['label']}</div> | |
| <div class="step-desc">{step['desc']}</div> | |
| </div> | |
| """ | |
| # 添加連接線 | |
| if i < len(steps) - 1: | |
| connector_class = 'complete' if is_complete else '' | |
| html += f'<div class="step-connector {connector_class}"></div>' | |
| html += '</div>' | |
| # 內嵌樣式 | |
| css = """ | |
| <style> | |
| .progress-stepper { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| padding: 24px; | |
| background: white; | |
| border-radius: 16px; | |
| margin-bottom: 24px; | |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); | |
| } | |
| .step { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| position: relative; | |
| flex: 1; | |
| min-width: 80px; | |
| } | |
| .step-number { | |
| width: 56px; | |
| height: 56px; | |
| border-radius: 50%; | |
| background: #e2e8f0; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 24px; | |
| transition: all 0.3s ease; | |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | |
| margin-bottom: 8px; | |
| } | |
| .step.active .step-number { | |
| background: linear-gradient(135deg, #6366f1, #8b5cf6); | |
| color: white; | |
| box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.2), 0 4px 8px rgba(0, 0, 0, 0.15); | |
| transform: scale(1.1); | |
| animation: pulse-ring 2s infinite; | |
| } | |
| .step.complete .step-number { | |
| background: #10b981; | |
| color: white; | |
| box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3); | |
| } | |
| .step-label { | |
| margin-top: 8px; | |
| font-size: 14px; | |
| font-weight: 600; | |
| color: #64748b; | |
| transition: color 0.3s; | |
| } | |
| .step.active .step-label { | |
| color: #6366f1; | |
| font-weight: 700; | |
| } | |
| .step.complete .step-label { | |
| color: #10b981; | |
| } | |
| .step-desc { | |
| font-size: 11px; | |
| color: #94a3b8; | |
| margin-top: 4px; | |
| text-align: center; | |
| } | |
| .step-connector { | |
| flex: 1; | |
| height: 3px; | |
| background: #e2e8f0; | |
| margin: 0 8px; | |
| transition: background 0.3s; | |
| position: relative; | |
| top: -20px; | |
| } | |
| .step-connector.complete { | |
| background: #10b981; | |
| } | |
| @keyframes pulse-ring { | |
| 0% { | |
| box-shadow: 0 0 0 0 rgba(99, 102, 241, 0.7), 0 4px 8px rgba(0, 0, 0, 0.15); | |
| } | |
| 50% { | |
| box-shadow: 0 0 0 8px rgba(99, 102, 241, 0), 0 4px 8px rgba(0, 0, 0, 0.15); | |
| } | |
| 100% { | |
| box-shadow: 0 0 0 0 rgba(99, 102, 241, 0), 0 4px 8px rgba(0, 0, 0, 0.15); | |
| } | |
| } | |
| /* 響應式 */ | |
| @media (max-width: 768px) { | |
| .progress-stepper { | |
| padding: 16px 8px; | |
| } | |
| .step { | |
| min-width: 60px; | |
| } | |
| .step-number { | |
| width: 44px; | |
| height: 44px; | |
| font-size: 18px; | |
| } | |
| .step-label { | |
| font-size: 12px; | |
| } | |
| .step-desc { | |
| display: none; | |
| } | |
| .step-connector { | |
| margin: 0 4px; | |
| } | |
| } | |
| @media (max-width: 480px) { | |
| .progress-stepper { | |
| flex-wrap: wrap; | |
| gap: 12px; | |
| } | |
| .step-connector { | |
| display: none; | |
| } | |
| .step { | |
| flex-direction: row; | |
| gap: 8px; | |
| width: calc(50% - 6px); | |
| } | |
| .step-number { | |
| width: 36px; | |
| height: 36px; | |
| font-size: 16px; | |
| } | |
| } | |
| </style> | |
| """ | |
| return gr.HTML(css + html) | |
| def update_stepper(step_number: int): | |
| """更新進度條到指定步驟""" | |
| return create_progress_stepper(step_number) |