piclets / src /lib /components /PicletGenerator /WorkflowProgress.svelte
Fraser's picture
try help json
d8ca9dc
<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>