Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 0 additions & 52 deletions src/components/layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -288,58 +288,6 @@ export function Layout() {
/>
)}

{/* Abstract art background */}
<div className="absolute inset-0 overflow-hidden pointer-events-none z-0">
{/* Gradient orbs */}
<div className="absolute -top-40 -right-40 w-96 h-96 bg-primary/10 rounded-full blur-3xl animate-float" />
<div className="absolute top-1/2 -left-40 w-80 h-80 bg-primary/10 rounded-full blur-3xl animate-float-delayed" />
<div className="absolute -bottom-40 right-1/3 w-72 h-72 bg-accent/15 rounded-full blur-3xl animate-float-slow" />

{/* Abstract SVG shapes */}
<svg className="absolute inset-0 w-full h-full opacity-[0.15]" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="hsl(var(--gradient-start))" />
<stop offset="50%" stopColor="hsl(var(--gradient-mid))" />
<stop offset="100%" stopColor="hsl(var(--gradient-end))" />
</linearGradient>
</defs>
{/* Flowing curves */}
<path
d="M0,100 Q200,50 400,100 T800,100 T1200,100 T1600,100 T2000,100"
fill="none"
stroke="url(#grad1)"
strokeWidth="2"
className="animate-wave"
/>
<path
d="M0,200 Q200,150 400,200 T800,200 T1200,200 T1600,200 T2000,200"
fill="none"
stroke="url(#grad1)"
strokeWidth="1.5"
className="animate-wave-delayed"
/>
<path
d="M0,300 Q200,250 400,300 T800,300 T1200,300 T1600,300 T2000,300"
fill="none"
stroke="url(#grad1)"
strokeWidth="1"
className="animate-wave-slow"
/>
{/* Geometric shapes */}
<circle cx="15%" cy="20%" r="80" fill="none" stroke="url(#grad1)" strokeWidth="1" className="animate-rotate-slow" style={{ transformOrigin: '15% 20%' }} />
<circle cx="85%" cy="70%" r="120" fill="none" stroke="url(#grad1)" strokeWidth="1" className="animate-rotate-reverse" style={{ transformOrigin: '85% 70%' }} />
<polygon points="50,50 100,25 100,75" fill="url(#grad1)" className="animate-pulse-slow" style={{ transform: 'translate(70%, 60%)' }} />
<rect x="80%" y="15%" width="60" height="60" rx="8" fill="none" stroke="url(#grad1)" strokeWidth="1" className="animate-spin-slow" style={{ transformOrigin: '83% 18%' }} />
</svg>

{/* Floating particles */}
<div className="absolute top-1/4 left-1/4 w-3 h-3 rounded-full bg-primary/40 animate-particle" />
<div className="absolute top-3/4 left-1/3 w-2.5 h-2.5 rounded-full bg-primary/30 animate-particle-delayed" />
<div className="absolute top-1/3 right-1/4 w-4 h-4 rounded-full bg-accent/35 animate-particle-slow" />
<div className="absolute bottom-1/4 right-1/3 w-2 h-2 rounded-full bg-primary/45 animate-particle" />
<div className="absolute top-2/3 left-1/2 w-3 h-3 rounded-full bg-accent/30 animate-particle-delayed" />
</div>
<Sidebar
collapsed={sidebarCollapsed}
onToggle={() => setSidebarCollapsed(!sidebarCollapsed)}
Expand Down
66 changes: 55 additions & 11 deletions src/components/playground/BatchOutputGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@ import {
DialogContent,
DialogTitle,
} from '@/components/ui/dialog'
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip'
import { Download, CheckCircle2, XCircle, ExternalLink, Copy, Check, Loader2 } from 'lucide-react'
import { AudioPlayer } from '@/components/shared/AudioPlayer'
import { FlappyBird } from './FlappyBird'
Expand Down Expand Up @@ -232,6 +227,26 @@ export function BatchOutputGrid({
return null
}

// Get successful results for navigation
const successfulResults = results.filter(r => !r.error).sort((a, b) => a.index - b.index)

// Navigate to previous/next result (with loop support)
const navigateResult = useCallback((direction: 'prev' | 'next') => {
if (!selectedResult || successfulResults.length <= 1) return

const currentIdx = successfulResults.findIndex(r => r.index === selectedResult.index)
if (currentIdx === -1) return

let newIdx: number
if (direction === 'prev') {
newIdx = currentIdx === 0 ? successfulResults.length - 1 : currentIdx - 1
} else {
newIdx = currentIdx === successfulResults.length - 1 ? 0 : currentIdx + 1
}

setSelectedResult(successfulResults[newIdx])
}, [selectedResult, successfulResults])

return (
<div className={cn('flex flex-col h-full', className)}>
{/* Header */}
Expand Down Expand Up @@ -408,13 +423,42 @@ export function BatchOutputGrid({

{/* Detail Dialog */}
<Dialog open={!!selectedResult} onOpenChange={() => setSelectedResult(null)}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
<DialogTitle>
{t('playground.batch.result')} #{selectedResult?.index !== undefined ? selectedResult.index + 1 : ''}
</DialogTitle>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-hidden flex flex-col p-0">
{/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b">
<DialogTitle className="flex items-center gap-2">
{t('playground.batch.result')} #{selectedResult?.index !== undefined ? selectedResult.index + 1 : ''}
{successfulResults.length > 1 && (
<span className="text-sm font-normal text-muted-foreground">
({successfulResults.findIndex(r => r.index === selectedResult?.index) + 1}/{successfulResults.length})
</span>
)}
</DialogTitle>
</div>
{selectedResult && (
<div className="flex-1 overflow-auto">
<div className="space-y-4">
<div className="flex-1 overflow-auto relative">
{/* Navigation buttons on sides */}
{successfulResults.length > 1 && (
<>
<Button
size="icon"
variant="secondary"
onClick={() => navigateResult('prev')}
className="absolute left-2 top-1/2 -translate-y-1/2 z-10 h-10 w-10 rounded-full opacity-80 hover:opacity-100"
>
<span className="text-xl">◀</span>
</Button>
<Button
size="icon"
variant="secondary"
onClick={() => navigateResult('next')}
className="absolute right-2 top-1/2 -translate-y-1/2 z-10 h-10 w-10 rounded-full opacity-80 hover:opacity-100"
>
<span className="text-xl">▶</span>
</Button>
</>
)}
<div className="space-y-4 p-6">
{selectedResult.outputs.map((output, outputIndex) => {
const isObject = typeof output === 'object' && output !== null
const outputStr = isObject ? JSON.stringify(output, null, 2) : String(output)
Expand Down
2 changes: 2 additions & 0 deletions src/lib/electronAPI.web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,4 +351,6 @@ export const electronAPIWeb: ElectronAPI = {
// Inject electronAPI when running in a browser environment.
if (isBrowser) {
;(window as Window & { electronAPI: ElectronAPI }).electronAPI = electronAPIWeb
// Set document title for web version (Desktop keeps "WaveSpeed Desktop" from index.html)
document.title = 'WaveSpeedAI Studio'
}
49 changes: 47 additions & 2 deletions src/pages/HistoryPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,23 @@ export function HistoryPage() {
setTimeout(() => setCopiedId(false), 2000)
}

// Navigate to previous/next history item (with loop support)
const navigateHistory = useCallback((direction: 'prev' | 'next') => {
if (!selectedItem || items.length <= 1) return

const currentIdx = items.findIndex(item => item.id === selectedItem.id)
if (currentIdx === -1) return

let newIdx: number
if (direction === 'prev') {
newIdx = currentIdx === 0 ? items.length - 1 : currentIdx - 1
} else {
newIdx = currentIdx === items.length - 1 ? 0 : currentIdx + 1
}

setSelectedItem(items[newIdx])
}, [selectedItem, items])

const fetchHistory = useCallback(async () => {
if (!isValidated) return

Expand Down Expand Up @@ -587,10 +604,38 @@ export function HistoryPage() {
<Dialog open={!!selectedItem} onOpenChange={(open) => !open && setSelectedItem(null)}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
<DialogHeader>
<DialogTitle>{t('history.generationDetails')}</DialogTitle>
<DialogTitle className="flex items-center gap-2">
{t('history.generationDetails')}
{items.length > 1 && (
<span className="text-sm font-normal text-muted-foreground">
({items.findIndex(item => item.id === selectedItem?.id) + 1}/{items.length})
</span>
)}
</DialogTitle>
</DialogHeader>
{selectedItem && (
<div className="flex-1 overflow-y-auto space-y-4">
<div className="flex-1 overflow-y-auto space-y-4 relative">
{/* Navigation buttons on sides */}
{items.length > 1 && (
<>
<Button
size="icon"
variant="secondary"
onClick={() => navigateHistory('prev')}
className="absolute left-2 top-1/2 -translate-y-1/2 z-10 h-10 w-10 rounded-full opacity-80 hover:opacity-100"
>
<span className="text-xl">◀</span>
</Button>
<Button
size="icon"
variant="secondary"
onClick={() => navigateHistory('next')}
className="absolute right-2 top-1/2 -translate-y-1/2 z-10 h-10 w-10 rounded-full opacity-80 hover:opacity-100"
>
<span className="text-xl">▶</span>
</Button>
</>
)}
<div className="flex justify-end">
<Button
variant="destructive"
Expand Down
2 changes: 1 addition & 1 deletion src/pages/WelcomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ export function WelcomePage() {
<Sparkles className="relative h-9 w-9 text-primary" />
</div>
<h1 className="text-2xl font-bold bg-gradient-to-r from-primary via-purple-500 to-pink-500 bg-clip-text text-transparent">
WaveSpeedAI Studio
{navigator.userAgent.toLowerCase().includes('electron') ? 'WaveSpeed Desktop' : 'WaveSpeedAI Studio'}
</h1>
</div>
<p className="text-base text-muted-foreground max-w-lg mx-auto">
Expand Down