import React, { useState } from 'react'; import { Plus, Edit, Trash2, Play, Save, X, GripVertical, Wand2, Sparkles, Upload, FileText, Brain, Image as ImageIcon, ArrowUp, ArrowDown } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { toast } from 'sonner'; import { T } from '@/i18n'; export type WorkflowActionType = | 'optimize_prompt' | 'generate_image' | 'generate_metadata' | 'publish_image' | 'quick_publish' | 'download_image' | 'enhance_image' | 'apply_style'; export interface WorkflowAction { id: string; type: WorkflowActionType; label: string; icon?: React.ReactNode; // Optional since it won't be stored in DB description: string; } export interface Workflow { id: string; name: string; actions: WorkflowAction[]; createdAt: string; } interface WorkflowManagerProps { workflows: Workflow[]; onSaveWorkflow: (workflow: Omit) => Promise; onUpdateWorkflow: (id: string, workflow: Omit) => Promise; onDeleteWorkflow: (id: string) => Promise; onExecuteWorkflow: (workflow: Workflow) => Promise; loading?: boolean; } // Icon mapping function to avoid serialization issues const getActionIcon = (type: WorkflowActionType) => { const iconMap: Record = { 'optimize_prompt': , 'generate_image': , 'generate_metadata': , 'publish_image': , 'quick_publish': , 'download_image': , 'enhance_image': , 'apply_style': , }; return iconMap[type] || ; }; // Available action palette const AVAILABLE_ACTIONS: WorkflowAction[] = [ { id: 'optimize_prompt', type: 'optimize_prompt', label: 'Optimize Prompt', icon: , description: 'Enhance prompt with AI suggestions', }, { id: 'generate_image', type: 'generate_image', label: 'Generate Image', icon: , description: 'Create image from prompt', }, { id: 'generate_metadata', type: 'generate_metadata', label: 'Generate Metadata', icon: , description: 'Create title and description', }, { id: 'publish_image', type: 'publish_image', label: 'Publish Image', icon: , description: 'Save to gallery with metadata', }, { id: 'quick_publish', type: 'quick_publish', label: 'Quick Publish', icon: , description: 'Fast publish with prompt as description', }, { id: 'download_image', type: 'download_image', label: 'Download Image', icon: , description: 'Download image to device', }, { id: 'enhance_image', type: 'enhance_image', label: 'Enhance Image', icon: , description: 'Apply AI enhancement', }, { id: 'apply_style', type: 'apply_style', label: 'Apply Style', icon: , description: 'Apply artistic style transformation', }, ]; const WorkflowManager: React.FC = ({ workflows, onSaveWorkflow, onUpdateWorkflow, onDeleteWorkflow, onExecuteWorkflow, loading = false, }) => { const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); const [isEditDialogOpen, setIsEditDialogOpen] = useState(false); const [deleteWorkflowId, setDeleteWorkflowId] = useState(null); const [editingWorkflow, setEditingWorkflow] = useState(null); const [workflowName, setWorkflowName] = useState(''); const [selectedActions, setSelectedActions] = useState([]); const handleCreateWorkflow = async () => { if (!workflowName.trim()) { toast.error('Please enter a workflow name'); return; } if (selectedActions.length === 0) { toast.error('Please add at least one action to the workflow'); return; } try { // Strip icon field to avoid serialization issues const actionsForDb = selectedActions.map(({ icon, ...action }) => action); await onSaveWorkflow({ name: workflowName.trim(), actions: actionsForDb as WorkflowAction[], }); toast.success('Workflow created successfully'); setIsCreateDialogOpen(false); setWorkflowName(''); setSelectedActions([]); } catch (error: any) { toast.error(error.message || 'Failed to create workflow'); } }; const handleUpdateWorkflow = async () => { if (!editingWorkflow) return; if (!workflowName.trim()) { toast.error('Please enter a workflow name'); return; } if (selectedActions.length === 0) { toast.error('Please add at least one action to the workflow'); return; } try { // Strip icon field to avoid serialization issues const actionsForDb = selectedActions.map(({ icon, ...action }) => action); await onUpdateWorkflow(editingWorkflow.id, { name: workflowName.trim(), actions: actionsForDb as WorkflowAction[], }); toast.success('Workflow updated successfully'); setIsEditDialogOpen(false); setEditingWorkflow(null); setWorkflowName(''); setSelectedActions([]); } catch (error: any) { toast.error(error.message || 'Failed to update workflow'); } }; const handleDeleteWorkflow = async () => { if (!deleteWorkflowId) return; try { await onDeleteWorkflow(deleteWorkflowId); toast.success('Workflow deleted successfully'); setDeleteWorkflowId(null); } catch (error: any) { toast.error(error.message || 'Failed to delete workflow'); } }; const openEditDialog = (workflow: Workflow) => { setEditingWorkflow(workflow); setWorkflowName(workflow.name); setSelectedActions([...workflow.actions]); setIsEditDialogOpen(true); }; const addAction = (action: WorkflowAction) => { // Create a unique instance for the workflow (without icon to avoid serialization issues) const newAction = { id: `${action.type}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, type: action.type, label: action.label, description: action.description, icon: action.icon, // Keep icon for UI but it won't be saved to DB }; setSelectedActions([...selectedActions, newAction]); }; const removeAction = (actionId: string) => { setSelectedActions(selectedActions.filter(a => a.id !== actionId)); }; const moveAction = (index: number, direction: 'up' | 'down') => { if (direction === 'up' && index === 0) return; if (direction === 'down' && index === selectedActions.length - 1) return; const newActions = [...selectedActions]; const targetIndex = direction === 'up' ? index - 1 : index + 1; [newActions[index], newActions[targetIndex]] = [newActions[targetIndex], newActions[index]]; setSelectedActions(newActions); }; const handleExecuteWorkflow = async (workflow: Workflow) => { try { await onExecuteWorkflow(workflow); } catch (error: any) { toast.error(error.message || 'Failed to execute workflow'); } }; return (

Workflows

{workflows.length}
{workflows.length === 0 ? ( No workflows saved yet. Create a workflow to automate your image generation process! ) : ( workflows.map((workflow) => (
{workflow.name}
{workflow.actions.map((action, idx) => ( {idx + 1}. {getActionIcon(action.type)} {action.label} ))}
)) )}
{/* Create/Edit Workflow Dialog */} { if (!open) { setIsCreateDialogOpen(false); setIsEditDialogOpen(false); setEditingWorkflow(null); setWorkflowName(''); setSelectedActions([]); } }}> {isEditDialogOpen ? Edit Workflow : Create New Workflow} Build a custom workflow by selecting and ordering actions from the palette below.
{/* Workflow Name */}
setWorkflowName(e.target.value)} maxLength={50} />
{/* Action Palette */}
{AVAILABLE_ACTIONS.map((action) => ( ))}
{/* Selected Actions (Workflow Steps) */}
{selectedActions.length === 0 ? (
No actions added yet. Click on actions above to build your workflow.
) : (
{selectedActions.map((action, index) => (
{index + 1}
{getActionIcon(action.type)}
{action.label}
{action.description}
))}
)}
{/* Delete Confirmation Dialog */} !open && setDeleteWorkflowId(null)}> Delete Workflow? Are you sure you want to delete this workflow? This action cannot be undone. setDeleteWorkflowId(null)}> Cancel Delete
); }; export default WorkflowManager;