| <script lang="ts"> | |
| import type { PicletWorkflowStep } from '$lib/types'; | |
| interface Props { | |
| currentStep: PicletWorkflowStep; | |
| error?: string | null; | |
| } | |
| let { currentStep, error = null }: Props = $props(); | |
| interface StepInfo { | |
| id: PicletWorkflowStep; | |
| label: string; | |
| description: string; | |
| } | |
| const steps: StepInfo[] = [ | |
| { | |
| id: 'upload', | |
| label: 'Upload Photo', | |
| description: 'Select your image' | |
| }, | |
| { | |
| id: 'captioning', | |
| label: 'Image Analysis', | |
| description: 'Analyzing your photo' | |
| }, | |
| { | |
| id: 'conceptualizing', | |
| label: 'Piclet Design', | |
| description: 'Creating concept & lore' | |
| }, | |
| { | |
| id: 'statsGenerating', | |
| label: 'Battle Stats', | |
| description: 'Generating abilities' | |
| }, | |
| { | |
| id: 'promptCrafting', | |
| label: 'Art Planning', | |
| description: 'Preparing visual prompt' | |
| }, | |
| { | |
| id: 'generating', | |
| label: 'Image Generation', | |
| description: 'Creating piclet art' | |
| }, | |
| { | |
| id: 'complete', | |
| label: 'Complete', | |
| description: 'Your piclet is ready!' | |
| } | |
| ]; | |
| function getStepIndex(step: PicletWorkflowStep): number { | |
| return steps.findIndex(s => s.id === step); | |
| } | |
| function getStepStatus(step: StepInfo): 'completed' | 'current' | 'pending' | 'error' { | |
| const currentIndex = getStepIndex(currentStep); | |
| const stepIndex = getStepIndex(step.id); | |
| if (error && step.id === currentStep) return 'error'; | |
| if (stepIndex < currentIndex) return 'completed'; | |
| if (stepIndex === currentIndex) return 'current'; | |
| return 'pending'; | |
| } | |
| </script> | |
| <div class="workflow-progress"> | |
| <div class="steps-container"> | |
| {#each steps as step, i} | |
| {@const status = getStepStatus(step)} | |
| <div class="step" class:completed={status === 'completed'} class:current={status === 'current'} class:error={status === 'error'}> | |
| <div class="step-indicator"> | |
| {#if status === 'completed'} | |
| <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor"> | |
| <path d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 111.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"/> | |
| </svg> | |
| {:else if status === 'error'} | |
| <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor"> | |
| <path d="M10 2a8 8 0 100 16 8 8 0 000-16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"/> | |
| </svg> | |
| {:else} | |
| <span class="step-number">{i + 1}</span> | |
| {/if} | |
| </div> | |
| <div class="step-content"> | |
| <div class="step-label">{step.label}</div> | |
| <div class="step-description">{step.description}</div> | |
| </div> | |
| {#if i < steps.length - 1} | |
| <div class="step-connector" class:active={status === 'completed'}></div> | |
| {/if} | |
| </div> | |
| {/each} | |
| </div> | |
| {#if error} | |
| <div class="error-message"> | |
| <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor"> | |
| <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"/> | |
| </svg> | |
| <span>{error}</span> | |
| </div> | |
| {/if} | |
| </div> | |
| <style> | |
| .workflow-progress { | |
| max-width: 800px; | |
| margin: 2rem auto; | |
| } | |
| .steps-container { | |
| display: flex; | |
| justify-content: space-between; | |
| position: relative; | |
| padding: 0 20px; | |
| } | |
| .step { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| position: relative; | |
| flex: 1; | |
| } | |
| .step-indicator { | |
| width: 40px; | |
| height: 40px; | |
| border-radius: 50%; | |
| background: #e9ecef; | |
| color: #6c757d; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-weight: 600; | |
| margin-bottom: 0.5rem; | |
| transition: all 0.3s ease; | |
| z-index: 2; | |
| } | |
| .step.completed .step-indicator { | |
| background: #28a745; | |
| color: white; | |
| } | |
| .step.current .step-indicator { | |
| background: #007bff; | |
| color: white; | |
| box-shadow: 0 0 0 4px rgba(0, 123, 255, 0.2); | |
| animation: pulse 2s infinite; | |
| } | |
| .step.error .step-indicator { | |
| background: #dc3545; | |
| color: white; | |
| } | |
| .step-number { | |
| font-size: 0.9rem; | |
| } | |
| .step-content { | |
| text-align: center; | |
| } | |
| .step-label { | |
| font-weight: 600; | |
| color: #333; | |
| margin-bottom: 0.25rem; | |
| font-size: 0.9rem; | |
| } | |
| .step-description { | |
| font-size: 0.8rem; | |
| color: #666; | |
| } | |
| .step-connector { | |
| position: absolute; | |
| top: 20px; | |
| left: 50%; | |
| width: 100%; | |
| height: 2px; | |
| background: #e9ecef; | |
| z-index: 1; | |
| } | |
| .step-connector.active { | |
| background: #28a745; | |
| } | |
| .error-message { | |
| margin-top: 2rem; | |
| padding: 1rem; | |
| background: #f8d7da; | |
| color: #721c24; | |
| border-radius: 6px; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| @keyframes pulse { | |
| 0% { | |
| box-shadow: 0 0 0 0 rgba(0, 123, 255, 0.4); | |
| } | |
| 70% { | |
| box-shadow: 0 0 0 10px rgba(0, 123, 255, 0); | |
| } | |
| 100% { | |
| box-shadow: 0 0 0 0 rgba(0, 123, 255, 0); | |
| } | |
| } | |
| @media (max-width: 768px) { | |
| .steps-container { | |
| flex-direction: column; | |
| padding: 0; | |
| } | |
| .step { | |
| flex-direction: row; | |
| margin-bottom: 1rem; | |
| } | |
| .step-indicator { | |
| margin-right: 1rem; | |
| margin-bottom: 0; | |
| } | |
| .step-content { | |
| text-align: left; | |
| flex: 1; | |
| } | |
| .step-connector { | |
| display: none; | |
| } | |
| } | |
| </style> |