Skip to content
Closed
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
Binary file added build/images/AudioConverter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added build/images/FaceEnhancerNew.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added build/images/ImageConverter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added build/images/MediaMerger.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added build/images/MediaTrimmer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added build/images/VideoConverter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions mobile/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Routes, Route } from 'react-router-dom'
import { MobileLayout } from '@mobile/components/layout/MobileLayout'
import { WelcomePage } from '@/pages/WelcomePage'
import { FeaturedModelsPage } from '@/pages/FeaturedModelsPage'
import { SmartPlaygroundPage } from '@/pages/SmartPlaygroundPage'
import { ModelsPage } from '@/pages/ModelsPage'
import { MobilePlaygroundPage } from '@mobile/pages/MobilePlaygroundPage'
import { MobileTemplatesPage } from '@mobile/pages/MobileTemplatesPage'
Expand Down Expand Up @@ -37,6 +38,7 @@ function App() {
<Route path="/" element={<MobileLayout />}>
<Route index element={<WelcomePage />} />
<Route path="featured-models" element={<FeaturedModelsPage />} />
<Route path="featured-models/:familyId" element={<SmartPlaygroundPage />} />
<Route path="models" element={<ModelsPage />} />
<Route path="playground" element={<MobilePlaygroundPage />} />
<Route path="playground/*" element={<MobilePlaygroundPage />} />
Expand Down
19 changes: 10 additions & 9 deletions mobile/src/components/layout/MobileHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useLocation, useNavigate } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { ChevronLeft, Zap, Home, Sun, Moon } from 'lucide-react'
import { ChevronLeft, Home, Sun, Moon } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { useThemeStore } from '@/stores/themeStore'
import { AppLogo } from '@/components/layout/AppLogo'
import { cn } from '@/lib/utils'

// Map paths to page titles (translation key or plain text prefixed with '!')
Expand Down Expand Up @@ -59,8 +60,8 @@ export function MobileHeader() {
return t('nav.playground')
}

// Default to app name
return 'WaveSpeed'
// Default to app name (home page)
return null
}

const showBackButton = pagesWithBackButton.some(path =>
Expand Down Expand Up @@ -94,11 +95,7 @@ export function MobileHeader() {
<ChevronLeft className="h-5 w-5" />
</Button>
) : location.pathname === '/' ? (
<div className="flex items-center gap-2">
<div className="gradient-bg rounded-lg p-1">
<Zap className="h-4 w-4 text-white" />
</div>
</div>
<AppLogo className="h-7 w-7 shrink-0" />
) : (
<Button
variant="ghost"
Expand All @@ -116,7 +113,11 @@ export function MobileHeader() {
"font-semibold text-base truncate",
showBackButton ? "flex-1 text-center" : "flex-1"
)}>
{getPageTitle()}
{getPageTitle() ?? (
<span className="font-bold bg-clip-text text-transparent bg-gradient-to-r from-[#1a2654] to-[#1a6b7c] dark:from-[#38bdf8] dark:to-[#34d399]">
WaveSpeed
</span>
)}
</h1>

{/* Right side - Action buttons */}
Expand Down
30 changes: 18 additions & 12 deletions mobile/src/pages/MobileFreeToolsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import imageEnhancerImg from '../../../build/images/ImageEnhancer.jpeg'
import backgroundRemoverImg from '../../../build/images/BackgroundRemover.jpeg'
import imageEraserImg from '../../../build/images/ImageEraser.jpeg'
import SegmentAnythingImg from '../../../build/images/SegmentAnything.png'
import faceEnhancerImg from '../../../build/images/FaceEnhancerNew.png'
import videoConverterImg from '../../../build/images/VideoConverter.png'
import audioConverterImg from '../../../build/images/AudioConverter.png'
import imageConverterImg from '../../../build/images/ImageConverter.png'
import mediaTrimmerImg from '../../../build/images/MediaTrimmer.png'
import mediaMergerImg from '../../../build/images/MediaMerger.png'

export function MobileFreeToolsPage() {
const { t } = useTranslation()
Expand Down Expand Up @@ -73,8 +79,8 @@ export function MobileFreeToolsPage() {
descriptionKey: 'freeTools.faceEnhancer.description',
route: '/free-tools/face-enhancer',
gradient: 'from-rose-500/20 via-pink-500/10 to-transparent',
iconGradient: 'from-rose-500 to-pink-600'
// No image - will show icon placeholder
iconGradient: 'from-rose-500 to-pink-600',
image: faceEnhancerImg
},
{
id: 'video-converter',
Expand All @@ -83,8 +89,8 @@ export function MobileFreeToolsPage() {
descriptionKey: 'freeTools.videoConverter.description',
route: '/free-tools/video-converter',
gradient: 'from-indigo-500/20 via-blue-500/10 to-transparent',
iconGradient: 'from-indigo-500 to-blue-600'
// No image - will show icon placeholder
iconGradient: 'from-indigo-500 to-blue-600',
image: videoConverterImg
},
{
id: 'audio-converter',
Expand All @@ -93,8 +99,8 @@ export function MobileFreeToolsPage() {
descriptionKey: 'freeTools.audioConverter.description',
route: '/free-tools/audio-converter',
gradient: 'from-teal-500/20 via-cyan-500/10 to-transparent',
iconGradient: 'from-teal-500 to-cyan-600'
// No image - will show icon placeholder
iconGradient: 'from-teal-500 to-cyan-600',
image: audioConverterImg
},
{
id: 'image-converter',
Expand All @@ -103,8 +109,8 @@ export function MobileFreeToolsPage() {
descriptionKey: 'freeTools.imageConverter.description',
route: '/free-tools/image-converter',
gradient: 'from-amber-500/20 via-yellow-500/10 to-transparent',
iconGradient: 'from-amber-500 to-yellow-600'
// No image - will show icon placeholder
iconGradient: 'from-amber-500 to-yellow-600',
image: imageConverterImg
},
{
id: 'media-trimmer',
Expand All @@ -113,8 +119,8 @@ export function MobileFreeToolsPage() {
descriptionKey: 'freeTools.mediaTrimmer.description',
route: '/free-tools/media-trimmer',
gradient: 'from-red-500/20 via-orange-500/10 to-transparent',
iconGradient: 'from-red-500 to-orange-600'
// No image - will show icon placeholder
iconGradient: 'from-red-500 to-orange-600',
image: mediaTrimmerImg
},
{
id: 'media-merger',
Expand All @@ -123,8 +129,8 @@ export function MobileFreeToolsPage() {
descriptionKey: 'freeTools.mediaMerger.description',
route: '/free-tools/media-merger',
gradient: 'from-purple-500/20 via-fuchsia-500/10 to-transparent',
iconGradient: 'from-purple-500 to-fuchsia-600'
// No image - will show icon placeholder
iconGradient: 'from-purple-500 to-fuchsia-600',
image: mediaMergerImg
}
]

Expand Down
34 changes: 26 additions & 8 deletions mobile/src/pages/MobilePlaygroundPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export function MobilePlaygroundPage() {

const activeTab = getActiveTab()
const templateLoadedRef = useRef<string | null>(null)
const pendingTemplateRef = useRef<{ values: Record<string, unknown>, name: string } | null>(null)
const prevOutputsLengthRef = useRef(0)
const lastSavedPredictionRef = useRef<string | null>(null)

Expand Down Expand Up @@ -130,18 +131,25 @@ export function MobilePlaygroundPage() {
if (templateId && templatesLoaded && activeTab && templateLoadedRef.current !== templateId) {
const template = templates.find(t => t.id === templateId)
if (template) {
setFormValues(template.values)
templateLoadedRef.current = templateId
// Switch to input view when loading template
setActiveView('input')
toast({
title: t('playground.templateLoaded'),
description: t('playground.loadedTemplate', { name: template.name }),
})
// Store template values as pending — they will be applied in handleSetDefaults
// after DynamicForm loads the model schema and sets default values.
// This avoids the race condition where defaults overwrite template values.
pendingTemplateRef.current = { values: template.values, name: template.name }
// If model is already correct and form fields exist, apply immediately
if (activeTab.selectedModel?.model_id === template.modelId && activeTab.formFields.length > 0) {
setFormValues(template.values)
pendingTemplateRef.current = null
toast({
title: t('playground.templateLoaded'),
description: t('playground.loadedTemplate', { name: template.name }),
})
}
setSearchParams({}, { replace: true })
}
}
}, [searchParams, templates, templatesLoaded, activeTab, setFormValues, setSearchParams])
}, [searchParams, templates, templatesLoaded, activeTab, setFormValues, setSearchParams, t])

const handleSaveTemplate = () => {
if (!activeTab?.selectedModel || !newTemplateName.trim()) return
Expand Down Expand Up @@ -186,7 +194,17 @@ export function MobilePlaygroundPage() {

const handleSetDefaults = useCallback((defaults: Record<string, unknown>) => {
setFormValues(defaults)
}, [setFormValues])
// Apply pending template values after defaults are set (overrides defaults)
if (pendingTemplateRef.current) {
const { values, name } = pendingTemplateRef.current
pendingTemplateRef.current = null
setFormValues(values)
toast({
title: t('playground.templateLoaded'),
description: t('playground.loadedTemplate', { name }),
})
}
}, [setFormValues, t])

const handleRun = async () => {
// Switch to output view immediately so user can play game while waiting
Expand Down
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { HistoryPage } from '@/pages/HistoryPage'
import { AssetsPage } from '@/pages/AssetsPage'
import { SettingsPage } from '@/pages/SettingsPage'
import { FeaturedModelsPage } from '@/pages/FeaturedModelsPage'
import { SmartPlaygroundPage } from '@/pages/SmartPlaygroundPage'
import { FreeToolsPage } from '@/pages/FreeToolsPage'
import { ZImagePage } from '@/pages/ZImagePage'
import { useApiKeyStore } from '@/stores/apiKeyStore'
Expand Down Expand Up @@ -63,6 +64,7 @@ function App() {
<Route path="/" element={<Layout />}>
<Route index element={<WelcomePage />} />
<Route path="featured-models" element={<FeaturedModelsPage />} />
<Route path="featured-models/:familyId" element={<SmartPlaygroundPage />} />
<Route path="models" element={<ModelsPage />} />
<Route path="playground" element={<PlaygroundPage />} />
<Route path="playground/*" element={<PlaygroundPage />} />
Expand Down
Binary file added src/assets/logo-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/logo-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions src/components/layout/AppLogo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import logoLight from '@/assets/logo-light.png'
import logoDark from '@/assets/logo-dark.png'

interface AppLogoProps {
className?: string
}

export function AppLogo({ className = 'h-10 w-10' }: AppLogoProps) {
return (
<>
<img
src={logoLight}
alt="WaveSpeed"
className={`${className} block dark:hidden object-contain`}
/>
<img
src={logoDark}
alt="WaveSpeed"
className={`${className} hidden dark:block object-contain`}
/>
</>
)
}
8 changes: 2 additions & 6 deletions src/components/layout/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useState, useEffect, useRef, useCallback, createContext } from 'react'
import { useState, useEffect, useRef, useCallback } from 'react'
import { Outlet, useNavigate, useLocation } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { Sidebar } from './Sidebar'
import { PageResetContext } from './PageResetContext'
import { Toaster } from '@/components/ui/toaster'
import { TooltipProvider } from '@/components/ui/tooltip'
import { ToastAction } from '@/components/ui/toast'
Expand All @@ -28,11 +29,6 @@ import { FaceSwapperPage } from '@/pages/FaceSwapperPage'
import { WorkflowPage } from '@/workflow/WorkflowPage'
import { useFreeToolListener } from '@/workflow/hooks/useFreeToolListener'

// Context for resetting persistent pages (forces remount by changing key)
export const PageResetContext = createContext<{ resetPage: (path: string) => void }>({
resetPage: () => {}
})

// Helper to generate next key
let keyCounter = 0
const nextKey = () => ++keyCounter
Expand Down
8 changes: 8 additions & 0 deletions src/components/layout/PageResetContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createContext } from 'react'

// Context for resetting persistent pages (forces remount by changing key)
// Extracted to its own file to avoid pulling in Layout's heavy imports (WorkflowPage, etc.)
// when only the context is needed.
export const PageResetContext = createContext<{ resetPage: (path: string) => void }>({
resetPage: () => {}
})
11 changes: 3 additions & 8 deletions src/components/layout/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
Star,
X
} from 'lucide-react'
import appIcon from '../../../build/icon.png'
import { AppLogo } from './AppLogo'

interface NavItem {
titleKey: string
Expand Down Expand Up @@ -173,15 +173,10 @@ export function Sidebar({ collapsed, onToggle, lastFreeToolsPage, isMobileOpen,
collapsed && !isMobileOpen ? "justify-center px-2" : "gap-3 px-5"
)}
>
<img
src={appIcon}
alt="WaveSpeed"
className="h-10 w-10 rounded-xl shadow-sm object-cover"
style={{ flexShrink: 0 }}
/>
<AppLogo className="h-10 w-10 shrink-0" />
{(!collapsed || isMobileOpen) && (
<div className="min-w-0">
<span className="block whitespace-nowrap text-lg font-bold gradient-text">WaveSpeed</span>
<span className="block whitespace-nowrap text-lg font-bold bg-clip-text text-transparent bg-gradient-to-r from-[#1a2654] to-[#1a6b7c] dark:from-[#38bdf8] dark:to-[#34d399]">WaveSpeed</span>
</div>
)}
</div>
Expand Down
10 changes: 7 additions & 3 deletions src/components/playground/FileUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -295,9 +295,13 @@ export function FileUpload({
<div className="flex gap-2 flex-wrap">
{urls.map((url, index) => {
const FileIconComponent = getFileIcon()
const isImage = accept.includes('image') && url.match(/\.(jpg|jpeg|png|gif|webp)(\?.*)?$/i)
const isVideo = accept.includes('video') && url.match(/\.(mp4|webm|mov|avi|mkv)(\?.*)?$/i)
const isAudio = accept.includes('audio') && url.match(/\.(mp3|wav|ogg|webm|m4a)(\?.*)?$/i)
const hasImageExt = url.match(/\.(jpg|jpeg|png|gif|webp)(\?.*)?$/i)
const hasVideoExt = url.match(/\.(mp4|webm|mov|avi|mkv)(\?.*)?$/i)
const hasAudioExt = url.match(/\.(mp3|wav|ogg|webm|m4a)(\?.*)?$/i)
// Fallback: if accept type matches but URL has no recognized extension, trust accept
const isImage = accept.includes('image') && (hasImageExt || (!hasVideoExt && !hasAudioExt))
const isVideo = accept.includes('video') && (hasVideoExt || (!hasImageExt && !hasAudioExt))
const isAudio = accept.includes('audio') && (hasAudioExt || (!hasImageExt && !hasVideoExt))

const handlePreview = () => {
setPreviewUrl(url)
Expand Down
Loading