Initial scaffold: FastAPI backend + React frontend + Docker Compose
- Backend: FastAPI with SQLite, clip CRUD, health check - Frontend: React + Vite + TypeScript + Tailwind, dark theme - Components: Generator, Gallery, Player - Docker: Compose setup for backend + frontend - Architecture docs in Palace KB
This commit is contained in:
commit
a2bc785b14
23 files changed
+819
No files matched your search
@@ -0,0 +1,95 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import Generator from './components/Generator'
|
||||
import Gallery from './components/Gallery'
|
||||
import Player from './components/Player'
|
||||
|
||||
interface Clip {
|
||||
id: string
|
||||
prompt: string
|
||||
model: string
|
||||
duration_seconds: number
|
||||
status: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [clips, setClips] = useState<Clip[]>([])
|
||||
const [currentClip, setCurrentClip] = useState<Clip | null>(null)
|
||||
const [isPlaying, setIsPlaying] = useState(false)
|
||||
|
||||
const fetchClips = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/clips')
|
||||
const data = await res.json()
|
||||
setClips(data.clips)
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch clips:', err)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchClips()
|
||||
const interval = setInterval(fetchClips, 5000)
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
const handlePlay = (clip: Clip) => {
|
||||
setCurrentClip(clip)
|
||||
setIsPlaying(true)
|
||||
}
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
try {
|
||||
await fetch(`/api/clips/${id}`, { method: 'DELETE' })
|
||||
if (currentClip?.id === id) {
|
||||
setCurrentClip(null)
|
||||
setIsPlaying(false)
|
||||
}
|
||||
fetchClips()
|
||||
} catch (err) {
|
||||
console.error('Failed to delete clip:', err)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-lyria-dark">
|
||||
<header className="border-b border-white/10 px-6 py-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded bg-lyria-accent flex items-center justify-center text-white font-bold text-sm">
|
||||
L
|
||||
</div>
|
||||
<h1 className="text-xl font-bold text-white">Lyria Studio</h1>
|
||||
<span className="text-xs text-lyria-muted bg-white/5 px-2 py-0.5 rounded">v0.1</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="max-w-6xl mx-auto p-6 grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<div className="lg:col-span-1">
|
||||
<Generator onGenerate={fetchClips} />
|
||||
</div>
|
||||
<div className="lg:col-span-2 space-y-4">
|
||||
<Gallery
|
||||
clips={clips}
|
||||
onPlay={handlePlay}
|
||||
onDelete={handleDelete}
|
||||
currentClipId={currentClip?.id}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{currentClip && (
|
||||
<Player
|
||||
clip={currentClip}
|
||||
isPlaying={isPlaying}
|
||||
onTogglePlay={() => setIsPlaying(!isPlaying)}
|
||||
onClose={() => {
|
||||
setCurrentClip(null)
|
||||
setIsPlaying(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
Reference in new issue
Block a user