File size: 15,917 Bytes
07381d2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Workflows — NSFWStudio</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="https://unpkg.com/lucide@latest"></script>
    <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,300..800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
    <script>
        tailwind.config = { theme: { extend: { fontFamily: { sans: ['Inter','system-ui','sans-serif'], mono: ['JetBrains Mono','monospace'] }, colors: { bg: 'oklch(0.07 0.006 285)', surface: 'oklch(0.12 0.02 285)', 'surface-2': 'oklch(0.16 0.025 285)', 'surface-3': 'oklch(0.20 0.028 285)', fg: 'oklch(0.97 0.005 285)', muted: 'oklch(0.55 0.02 285)', primary: 'oklch(0.70 0.28 350)', 'primary-foreground': 'oklch(0.98 0.005 285)', secondary: 'oklch(0.75 0.15 215)', accent: 'oklch(0.80 0.18 85)', border: 'oklch(0.22 0.02 285)', ring: 'oklch(0.70 0.28 350)', destructive: 'oklch(0.55 0.22 25)', success: 'oklch(0.65 0.2 145)' } } } }
    </script>
    <style>
        body { background-color: oklch(0.07 0.006 285); color: oklch(0.97 0.005 285); font-family: 'Inter', system-ui, sans-serif; -webkit-font-smoothing: antialiased; }
        .glass { background: oklch(0.14 0.015 285 / 0.6); backdrop-filter: blur(24px) saturate(1.2); -webkit-backdrop-filter: blur(24px) saturate(1.2); border: 1px solid oklch(0.25 0.02 285 / 0.5); }
        ::selection { background: oklch(0.70 0.28 350 / 0.3); }
        body::after { content:''; position:fixed; inset:0; pointer-events:none; z-index:9999; opacity:0.025; background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); background-repeat:repeat; background-size:256px 256px; }
        .no-scrollbar::-webkit-scrollbar { display: none; } .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
    </style>
</head>
<body class="antialiased">
    <div id="root"></div>
    <script type="text/babel">
        const { useState } = React;
        const Icon = ({ name, size = 20, className = "", strokeWidth = 2 }) => {
            const ref = React.useRef(null);
            React.useEffect(() => { if (ref.current) { const existing = ref.current.querySelector('svg'); if (existing) existing.remove(); const svg = lucide.createElement(lucide.icons[name]); if (svg) { svg.setAttribute('width',size); svg.setAttribute('height',size); svg.setAttribute('stroke-width',strokeWidth); ref.current.appendChild(svg); } } }, [name, size, className, strokeWidth]);
            return <span ref={ref} className={`inline-flex items-center justify-center ${className}`} style={{ width: size, height: size }} />;
        };

        const navItems = [
            { name: 'Dashboard', icon: 'layout-dashboard', href: 'dashboard.html' },
            { name: 'Generator', icon: 'sparkles', href: 'generator.html' },
            { name: 'Playground', icon: 'code', href: 'playground.html' },
            { name: 'Models', icon: 'box', href: 'models.html' },
            { name: 'Workflows', icon: 'workflow', href: 'workflows.html', active: true },
            { name: 'Marketplace', icon: 'shopping-bag', href: 'marketplace.html' },
            { name: 'API Keys', icon: 'key', href: 'api-keys.html' },
            { divider: true },
            { name: 'Profile', icon: 'user', href: 'profile.html' },
            { name: 'Settings', icon: 'settings', href: 'settings.html' },
        ];

        const Sidebar = ({ isOpen, setIsOpen }) => (
            <>
                <div className={`fixed inset-0 z-40 lg:hidden transition-all ${isOpen ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'}`}>
                    <div className="absolute inset-0 bg-black/60 backdrop-blur-sm" onClick={() => setIsOpen(false)} />
                </div>
                <aside className={`fixed top-0 left-0 bottom-0 z-50 w-64 bg-surface border-r border-border flex flex-col transition-transform duration-300 lg:translate-x-0 ${isOpen ? 'translate-x-0' : '-translate-x-full'}`}>
                    <div className="p-4 flex items-center gap-2.5 border-b border-border"><div className="w-8 h-8 rounded-lg bg-primary flex items-center justify-center"><Icon name="sparkles" size={16} className="text-primary-foreground" /></div><span className="text-lg font-bold tracking-tight">NSFW<span className="text-muted">Studio</span></span></div>
                    <nav className="flex-1 p-3 space-y-1 overflow-y-auto no-scrollbar">{navItems.map((item, i) => item.divider ? <div key={i} className="my-3 border-t border-border" /> : <a key={i} href={item.href} className={`flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-colors ${item.active ? 'bg-primary/10 text-primary border border-primary/20' : 'text-muted hover:text-fg hover:bg-surface-2 border border-transparent'}`}><Icon name={item.icon} size={18} />{item.name}</a>)}</nav>
                    <div className="p-4 border-t border-border"><a href="documentation.html" className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm text-muted hover:text-fg hover:bg-surface-2 transition-colors"><Icon name="book-open" size={18} />Documentation</a><a href="api-reference.html" className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm text-muted hover:text-fg hover:bg-surface-2 transition-colors"><Icon name="file-code" size={18} />API Reference</a></div>
                </aside>
            </>
        );

        const App = () => {
            const [sidebarOpen, setSidebarOpen] = useState(false);
            const [filter, setFilter] = useState('all');

            const workflows = [
                { name: 'Auto Tag & Categorize', desc: 'Automatically tag and categorize generated content using AI', steps: 4, runs: '12.4K', lastRun: '2 min ago', status: 'active', trigger: 'On Generation', icon: 'tag' },
                { name: 'Batch Upscale Pipeline', desc: 'Upscale images in batch with quality enhancement', steps: 3, runs: '8.2K', lastRun: '1 hour ago', status: 'active', trigger: 'Manual', icon: 'maximize' },
                { name: 'Content Moderation', desc: 'Auto-moderate content before publishing', steps: 5, runs: '45.1K', lastRun: '5 min ago', status: 'active', trigger: 'On Upload', icon: 'shield' },
                { name: 'Social Media Scheduler', desc: 'Generate and schedule social media content', steps: 6, runs: '3.7K', lastRun: '3 days ago', status: 'paused', trigger: 'Scheduled', icon: 'calendar' },
                { name: 'Model Fine-Tuner', desc: 'Automated fine-tuning workflow with dataset preparation', steps: 7, runs: '1.2K', lastRun: '1 week ago', status: 'draft', trigger: 'Manual', icon: 'cpu' },
                { name: 'Video Frame Extractor', desc: 'Extract and enhance key frames from video generations', steps: 3, runs: '6.8K', lastRun: '30 min ago', status: 'active', trigger: 'On Video Gen', icon: 'film' },
            ];

            const filtered = filter === 'all' ? workflows : workflows.filter(w => w.status === filter);
            const statusColors = { active: 'bg-success/10 text-success border-success/20', paused: 'bg-accent/10 text-accent border-accent/20', draft: 'bg-muted/10 text-muted border-border' };

            return (
                <div className="min-h-screen bg-bg">
                    <Sidebar isOpen={sidebarOpen} setIsOpen={setSidebarOpen} />
                    <div className="lg:ml-64">
                        <header className="sticky top-0 z-30 glass border-b border-border">
                            <div className="flex items-center justify-between px-4 sm:px-6 h-16">
                                <div className="flex items-center gap-4">
                                    <button onClick={() => setSidebarOpen(true)} className="lg:hidden p-2 rounded-lg hover:bg-surface-2 text-muted"><Icon name="menu" size={20} /></button>
                                    <h1 className="text-xl font-bold">Workflows</h1>
                                </div>
                                <div className="flex items-center gap-3">
                                    <a href="#" className="px-4 py-2 rounded-lg bg-primary text-primary-foreground text-sm font-semibold hover:brightness-110 transition-all flex items-center gap-2"><Icon name="plus" size={16} />Create Workflow</a>
                                    <a href="profile.html" className="w-9 h-9 rounded-lg overflow-hidden border border-border"><img src="http://static.photos/people/100x100/10" alt="" className="w-full h-full object-cover" /></a>
                                </div>
                            </div>
                        </header>

                        <main className="p-4 sm:p-6 lg:p-8">
                            {/* Stats */}
                            <div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
                                {[
                                    { label: 'Total Workflows', value: '6', icon: 'workflow', color: 'text-primary', bg: 'bg-primary/10' },
                                    { label: 'Active', value: '4', icon: 'play-circle', color: 'text-success', bg: 'bg-success/10' },
                                    { label: 'Total Runs', value: '77.4K', icon: 'activity', color: 'text-secondary', bg: 'bg-secondary/10' },
                                    { label: 'Avg Duration', value: '2.4s', icon: 'clock', color: 'text-accent', bg: 'bg-accent/10' },
                                ].map((s, i) => (
                                    <div key={i} className="rounded-2xl p-5 bg-surface border border-border">
                                        <div className="flex items-center gap-3 mb-3">
                                            <div className={`w-9 h-9 rounded-lg ${s.bg} flex items-center justify-center`}><Icon name={s.icon} size={16} className={s.color} /></div>
                                            <span className="text-xs text-muted">{s.label}</span>
                                        </div>
                                        <div className="text-2xl font-bold text-fg">{s.value}</div>
                                    </div>
                                ))}
                            </div>

                            {/* Filters */}
                            <div className="flex items-center gap-2 mb-6">
                                {['all', 'active', 'paused', 'draft'].map(f => (
                                    <button key={f} onClick={() => setFilter(f)} className={`px-4 py-2 rounded-lg text-sm font-medium capitalize transition-colors ${filter === f ? 'bg-primary text-primary-foreground' : 'text-muted hover:text-fg hover:bg-surface-2'}`}>{f}</button>
                                ))}
                            </div>

                            {/* Workflow list */}
                            <div className="space-y-4">
                                {filtered.map((w, i) => (
                                    <div key={i} className="group rounded-2xl p-5 bg-surface border border-border hover:border-surface-3 transition-all">
                                        <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
                                            <div className="flex items-start gap-4">
                                                <div className="w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center shrink-0"><Icon name={w.icon} size={22} className="text-primary" /></div>
                                                <div>
                                                    <div className="flex items-center gap-2 mb-1">
                                                        <h3 className="font-semibold text-fg">{w.name}</h3>
                                                        <span className={`px-2.5 py-0.5 rounded-full text-xs font-semibold border ${statusColors[w.status]}`}>{w.status}</span>
                                                    </div>
                                                    <p className="text-sm text-muted mb-2">{w.desc}</p>
                                                    <div className="flex items-center gap-4 text-xs text-muted">
                                                        <span className="flex items-center gap-1"><Icon name="git-branch" size={12} />{w.steps} steps</span>
                                                        <span className="flex items-center gap-1"><Icon name="play" size={12} />{w.runs} runs</span>
                                                        <span className="flex items-center gap-1"><Icon name="zap" size={12} />{w.trigger}</span>
                                                    </div>
                                                </div>
                                            </div>
                                            <div className="flex items-center gap-2">
                                                <button className="px-3 py-2 rounded-lg border border-border text-sm text-muted hover:text-fg hover:bg-surface-2 transition-colors flex items-center gap-1.5"><Icon name="play" size={14} />Run</button>
                                                <button className="px-3 py-2 rounded-lg border border-border text-sm text-muted hover:text-fg hover:bg-surface-2 transition-colors flex items-center gap-1.5"><Icon name="pencil" size={14} />Edit</button>
                                                <button className="p-2 rounded-lg border border-border text-muted hover:text-fg hover:bg-surface-2 transition-colors"><Icon name="more-vertical" size={16} /></button>
                                            </div>
                                        </div>

                                        {/* Mini flow visualization */}
                                        <div className="mt-4 flex items-center gap-2 overflow-x-auto no-scrollbar pb-1">
                                            {Array.from({length: w.steps}).map((_, j) => (
                                                <React.Fragment key={j}>
                                                    <div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-surface-2 border border-border shrink-0">
                                                        <div className={`w-2 h-2 rounded-full ${j < 3 ? 'bg-success' : j === 3 ? 'bg-primary' : 'bg-muted'}`} />
                                                        <span className="text-xs text-muted">Step {j + 1}</span>
                                                    </div>
                                                    {j < w.steps - 1 && <Icon name="chevron-right" size={14} className="text-muted shrink-0" />}
                                                </React.Fragment>
                                            ))}
                                        </div>
                                    </div>
                                ))}
                            </div>
                        </main>
                    </div>
                </div>
            );
        };
        ReactDOM.createRoot(document.getElementById('root')).render(<App />);
    </script>
</body>
</html>