refactor: rename project Lyria Studio → Kithara Studio

- App name, UI text, page titles → Kithara Studio
- Python module: lyria_client.py → kithara_client.py
- Tailwind color classes: lyria-* → kithara-*
- CSS custom properties: --lyria-* → --kithara-*
- Database: lyria.db → kithara.db
- Package name: kithara-studio-frontend
- localStorage key: kithara-saved-searches
- Add project logo (lyre icon, black on transparent, invert via CSS)

Preserved as Google's property:
- LYRIA_API_KEY, LYRIA_BASE_URL, LYRIA_DEFAULT_MODEL env vars
- LYRIA_MODELS dict and model IDs (lyria-3-clip-preview, lyria-3-pro-preview)
- Google Lyria API references in error messages and docstrings
This commit is contained in:
rin.kagamine committed 2026-05-31 09:45:22 -04:00
1 parent 6c1e1b4509
commit 816f8fbd2f
20 files changed
+191 -193

No files matched your search

+5 -7
View File
@@ -237,19 +237,17 @@ function App() {
}
return (
<div className="min-h-screen bg-lyria-dark">
<div className="min-h-screen bg-kithara-dark">
<header className="border-b border-white/10 px-6 py-4">
<div className="flex items-center justify-between">
<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>
<img src="/logo.png" alt="Kithara Studio" className="w-8 h-8 invert" />
<h1 className="text-xl font-bold text-white">Kithara Studio</h1>
<span className="text-xs text-kithara-muted bg-white/5 px-2 py-0.5 rounded">v0.1</span>
</div>
<button
onClick={() => setSettingsOpen(true)}
className="text-lyria-muted hover:text-white transition-colors p-1 rounded hover:bg-white/5"
className="text-kithara-muted hover:text-white transition-colors p-1 rounded hover:bg-white/5"
title="Settings"
aria-label="Open settings"
>
@@ -41,17 +41,17 @@ export default function ArtworkGenerationModal({ isOpen, onClose, onGenerate, tr
/>
{/* Modal */}
<div className="relative bg-lyria-surface border border-white/10 rounded-xl shadow-2xl w-full max-w-md mx-4 overflow-hidden">
<div className="relative bg-kithara-surface border border-white/10 rounded-xl shadow-2xl w-full max-w-md mx-4 overflow-hidden">
{/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b border-white/10">
<div>
<h2 className="text-lg font-semibold text-white">Generate Artwork</h2>
<p className="text-xs text-lyria-muted mt-0.5 truncate max-w-[200px]">{trackName}</p>
<p className="text-xs text-kithara-muted mt-0.5 truncate max-w-[200px]">{trackName}</p>
</div>
<button
onClick={handleClose}
disabled={loading}
className="text-lyria-muted hover:text-white transition-colors disabled:opacity-50"
className="text-kithara-muted hover:text-white transition-colors disabled:opacity-50"
aria-label="Close"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -70,10 +70,10 @@ export default function ArtworkGenerationModal({ isOpen, onClose, onGenerate, tr
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="e.g., A cyberpunk cityscape at night with neon lights reflecting on wet streets..."
className="w-full h-32 bg-black/30 border border-white/10 rounded-lg px-4 py-3 text-sm text-white placeholder:text-lyria-muted/40 focus:outline-none focus:border-lyria-accent resize-none"
className="w-full h-32 bg-black/30 border border-white/10 rounded-lg px-4 py-3 text-sm text-white placeholder:text-kithara-muted/40 focus:outline-none focus:border-kithara-accent resize-none"
disabled={loading}
/>
<p className="text-xs text-lyria-muted/60 mt-2">
<p className="text-xs text-kithara-muted/60 mt-2">
Uses Gemini Image model. Be descriptive for best results.
</p>
</div>
@@ -82,7 +82,7 @@ export default function ArtworkGenerationModal({ isOpen, onClose, onGenerate, tr
<button
onClick={handleGenerate}
disabled={!prompt.trim() || loading}
className="w-full py-3 text-sm font-medium rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed bg-lyria-accent text-black hover:bg-lyria-accent/90"
className="w-full py-3 text-sm font-medium rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed bg-kithara-accent text-black hover:bg-kithara-accent/90"
>
{loading ? (
<span className="flex items-center justify-center gap-2">
+32 -32
View File
@@ -98,7 +98,7 @@ export default function DetailPanel({ clip, isPlaying, currentClipId, onClose, o
case 'ready': return 'bg-green-500/20 text-green-400'
case 'generating': return 'bg-yellow-500/20 text-yellow-400'
case 'failed': return 'bg-red-500/20 text-red-400'
default: return 'bg-white/10 text-lyria-muted'
default: return 'bg-white/10 text-kithara-muted'
}
}
@@ -119,13 +119,13 @@ export default function DetailPanel({ clip, isPlaying, currentClipId, onClose, o
onClick={onClose}
/>
{/* Panel */}
<div className="fixed right-0 top-0 bottom-0 w-full max-w-md bg-lyria-panel border-l border-white/10 z-50 overflow-y-auto">
<div className="fixed right-0 top-0 bottom-0 w-full max-w-md bg-kithara-panel border-l border-white/10 z-50 overflow-y-auto">
{/* Header */}
<div className="sticky top-0 bg-lyria-panel border-b border-white/10 px-5 py-4 flex items-center justify-between">
<div className="sticky top-0 bg-kithara-panel border-b border-white/10 px-5 py-4 flex items-center justify-between">
<h2 className="text-sm font-semibold text-white uppercase tracking-wider">Track Details</h2>
<button
onClick={onClose}
className="text-lyria-muted hover:text-white transition-colors"
className="text-kithara-muted hover:text-white transition-colors"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
@@ -137,17 +137,17 @@ export default function DetailPanel({ clip, isPlaying, currentClipId, onClose, o
{/* Album Artwork */}
<div>
<div className="flex items-center justify-between mb-2">
<label className="text-xs text-lyria-muted uppercase tracking-wider">Album Art</label>
<label className="text-xs text-kithara-muted uppercase tracking-wider">Album Art</label>
<div className="flex items-center gap-2">
<button
onClick={() => onGenerateArtwork?.(clip)}
className="text-xs text-lyria-accent hover:text-lyria-accent/80 transition-colors"
className="text-xs text-kithara-accent hover:text-kithara-accent/80 transition-colors"
>
Generate
</button>
<button
onClick={() => artworkInputRef.current?.click()}
className="text-xs text-lyria-accent hover:text-lyria-accent/80 transition-colors"
className="text-xs text-kithara-accent hover:text-kithara-accent/80 transition-colors"
>
{clip.artwork_filename ? 'Change' : 'Upload'}
</button>
@@ -191,7 +191,7 @@ export default function DetailPanel({ clip, isPlaying, currentClipId, onClose, o
</div>
) : (
<div
className="relative w-full aspect-square bg-white/5 rounded-lg flex flex-col items-center justify-center text-lyria-accent/20 cursor-pointer group hover:bg-white/10 transition-colors"
className="relative w-full aspect-square bg-white/5 rounded-lg flex flex-col items-center justify-center text-kithara-accent/20 cursor-pointer group hover:bg-white/10 transition-colors"
onClick={() => {
if (currentClipId === clip.id && isPlaying) {
onTogglePlay?.()
@@ -203,7 +203,7 @@ export default function DetailPanel({ clip, isPlaying, currentClipId, onClose, o
<svg className="w-14 h-14" viewBox="0 0 24 24">
<text x="12" y="19" textAnchor="middle" fontSize="20" fill="currentColor">𝄞</text>
</svg>
<span className="text-xs mt-1 text-lyria-muted/40">No album art</span>
<span className="text-xs mt-1 text-kithara-muted/40">No album art</span>
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
{currentClipId === clip.id && isPlaying ? (
<svg className="w-12 h-12 text-white" fill="currentColor" viewBox="0 0 24 24">
@@ -246,11 +246,11 @@ export default function DetailPanel({ clip, isPlaying, currentClipId, onClose, o
{/* Title — inline editable */}
<div>
<div className="flex items-center justify-between mb-1">
<label className="text-xs text-lyria-muted uppercase tracking-wider">Title</label>
<label className="text-xs text-kithara-muted uppercase tracking-wider">Title</label>
{!isEditingTitle && (
<button
onClick={startEditing}
className="text-xs text-lyria-accent hover:text-lyria-accent/80 transition-colors"
className="text-xs text-kithara-accent hover:text-kithara-accent/80 transition-colors"
>
Rename
</button>
@@ -264,7 +264,7 @@ export default function DetailPanel({ clip, isPlaying, currentClipId, onClose, o
onChange={(e) => setEditTitle(e.target.value)}
onBlur={saveTitle}
onKeyDown={handleTitleKeyDown}
className="w-full bg-white/5 border border-lyria-accent/50 rounded-md px-3 py-2 text-white text-lg font-medium outline-none focus:border-lyria-accent focus:ring-1 focus:ring-lyria-accent/30 transition-all"
className="w-full bg-white/5 border border-kithara-accent/50 rounded-md px-3 py-2 text-white text-lg font-medium outline-none focus:border-kithara-accent focus:ring-1 focus:ring-kithara-accent/30 transition-all"
placeholder="Track title..."
/>
) : (
@@ -272,14 +272,14 @@ export default function DetailPanel({ clip, isPlaying, currentClipId, onClose, o
onClick={startEditing}
title="Click to rename"
>
{clip.title || <span className="text-lyria-muted italic">Untitled</span>}
{clip.title || <span className="text-kithara-muted italic">Untitled</span>}
</p>
)}
</div>
{/* Prompt */}
<div>
<label className="text-xs text-lyria-muted uppercase tracking-wider mb-1 block">Prompt</label>
<label className="text-xs text-kithara-muted uppercase tracking-wider mb-1 block">Prompt</label>
<p className="text-sm text-white/90 whitespace-pre-wrap break-words bg-white/5 rounded-md p-3">
{clip.prompt}
</p>
@@ -288,7 +288,7 @@ export default function DetailPanel({ clip, isPlaying, currentClipId, onClose, o
{/* Structured Prompt Breakdown */}
{clip.prompt_structure && (
<div>
<label className="text-xs text-lyria-muted uppercase tracking-wider mb-1 block">Structured Prompt</label>
<label className="text-xs text-kithara-muted uppercase tracking-wider mb-1 block">Structured Prompt</label>
<div className="space-y-2">
{[
{ key: 'style', label: 'Style' },
@@ -304,7 +304,7 @@ export default function DetailPanel({ clip, isPlaying, currentClipId, onClose, o
if (!val) return null
return (
<div key={key} className="bg-white/5 rounded-md p-2">
<span className="text-xs text-lyria-accent font-medium">{label}: </span>
<span className="text-xs text-kithara-accent font-medium">{label}: </span>
<span className="text-sm text-white/90">{val}</span>
</div>
)
@@ -315,7 +315,7 @@ export default function DetailPanel({ clip, isPlaying, currentClipId, onClose, o
{/* Status */}
<div>
<label className="text-xs text-lyria-muted uppercase tracking-wider mb-1 block">Status</label>
<label className="text-xs text-kithara-muted uppercase tracking-wider mb-1 block">Status</label>
<span className={`text-xs px-2 py-1 rounded ${statusColor(clip.status)}`}>
{statusLabel(clip.status)}
</span>
@@ -324,7 +324,7 @@ export default function DetailPanel({ clip, isPlaying, currentClipId, onClose, o
{/* Error message */}
{clip.status === 'failed' && clip.error_message && (
<div>
<label className="text-xs text-lyria-muted uppercase tracking-wider mb-1 block">Error</label>
<label className="text-xs text-kithara-muted uppercase tracking-wider mb-1 block">Error</label>
<p className="text-sm text-red-400/90 bg-red-500/10 rounded-md p-3 break-words">
{clip.error_message}
</p>
@@ -334,16 +334,16 @@ export default function DetailPanel({ clip, isPlaying, currentClipId, onClose, o
{/* Metadata grid */}
<div className="grid grid-cols-2 gap-3">
<div>
<label className="text-xs text-lyria-muted uppercase tracking-wider block">Model</label>
<label className="text-xs text-kithara-muted uppercase tracking-wider block">Model</label>
<p className="text-sm text-white">{modelName(clip.model)}</p>
</div>
<div>
<label className="text-xs text-lyria-muted uppercase tracking-wider block">Duration</label>
<label className="text-xs text-kithara-muted uppercase tracking-wider block">Duration</label>
<p className="text-sm text-white">{clip.duration_seconds}s</p>
</div>
{clip.file_size_bytes && (
<div>
<label className="text-xs text-lyria-muted uppercase tracking-wider block">File Size</label>
<label className="text-xs text-kithara-muted uppercase tracking-wider block">File Size</label>
<p className="text-sm text-white">{(clip.file_size_bytes / 1024).toFixed(0)} KB</p>
</div>
)}
@@ -351,7 +351,7 @@ export default function DetailPanel({ clip, isPlaying, currentClipId, onClose, o
const c = getBPMColor(clip.bpm)
return c ? (
<div>
<label className="text-xs text-lyria-muted uppercase tracking-wider block">BPM</label>
<label className="text-xs text-kithara-muted uppercase tracking-wider block">BPM</label>
<span
className="text-sm px-2 py-0.5 rounded font-medium inline-block"
style={{ backgroundColor: c.bg, color: c.text, border: `1px solid ${c.border}` }}
@@ -365,7 +365,7 @@ export default function DetailPanel({ clip, isPlaying, currentClipId, onClose, o
const c = getKeyColor(clip.key)
return c ? (
<div>
<label className="text-xs text-lyria-muted uppercase tracking-wider block">Key</label>
<label className="text-xs text-kithara-muted uppercase tracking-wider block">Key</label>
<span
className="text-sm px-2 py-0.5 rounded font-medium inline-block"
style={{ backgroundColor: c.bg, color: c.text, border: `1px solid ${c.border}` }}
@@ -376,7 +376,7 @@ export default function DetailPanel({ clip, isPlaying, currentClipId, onClose, o
) : null
})()}
<div>
<label className="text-xs text-lyria-muted uppercase tracking-wider block">Created</label>
<label className="text-xs text-kithara-muted uppercase tracking-wider block">Created</label>
<p className="text-sm text-white">{formatDate(clip.created_at)}</p>
</div>
</div>
@@ -405,8 +405,8 @@ export default function DetailPanel({ clip, isPlaying, currentClipId, onClose, o
{/* ID (for reference) */}
<div>
<label className="text-xs text-lyria-muted uppercase tracking-wider mb-1 block">Clip ID</label>
<p className="text-xs text-lyria-muted/60 font-mono break-all">{clip.id}</p>
<label className="text-xs text-kithara-muted uppercase tracking-wider mb-1 block">Clip ID</label>
<p className="text-xs text-kithara-muted/60 font-mono break-all">{clip.id}</p>
</div>
{/* Actions */}
@@ -414,7 +414,7 @@ export default function DetailPanel({ clip, isPlaying, currentClipId, onClose, o
<a
href={`/api/clips/${clip.id}/download`}
download
className="w-full py-2 text-sm text-lyria-accent bg-lyria-accent/10 hover:bg-lyria-accent/20 rounded-md transition-colors flex items-center justify-center gap-2"
className="w-full py-2 text-sm text-kithara-accent bg-kithara-accent/10 hover:bg-kithara-accent/20 rounded-md transition-colors flex items-center justify-center gap-2"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
@@ -466,7 +466,7 @@ function MetadataField({ label, value, placeholder, onSave }: {
if (editing) {
return (
<div>
<label className="text-xs text-lyria-muted uppercase tracking-wider block mb-1">{label}</label>
<label className="text-xs text-kithara-muted uppercase tracking-wider block mb-1">{label}</label>
<div className="flex items-center gap-2">
<input
ref={inputRef}
@@ -479,7 +479,7 @@ function MetadataField({ label, value, placeholder, onSave }: {
}}
onBlur={save}
placeholder={placeholder}
className="flex-1 bg-black/30 border border-lyria-accent rounded-md px-3 py-1.5 text-sm text-white placeholder:text-lyria-muted/40 focus:outline-none"
className="flex-1 bg-black/30 border border-kithara-accent rounded-md px-3 py-1.5 text-sm text-white placeholder:text-kithara-muted/40 focus:outline-none"
/>
</div>
</div>
@@ -491,13 +491,13 @@ function MetadataField({ label, value, placeholder, onSave }: {
className="group cursor-pointer"
onClick={() => setEditing(true)}
>
<label className="text-xs text-lyria-muted uppercase tracking-wider block mb-1">{label}</label>
<label className="text-xs text-kithara-muted uppercase tracking-wider block mb-1">{label}</label>
<div className="flex items-center gap-2">
<span className={`text-sm ${value ? 'text-white' : 'text-lyria-muted/40 italic'}`}>
<span className={`text-sm ${value ? 'text-white' : 'text-kithara-muted/40 italic'}`}>
{value || placeholder}
</span>
<svg
className="w-3 h-3 text-lyria-muted/40 group-hover:text-lyria-accent transition-colors"
className="w-3 h-3 text-kithara-muted/40 group-hover:text-kithara-accent transition-colors"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
+61 -61
View File
@@ -71,7 +71,7 @@ function HighlightText({ text, query }: { text: string; query: string }) {
<>
{parts.map((part, i) =>
part.toLowerCase() === query.toLowerCase() ? (
<span key={i} className="bg-lyria-accent/30 text-white rounded px-0.5">{part}</span>
<span key={i} className="bg-kithara-accent/30 text-white rounded px-0.5">{part}</span>
) : (
<span key={i}>{part}</span>
)
@@ -104,7 +104,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
// Saved searches
const [savedSearches, setSavedSearches] = useState<SavedSearch[]>(() => {
try {
const raw = localStorage.getItem('lyria-saved-searches')
const raw = localStorage.getItem('kithara-saved-searches')
return raw ? JSON.parse(raw) : []
} catch {
return []
@@ -195,7 +195,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
}
const updated = [...savedSearches, newSearch]
setSavedSearches(updated)
localStorage.setItem('lyria-saved-searches', JSON.stringify(updated))
localStorage.setItem('kithara-saved-searches', JSON.stringify(updated))
}, [search, filters, savedSearches])
const loadSearch = useCallback((saved: SavedSearch) => {
@@ -207,7 +207,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
const deleteSearch = useCallback((id: string) => {
const updated = savedSearches.filter(s => s.id !== id)
setSavedSearches(updated)
localStorage.setItem('lyria-saved-searches', JSON.stringify(updated))
localStorage.setItem('kithara-saved-searches', JSON.stringify(updated))
}, [savedSearches])
const clearFilters = () => {
@@ -261,14 +261,14 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
if (clips.length === 0) {
return (
<div className="bg-lyria-panel rounded-lg p-8 border border-white/5 text-center">
<p className="text-lyria-muted text-sm">No clips yet. Generate your first one!</p>
<div className="bg-kithara-panel rounded-lg p-8 border border-white/5 text-center">
<p className="text-kithara-muted text-sm">No clips yet. Generate your first one!</p>
</div>
)
}
return (
<div className="bg-lyria-panel rounded-lg border border-white/5">
<div className="bg-kithara-panel rounded-lg border border-white/5">
{/* Header: title, search, view toggle */}
<div className="px-5 py-4 border-b border-white/5">
<div className="flex items-center gap-3">
@@ -279,7 +279,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
{/* Search bar */}
<div className="relative flex-1 max-w-xs">
<svg
className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-lyria-muted"
className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-kithara-muted"
fill="none" stroke="currentColor" viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
@@ -289,12 +289,12 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
placeholder="Search tracks, artists, albums..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-full bg-white/5 border border-white/10 rounded-md pl-8 pr-3 py-1.5 text-sm text-white placeholder-lyria-muted focus:outline-none focus:border-lyria-accent/50 focus:ring-1 focus:ring-lyria-accent/30 transition-colors"
className="w-full bg-white/5 border border-white/10 rounded-md pl-8 pr-3 py-1.5 text-sm text-white placeholder-kithara-muted focus:outline-none focus:border-kithara-accent/50 focus:ring-1 focus:ring-kithara-accent/30 transition-colors"
/>
{search && (
<button
onClick={() => setSearch('')}
className="absolute right-2 top-1/2 -translate-y-1/2 text-lyria-muted hover:text-white transition-colors"
className="absolute right-2 top-1/2 -translate-y-1/2 text-kithara-muted hover:text-white transition-colors"
>
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
@@ -308,8 +308,8 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
onClick={() => setShowFilters(!showFilters)}
className={`p-1.5 rounded transition-colors ${
showFilters || hasActiveFilters
? 'bg-lyria-accent/20 text-lyria-accent border border-lyria-accent/30'
: 'text-lyria-muted hover:text-white border border-white/10'
? 'bg-kithara-accent/20 text-kithara-accent border border-kithara-accent/30'
: 'text-kithara-muted hover:text-white border border-white/10'
}`}
title="Filters"
>
@@ -321,7 +321,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
{/* Saved searches button */}
<button
onClick={() => setShowSaved(!showSaved)}
className="p-1.5 rounded transition-colors text-lyria-muted hover:text-white border border-white/10"
className="p-1.5 rounded transition-colors text-kithara-muted hover:text-white border border-white/10"
title="Saved searches"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -333,7 +333,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
<div className="flex bg-white/5 rounded-md border border-white/10 overflow-hidden">
<button
onClick={() => setView('ribbons')}
className={`p-1.5 transition-colors ${view === 'ribbons' ? 'bg-lyria-accent text-white' : 'text-lyria-muted hover:text-white'}`}
className={`p-1.5 transition-colors ${view === 'ribbons' ? 'bg-kithara-accent text-white' : 'text-kithara-muted hover:text-white'}`}
title="List view"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -342,7 +342,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
</button>
<button
onClick={() => setView('tiles')}
className={`p-1.5 transition-colors ${view === 'tiles' ? 'bg-lyria-accent text-white' : 'text-lyria-muted hover:text-white'}`}
className={`p-1.5 transition-colors ${view === 'tiles' ? 'bg-kithara-accent text-white' : 'text-kithara-muted hover:text-white'}`}
title="Tile view"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -356,33 +356,33 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
{hasActiveFilters && (
<div className="flex items-center gap-2 flex-wrap mt-2">
{filters.model !== 'all' && (
<span className="text-xs px-2 py-1 rounded bg-lyria-accent/10 text-lyria-accent border border-lyria-accent/20">
<span className="text-xs px-2 py-1 rounded bg-kithara-accent/10 text-kithara-accent border border-kithara-accent/20">
Model: {filters.model}
</span>
)}
{filters.status !== 'all' && (
<span className="text-xs px-2 py-1 rounded bg-lyria-accent/10 text-lyria-accent border border-lyria-accent/20">
<span className="text-xs px-2 py-1 rounded bg-kithara-accent/10 text-kithara-accent border border-kithara-accent/20">
Status: {filters.status}
</span>
)}
{filters.hasArtwork !== 'all' && (
<span className="text-xs px-2 py-1 rounded bg-lyria-accent/10 text-lyria-accent border border-lyria-accent/20">
<span className="text-xs px-2 py-1 rounded bg-kithara-accent/10 text-kithara-accent border border-kithara-accent/20">
Artwork: {filters.hasArtwork}
</span>
)}
{(filters.bpmMin || filters.bpmMax) && (
<span className="text-xs px-2 py-1 rounded bg-lyria-accent/10 text-lyria-accent border border-lyria-accent/20">
<span className="text-xs px-2 py-1 rounded bg-kithara-accent/10 text-kithara-accent border border-kithara-accent/20">
BPM: {filters.bpmMin || '0'} - {filters.bpmMax || '∞'}
</span>
)}
{(filters.dateFrom || filters.dateTo) && (
<span className="text-xs px-2 py-1 rounded bg-lyria-accent/10 text-lyria-accent border border-lyria-accent/20">
<span className="text-xs px-2 py-1 rounded bg-kithara-accent/10 text-kithara-accent border border-kithara-accent/20">
Date: {filters.dateFrom || '...'} → {filters.dateTo || '...'}
</span>
)}
<button
onClick={clearFilters}
className="text-xs text-lyria-muted hover:text-white transition-colors"
className="text-xs text-kithara-muted hover:text-white transition-colors"
>
Clear all
</button>
@@ -391,7 +391,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
{/* Results count */}
{(search || hasActiveFilters) && (
<p className="text-xs text-lyria-muted mt-1.5">
<p className="text-xs text-kithara-muted mt-1.5">
Showing {filtered.length} of {clips.length} clips
</p>
)}
@@ -402,7 +402,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
<div className="px-5 py-4 border-b border-white/5 bg-white/5">
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
<div>
<label className="block text-xs text-lyria-muted mb-1">Model</label>
<label className="block text-xs text-kithara-muted mb-1">Model</label>
<select
value={filters.model}
onChange={(e) => setFilters(f => ({ ...f, model: e.target.value }))}
@@ -414,7 +414,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
</select>
</div>
<div>
<label className="block text-xs text-lyria-muted mb-1">Status</label>
<label className="block text-xs text-kithara-muted mb-1">Status</label>
<select
value={filters.status}
onChange={(e) => setFilters(f => ({ ...f, status: e.target.value }))}
@@ -427,7 +427,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
</select>
</div>
<div>
<label className="block text-xs text-lyria-muted mb-1">Artwork</label>
<label className="block text-xs text-kithara-muted mb-1">Artwork</label>
<select
value={filters.hasArtwork}
onChange={(e) => setFilters(f => ({ ...f, hasArtwork: e.target.value }))}
@@ -439,29 +439,29 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
</select>
</div>
<div>
<label className="block text-xs text-lyria-muted mb-1">BPM Range</label>
<label className="block text-xs text-kithara-muted mb-1">BPM Range</label>
<div className="flex items-center gap-2">
<input
type="number"
value={filters.bpmMin}
onChange={(e) => setFilters(f => ({ ...f, bpmMin: e.target.value }))}
placeholder="Min"
className="w-full bg-black/30 border border-white/10 rounded-md px-2 py-1.5 text-sm text-white placeholder:text-lyria-muted/30"
className="w-full bg-black/30 border border-white/10 rounded-md px-2 py-1.5 text-sm text-white placeholder:text-kithara-muted/30"
/>
<span className="text-lyria-muted">-</span>
<span className="text-kithara-muted">-</span>
<input
type="number"
value={filters.bpmMax}
onChange={(e) => setFilters(f => ({ ...f, bpmMax: e.target.value }))}
placeholder="Max"
className="w-full bg-black/30 border border-white/10 rounded-md px-2 py-1.5 text-sm text-white placeholder:text-lyria-muted/30"
className="w-full bg-black/30 border border-white/10 rounded-md px-2 py-1.5 text-sm text-white placeholder:text-kithara-muted/30"
/>
</div>
</div>
</div>
<div className="grid grid-cols-2 gap-3 mt-3">
<div>
<label className="block text-xs text-lyria-muted mb-1">Date From</label>
<label className="block text-xs text-kithara-muted mb-1">Date From</label>
<input
type="date"
value={filters.dateFrom}
@@ -470,7 +470,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
/>
</div>
<div>
<label className="block text-xs text-lyria-muted mb-1">Date To</label>
<label className="block text-xs text-kithara-muted mb-1">Date To</label>
<input
type="date"
value={filters.dateTo}
@@ -482,13 +482,13 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
<div className="flex items-center justify-between mt-3">
<button
onClick={clearFilters}
className="text-xs text-lyria-muted hover:text-white transition-colors"
className="text-xs text-kithara-muted hover:text-white transition-colors"
>
Reset all filters
</button>
<button
onClick={saveSearch}
className="text-xs text-lyria-accent hover:text-lyria-accent/80 transition-colors"
className="text-xs text-kithara-accent hover:text-kithara-accent/80 transition-colors"
>
Save this search 💾
</button>
@@ -499,19 +499,19 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
{/* Saved searches dropdown */}
{showSaved && savedSearches.length > 0 && (
<div className="px-5 py-3 border-b border-white/5 bg-white/5 space-y-2">
<p className="text-xs text-lyria-muted uppercase tracking-wider">Saved Searches</p>
<p className="text-xs text-kithara-muted uppercase tracking-wider">Saved Searches</p>
{savedSearches.map(saved => (
<div key={saved.id} className="flex items-center justify-between group">
<button
onClick={() => loadSearch(saved)}
className="text-sm text-white hover:text-lyria-accent transition-colors"
className="text-sm text-white hover:text-kithara-accent transition-colors"
>
{saved.name}
{saved.query && <span className="text-lyria-muted ml-1">"{saved.query}"</span>}
{saved.query && <span className="text-kithara-muted ml-1">"{saved.query}"</span>}
</button>
<button
onClick={() => deleteSearch(saved.id)}
className="text-lyria-muted hover:text-red-400 opacity-0 group-hover:opacity-100 transition-opacity"
className="text-kithara-muted hover:text-red-400 opacity-0 group-hover:opacity-100 transition-opacity"
>
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
@@ -525,7 +525,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
{/* Clip list */}
{filtered.length === 0 ? (
<div className="px-5 py-8 text-center">
<p className="text-lyria-muted text-sm">No clips match your search</p>
<p className="text-kithara-muted text-sm">No clips match your search</p>
</div>
) : view === 'ribbons' ? (
/* ── RIBBON VIEW ── */
@@ -546,7 +546,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
className="w-10 h-10 rounded object-cover"
/>
) : (
<div className="w-10 h-10 rounded bg-white/5 flex items-center justify-center text-lyria-accent/30">
<div className="w-10 h-10 rounded bg-white/5 flex items-center justify-center text-kithara-accent/30">
<svg className="w-5 h-5" viewBox="0 0 24 24">
<text x="12" y="19" textAnchor="middle" fontSize="20" fill="currentColor">𝄞</text>
</svg>
@@ -556,7 +556,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
>
<button
onClick={(e) => { e.stopPropagation(); onPlay(clip) }}
className="text-white hover:text-lyria-accent transition-colors"
className="text-white hover:text-kithara-accent transition-colors"
title="Play"
>
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
@@ -579,7 +579,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
}}
onBlur={commitRename}
placeholder="Enter a name..."
className="w-full bg-white/5 border border-lyria-accent/50 rounded px-2 py-1 text-sm text-white placeholder-lyria-muted focus:outline-none focus:ring-1 focus:ring-lyria-accent/50"
className="w-full bg-white/5 border border-kithara-accent/50 rounded px-2 py-1 text-sm text-white placeholder-kithara-muted focus:outline-none focus:ring-1 focus:ring-kithara-accent/50"
autoFocus
/>
) : clip.title ? (
@@ -587,7 +587,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
<p className="text-sm font-medium text-white truncate">
<HighlightText text={clip.title} query={debouncedSearch} />
</p>
<p className="text-xs text-lyria-muted/60 truncate">
<p className="text-xs text-kithara-muted/60 truncate">
<HighlightText text={clip.prompt} query={debouncedSearch} />
</p>
</div>
@@ -606,9 +606,9 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
}`}>
{clip.status}
</span>
<span className="text-xs text-lyria-muted">{clip.duration_seconds}s</span>
<span className="text-xs text-lyria-muted">{modelName(clip.model)}</span>
<span className="text-xs text-lyria-muted">{formatDate(clip.created_at)}</span>
<span className="text-xs text-kithara-muted">{clip.duration_seconds}s</span>
<span className="text-xs text-kithara-muted">{modelName(clip.model)}</span>
<span className="text-xs text-kithara-muted">{formatDate(clip.created_at)}</span>
{clip.bpm && (() => {
const c = getBPMColor(clip.bpm)
return c ? (
@@ -645,7 +645,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
<button
onClick={(e) => { e.stopPropagation(); startRename(clip) }}
className="text-lyria-muted/60 hover:text-white transition-colors shrink-0"
className="text-kithara-muted/60 hover:text-white transition-colors shrink-0"
title="Rename clip"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -658,7 +658,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
setUploadingId(clip.id)
setTimeout(() => artworkInputRef.current?.click(), 0)
}}
className="text-lyria-muted/60 hover:text-white transition-colors shrink-0"
className="text-kithara-muted/60 hover:text-white transition-colors shrink-0"
title="Upload album art"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -670,7 +670,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
e.stopPropagation()
window.open(`/api/clips/${clip.id}/download`, '_blank')
}}
className="text-lyria-muted hover:text-lyria-accent transition-colors shrink-0"
className="text-kithara-muted hover:text-kithara-accent transition-colors shrink-0"
title="Download MP3"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -679,7 +679,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
</button>
<button
onClick={(e) => { e.stopPropagation(); onDelete(clip.id) }}
className="text-lyria-muted hover:text-red-400 transition-colors shrink-0"
className="text-kithara-muted hover:text-red-400 transition-colors shrink-0"
title="Delete clip"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -695,8 +695,8 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
{paged.map((clip) => (
<div
key={clip.id}
className={`bg-lyria-dark rounded-lg border border-white/5 p-3 hover:border-lyria-accent/30 transition-colors flex flex-col cursor-pointer ${
currentClipId === clip.id ? 'border-lyria-accent/50' : ''
className={`bg-kithara-dark rounded-lg border border-white/5 p-3 hover:border-kithara-accent/30 transition-colors flex flex-col cursor-pointer ${
currentClipId === clip.id ? 'border-kithara-accent/50' : ''
}`}
onClick={() => onSelect(clip)}
>
@@ -708,7 +708,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
className="w-full aspect-square rounded-md object-cover"
/>
) : (
<div className="w-full aspect-square bg-white/5 rounded-md flex items-center justify-center text-lyria-accent/30">
<div className="w-full aspect-square bg-white/5 rounded-md flex items-center justify-center text-kithara-accent/30">
<svg className="w-10 h-10" viewBox="0 0 24 24">
<text x="12" y="19" textAnchor="middle" fontSize="20" fill="currentColor">𝄞</text>
</svg>
@@ -717,7 +717,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
<div className="absolute inset-0 rounded-md bg-black/60 flex items-center justify-center gap-2 opacity-0 group-hover/art:opacity-100 transition-opacity">
<button
onClick={(e) => { e.stopPropagation(); onPlay(clip) }}
className="text-white hover:text-lyria-accent transition-colors"
className="text-white hover:text-kithara-accent transition-colors"
title="Play"
>
<svg className="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
@@ -730,7 +730,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
setUploadingId(clip.id)
setTimeout(() => artworkInputRef.current?.click(), 0)
}}
className="text-white/80 hover:text-lyria-accent transition-colors"
className="text-white/80 hover:text-kithara-accent transition-colors"
title="Upload album art"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -752,7 +752,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
}}
onBlur={commitRename}
placeholder="Enter a name..."
className="w-full bg-white/5 border border-lyria-accent/50 rounded px-2 py-1 text-xs text-white placeholder-lyria-muted focus:outline-none focus:ring-1 focus:ring-lyria-accent/50 mb-1"
className="w-full bg-white/5 border border-kithara-accent/50 rounded px-2 py-1 text-xs text-white placeholder-kithara-muted focus:outline-none focus:ring-1 focus:ring-kithara-accent/50 mb-1"
autoFocus
/>
) : (
@@ -771,8 +771,8 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
}`}>
{clip.status}
</span>
<span className="text-[10px] text-lyria-muted">{clip.duration_seconds}s</span>
<span className="text-[10px] text-lyria-muted">{modelName(clip.model)}</span>
<span className="text-[10px] text-kithara-muted">{clip.duration_seconds}s</span>
<span className="text-[10px] text-kithara-muted">{modelName(clip.model)}</span>
{clip.bpm && (() => {
const c = getBPMColor(clip.bpm)
return c ? (
@@ -810,7 +810,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
<div className="flex items-center gap-1 mt-2 pt-1.5 border-t border-white/5">
<button
onClick={(e) => { e.stopPropagation(); startRename(clip) }}
className="text-lyria-muted/60 hover:text-white transition-colors text-xs"
className="text-kithara-muted/60 hover:text-white transition-colors text-xs"
title="Rename"
>
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -822,7 +822,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
e.stopPropagation()
window.open(`/api/clips/${clip.id}/download`, '_blank')
}}
className="text-lyria-muted hover:text-lyria-accent transition-colors text-xs"
className="text-kithara-muted hover:text-kithara-accent transition-colors text-xs"
title="Download MP3"
>
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -831,7 +831,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
</button>
<button
onClick={(e) => { e.stopPropagation(); onDelete(clip.id) }}
className="text-lyria-muted hover:text-red-400 transition-colors text-xs"
className="text-kithara-muted hover:text-red-400 transition-colors text-xs"
title="Delete"
>
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -847,7 +847,7 @@ export default function Gallery({ clips, onPlay, onDelete, onRename, onSelect, o
{/* Pagination */}
{totalPages > 1 && (
<div className="px-5 py-3 border-t border-white/5 flex items-center justify-between">
<p className="text-xs text-lyria-muted">
<p className="text-xs text-kithara-muted">
Page {page} of {totalPages} ({filtered.length} clip{filtered.length !== 1 ? 's' : ''})
</p>
<div className="flex items-center gap-2">
+16 -16
View File
@@ -107,7 +107,7 @@ export default function Generator({ onGenerate }: Props) {
}
return (
<div className="bg-lyria-panel rounded-lg p-5 border border-white/5">
<div className="bg-kithara-panel rounded-lg p-5 border border-white/5">
<h2 className="text-sm font-semibold text-white mb-4 uppercase tracking-wider">
Generate
</h2>
@@ -116,13 +116,13 @@ export default function Generator({ onGenerate }: Props) {
{/* Model & Duration */}
<div className="flex gap-4">
<div className="flex-1">
<label className="block text-xs text-lyria-muted mb-1.5">
<label className="block text-xs text-kithara-muted mb-1.5">
Model
</label>
<select
value={model}
onChange={(e) => handleModelChange(e.target.value)}
className="w-full bg-lyria-dark border border-white/10 rounded-md px-3 py-2 text-sm text-white focus:outline-none focus:border-lyria-accent"
className="w-full bg-kithara-dark border border-white/10 rounded-md px-3 py-2 text-sm text-white focus:outline-none focus:border-kithara-accent"
>
{Object.entries(models).map(([key, info]) => (
<option key={key} value={key}>
@@ -133,7 +133,7 @@ export default function Generator({ onGenerate }: Props) {
</div>
<div className="w-28">
<label className="block text-xs text-lyria-muted mb-1.5">
<label className="block text-xs text-kithara-muted mb-1.5">
Duration ({duration}s)
</label>
<input
@@ -142,12 +142,12 @@ export default function Generator({ onGenerate }: Props) {
max={maxDuration}
value={duration}
onChange={(e) => setDuration(Number(e.target.value))}
className="w-full mt-2 accent-lyria-accent"
className="w-full mt-2 accent-kithara-accent"
/>
</div>
</div>
<div className="flex items-center justify-between text-xs text-lyria-muted">
<div className="flex items-center justify-between text-xs text-kithara-muted">
<span>{currentModelInfo?.name || 'Clip Preview'}</span>
<span>{duration}s / {maxDuration}s max</span>
</div>
@@ -160,8 +160,8 @@ export default function Generator({ onGenerate }: Props) {
onClick={() => setPromptMode('basic')}
className={`flex-1 py-1.5 text-xs font-medium rounded-md transition-colors ${
promptMode === 'basic'
? 'bg-lyria-accent text-black'
: 'text-lyria-muted hover:text-white'
? 'bg-kithara-accent text-black'
: 'text-kithara-muted hover:text-white'
}`}
>
Basic
@@ -171,8 +171,8 @@ export default function Generator({ onGenerate }: Props) {
onClick={() => setPromptMode('advanced')}
className={`flex-1 py-1.5 text-xs font-medium rounded-md transition-colors ${
promptMode === 'advanced'
? 'bg-lyria-accent text-black'
: 'text-lyria-muted hover:text-white'
? 'bg-kithara-accent text-black'
: 'text-kithara-muted hover:text-white'
}`}
>
Advanced
@@ -184,12 +184,12 @@ export default function Generator({ onGenerate }: Props) {
{(promptMode === 'basic' || !isPro) && (
<div>
<div className="flex items-center justify-between mb-1.5">
<label className="text-xs text-lyria-muted">Prompt</label>
<label className="text-xs text-kithara-muted">Prompt</label>
<button
type="button"
onClick={() => setSuggestionsOpen(true)}
disabled={!prompt.trim()}
className="text-xs text-lyria-accent hover:text-lyria-accent/80 transition-colors disabled:opacity-40"
className="text-xs text-kithara-accent hover:text-kithara-accent/80 transition-colors disabled:opacity-40"
>
Get Suggestions ✨
</button>
@@ -198,7 +198,7 @@ export default function Generator({ onGenerate }: Props) {
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="Dark distorted kickdrum with atmospheric synth pads, 180 BPM..."
className="w-full h-24 bg-lyria-dark border border-white/10 rounded-md px-3 py-2 text-sm text-white placeholder:text-lyria-muted/50 focus:outline-none focus:border-lyria-accent resize-none"
className="w-full h-24 bg-kithara-dark border border-white/10 rounded-md px-3 py-2 text-sm text-white placeholder:text-kithara-muted/50 focus:outline-none focus:border-kithara-accent resize-none"
/>
</div>
)}
@@ -207,12 +207,12 @@ export default function Generator({ onGenerate }: Props) {
{promptMode === 'advanced' && isPro && (
<div>
<div className="flex items-center justify-between mb-1.5">
<label className="text-xs text-lyria-muted">Structured Prompt</label>
<label className="text-xs text-kithara-muted">Structured Prompt</label>
<button
type="button"
onClick={() => setSuggestionsOpen(true)}
disabled={Object.values(structuredPrompt).every(v => !v?.trim())}
className="text-xs text-lyria-accent hover:text-lyria-accent/80 transition-colors disabled:opacity-40"
className="text-xs text-kithara-accent hover:text-kithara-accent/80 transition-colors disabled:opacity-40"
>
Get Suggestions ✨
</button>
@@ -231,7 +231,7 @@ export default function Generator({ onGenerate }: Props) {
<button
type="submit"
disabled={isLoading || (promptMode === 'basic' ? !prompt.trim() : Object.values(structuredPrompt).every(v => !v?.trim()))}
className="w-full py-2.5 bg-lyria-accent text-white text-sm font-medium rounded-md hover:bg-lyria-accent/90 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
className="w-full py-2.5 bg-kithara-accent text-white text-sm font-medium rounded-md hover:bg-kithara-accent/90 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
>
{isLoading ? 'Generating...' : 'Generate'}
</button>
+13 -13
View File
@@ -167,12 +167,12 @@ export default function Player({ clip, isPlaying, onTogglePlay, onStop, onClose
return (
<>
<div className="fixed bottom-0 left-0 right-0 bg-lyria-panel border-t border-white/10 px-4 py-3 z-40">
<div className="fixed bottom-0 left-0 right-0 bg-kithara-panel border-t border-white/10 px-4 py-3 z-40">
<div className="max-w-6xl mx-auto flex items-center gap-3">
{/* Play/Pause */}
<button
onClick={onTogglePlay}
className="w-9 h-9 rounded-full bg-lyria-accent flex items-center justify-center text-white hover:bg-lyria-accent/90 transition-colors shrink-0"
className="w-9 h-9 rounded-full bg-kithara-accent flex items-center justify-center text-white hover:bg-kithara-accent/90 transition-colors shrink-0"
title={isPlaying ? 'Pause' : 'Play'}
>
{isPlaying ? (
@@ -189,7 +189,7 @@ export default function Player({ clip, isPlaying, onTogglePlay, onStop, onClose
{/* Stop */}
<button
onClick={handleStop}
className="w-7 h-7 rounded flex items-center justify-center text-lyria-muted hover:text-white hover:bg-white/5 transition-colors shrink-0"
className="w-7 h-7 rounded flex items-center justify-center text-kithara-muted hover:text-white hover:bg-white/5 transition-colors shrink-0"
title="Stop"
>
<svg className="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24">
@@ -200,7 +200,7 @@ export default function Player({ clip, isPlaying, onTogglePlay, onStop, onClose
{/* Restart — ISO skip backwards to beginning */}
<button
onClick={handleRestart}
className="w-7 h-7 rounded flex items-center justify-center text-lyria-muted hover:text-white hover:bg-white/5 transition-colors shrink-0"
className="w-7 h-7 rounded flex items-center justify-center text-kithara-muted hover:text-white hover:bg-white/5 transition-colors shrink-0"
title="Skip to beginning"
>
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
@@ -213,8 +213,8 @@ export default function Player({ clip, isPlaying, onTogglePlay, onStop, onClose
onClick={toggleRepeat}
className={`w-7 h-7 rounded flex items-center justify-center transition-colors shrink-0 ${
isRepeating
? 'text-lyria-accent bg-lyria-accent/10 shadow-[0_0_8px_rgba(255,107,53,0.4)]'
: 'text-lyria-muted hover:text-white hover:bg-white/5'
? 'text-kithara-accent bg-kithara-accent/10 shadow-[0_0_8px_rgba(255,107,53,0.4)]'
: 'text-kithara-muted hover:text-white hover:bg-white/5'
}`}
title={isRepeating ? 'Repeat on' : 'Repeat off'}
>
@@ -225,7 +225,7 @@ export default function Player({ clip, isPlaying, onTogglePlay, onStop, onClose
{/* Time + Scrubber */}
<div className="flex-1 min-w-0 flex items-center gap-3">
<span className="text-xs text-lyria-muted tabular-nums shrink-0 w-10 text-right">
<span className="text-xs text-kithara-muted tabular-nums shrink-0 w-10 text-right">
{formatTime(currentTime)}
</span>
@@ -237,7 +237,7 @@ export default function Player({ clip, isPlaying, onTogglePlay, onStop, onClose
onMouseUp={handleProgressMouseUp}
>
<div
className="absolute inset-y-0 left-0 bg-lyria-accent rounded-full transition-all duration-75"
className="absolute inset-y-0 left-0 bg-kithara-accent rounded-full transition-all duration-75"
style={{ width: `${progress}%` }}
/>
{/* Scrubber handle */}
@@ -247,7 +247,7 @@ export default function Player({ clip, isPlaying, onTogglePlay, onStop, onClose
/>
</div>
<span className="text-xs text-lyria-muted tabular-nums shrink-0 w-10">
<span className="text-xs text-kithara-muted tabular-nums shrink-0 w-10">
{formatTime(duration)}
</span>
</div>
@@ -256,7 +256,7 @@ export default function Player({ clip, isPlaying, onTogglePlay, onStop, onClose
<div className="hidden sm:flex items-center gap-2.5 min-w-0 max-w-[220px] shrink-0">
<button
onClick={() => window.open(`/api/clips/${clip.id}/download`, '_blank')}
className="text-lyria-muted/60 hover:text-lyria-accent transition-colors shrink-0 mr-2"
className="text-kithara-muted/60 hover:text-kithara-accent transition-colors shrink-0 mr-2"
title="Download MP3"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -274,7 +274,7 @@ export default function Player({ clip, isPlaying, onTogglePlay, onStop, onClose
target.style.display = 'none'
const parent = target.parentElement
if (parent) {
parent.innerHTML = `<svg class='w-4 h-4 text-lyria-accent/40' viewBox='0 0 24 24'><text x='12' y='19' text-anchor='middle' font-size='16' fill='currentColor'>𝄞</text></svg>`
parent.innerHTML = `<svg class='w-4 h-4 text-kithara-accent/40' viewBox='0 0 24 24'><text x='12' y='19' text-anchor='middle' font-size='16' fill='currentColor'>𝄞</text></svg>`
}
}}
/>
@@ -296,7 +296,7 @@ export default function Player({ clip, isPlaying, onTogglePlay, onStop, onClose
<div className="flex items-center gap-2 shrink-0">
<button
onClick={toggleMute}
className="text-lyria-muted hover:text-white transition-colors"
className="text-kithara-muted hover:text-white transition-colors"
title={isMuted ? 'Unmute' : 'Mute'}
>
{isMuted || volume === 0 ? (
@@ -332,7 +332,7 @@ export default function Player({ clip, isPlaying, onTogglePlay, onStop, onClose
{/* Close */}
<button
onClick={onClose}
className="text-lyria-muted hover:text-white transition-colors shrink-0"
className="text-kithara-muted hover:text-white transition-colors shrink-0"
title="Close player"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+14 -14
View File
@@ -164,7 +164,7 @@ export default function SettingsModal({ isOpen, onClose, onClearWaveforms, onDel
<span className="text-sm font-medium text-white">{title}</span>
</div>
<svg
className={`w-4 h-4 text-lyria-muted transition-transform ${gen.expanded ? 'rotate-180' : ''}`}
className={`w-4 h-4 text-kithara-muted transition-transform ${gen.expanded ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
@@ -177,9 +177,9 @@ export default function SettingsModal({ isOpen, onClose, onClearWaveforms, onDel
<div className="p-4 space-y-4">
{/* Model Selection */}
<div>
<label className="block text-xs text-lyria-muted mb-1.5">Model</label>
<label className="block text-xs text-kithara-muted mb-1.5">Model</label>
<select
className="w-full bg-black/30 border border-white/10 rounded-md px-3 py-2 text-sm text-white focus:outline-none focus:border-lyria-accent"
className="w-full bg-black/30 border border-white/10 rounded-md px-3 py-2 text-sm text-white focus:outline-none focus:border-kithara-accent"
value={gen.getValue('model')}
onChange={(e) => gen.update('model', e.target.value)}
>
@@ -194,7 +194,7 @@ export default function SettingsModal({ isOpen, onClose, onClearWaveforms, onDel
{/* API Key Status */}
<div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${gen.config?.has_api_key ? 'bg-green-400' : 'bg-red-400'}`} />
<span className="text-xs text-lyria-muted">
<span className="text-xs text-kithara-muted">
{gen.config?.has_api_key ? 'API key configured' : 'No API key — set in .env'}
</span>
</div>
@@ -209,9 +209,9 @@ export default function SettingsModal({ isOpen, onClose, onClearWaveforms, onDel
{ key: 'safety_dangerous', label: 'Dangerous Content' },
].map(({ key, label }) => (
<div key={key}>
<label className="block text-xs text-lyria-muted mb-1">{label}</label>
<label className="block text-xs text-kithara-muted mb-1">{label}</label>
<select
className="w-full bg-black/30 border border-white/10 rounded-md px-3 py-1.5 text-sm text-white focus:outline-none focus:border-lyria-accent"
className="w-full bg-black/30 border border-white/10 rounded-md px-3 py-1.5 text-sm text-white focus:outline-none focus:border-kithara-accent"
value={gen.getValue(key as keyof GenConfig)}
onChange={(e) => gen.update(key, e.target.value)}
>
@@ -228,7 +228,7 @@ export default function SettingsModal({ isOpen, onClose, onClearWaveforms, onDel
<button
onClick={gen.save}
disabled={!gen.hasChanges || gen.saving}
className="px-4 py-2 text-sm font-medium rounded-md transition-colors disabled:opacity-50 bg-lyria-accent text-black hover:bg-lyria-accent/90"
className="px-4 py-2 text-sm font-medium rounded-md transition-colors disabled:opacity-50 bg-kithara-accent text-black hover:bg-kithara-accent/90"
>
{gen.saving ? 'Saving...' : 'Save Changes'}
</button>
@@ -238,7 +238,7 @@ export default function SettingsModal({ isOpen, onClose, onClearWaveforms, onDel
</span>
)}
</div>
<p className="text-xs text-lyria-muted/60">
<p className="text-xs text-kithara-muted/60">
Changes apply immediately but reset on server restart. Set .env for persistence.
</p>
</div>
@@ -255,13 +255,13 @@ export default function SettingsModal({ isOpen, onClose, onClearWaveforms, onDel
/>
{/* Modal */}
<div className="relative bg-lyria-surface border border-white/10 rounded-xl shadow-2xl w-full max-w-md mx-4 overflow-hidden max-h-[80vh] flex flex-col">
<div className="relative bg-kithara-surface border border-white/10 rounded-xl shadow-2xl w-full max-w-md mx-4 overflow-hidden max-h-[80vh] flex flex-col">
{/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b border-white/10 shrink-0">
<h2 className="text-lg font-semibold text-white">Settings</h2>
<button
onClick={onClose}
className="text-lyria-muted hover:text-white transition-colors"
className="text-kithara-muted hover:text-white transition-colors"
aria-label="Close settings"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -274,14 +274,14 @@ export default function SettingsModal({ isOpen, onClose, onClearWaveforms, onDel
<div className="px-6 py-5 space-y-6 overflow-y-auto">
{/* Image Generation */}
{renderGenSection('Image Generation', (
<svg className="w-4 h-4 text-lyria-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="w-4 h-4 text-kithara-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
), imageGen)}
{/* Text Generation */}
{renderGenSection('Text Generation', (
<svg className="w-4 h-4 text-lyria-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="w-4 h-4 text-kithara-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" />
</svg>
), textGen)}
@@ -314,7 +314,7 @@ export default function SettingsModal({ isOpen, onClose, onClearWaveforms, onDel
<div className="flex items-start gap-4">
<div className="flex-1 min-w-0">
<p className="text-sm text-white font-medium">Clear Waveform Cache</p>
<p className="text-xs text-lyria-muted mt-0.5">Delete all cached SVGs. They regenerate on next view.</p>
<p className="text-xs text-kithara-muted mt-0.5">Delete all cached SVGs. They regenerate on next view.</p>
</div>
<button
onClick={handleClearWaveforms}
@@ -331,7 +331,7 @@ export default function SettingsModal({ isOpen, onClose, onClearWaveforms, onDel
<div className="flex items-start gap-4">
<div className="flex-1 min-w-0">
<p className="text-sm text-white font-medium">Delete All Tracks</p>
<p className="text-xs text-lyria-muted mt-0.5">
<p className="text-xs text-kithara-muted mt-0.5">
Remove {clipCount} clip{clipCount !== 1 ? 's' : ''}. Cannot be undone.
</p>
{confirmDelete && (
@@ -51,12 +51,12 @@ export default function StructuredPromptEditor({ value, onChange }: Props) {
return (
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-xs text-lyria-muted">
<span className="text-xs text-kithara-muted">
{filledCount} of {FIELDS.length} fields filled
</span>
<button
onClick={() => setExpanded(new Set(FIELDS.map(f => f.key)))}
className="text-xs text-lyria-accent hover:text-lyria-accent/80 transition-colors"
className="text-xs text-kithara-accent hover:text-kithara-accent/80 transition-colors"
>
Expand All
</button>
@@ -75,11 +75,11 @@ export default function StructuredPromptEditor({ value, onChange }: Props) {
}`}
>
<div className="flex items-center gap-2">
<span className={`w-1.5 h-1.5 rounded-full ${hasValue ? 'bg-lyria-accent' : 'bg-lyria-muted/30'}`} />
<span className={hasValue ? 'text-white' : 'text-lyria-muted'}>{label}</span>
<span className={`w-1.5 h-1.5 rounded-full ${hasValue ? 'bg-kithara-accent' : 'bg-kithara-muted/30'}`} />
<span className={hasValue ? 'text-white' : 'text-kithara-muted'}>{label}</span>
</div>
<svg
className={`w-3.5 h-3.5 text-lyria-muted transition-transform ${isExpanded ? 'rotate-180' : ''}`}
className={`w-3.5 h-3.5 text-kithara-muted transition-transform ${isExpanded ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
@@ -94,7 +94,7 @@ export default function StructuredPromptEditor({ value, onChange }: Props) {
value={value[key as keyof PromptStructure] || ''}
onChange={(e) => updateField(key, e.target.value)}
placeholder={placeholder}
className="w-full h-20 bg-black/20 border border-white/5 rounded-md px-3 py-2 text-sm text-white placeholder:text-lyria-muted/30 focus:outline-none focus:border-lyria-accent/50 resize-none"
className="w-full h-20 bg-black/20 border border-white/5 rounded-md px-3 py-2 text-sm text-white placeholder:text-kithara-muted/30 focus:outline-none focus:border-kithara-accent/50 resize-none"
/>
</div>
)}
+10 -10
View File
@@ -53,16 +53,16 @@ export default function SuggestionsModal({ isOpen, onClose, onSelect, userInput
onClick={onClose}
/>
<div className="relative bg-lyria-surface border border-white/10 rounded-xl shadow-2xl w-full max-w-lg mx-4 overflow-hidden max-h-[80vh] flex flex-col">
<div className="relative bg-kithara-surface border border-white/10 rounded-xl shadow-2xl w-full max-w-lg mx-4 overflow-hidden max-h-[80vh] flex flex-col">
{/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b border-white/10 shrink-0">
<div>
<h2 className="text-lg font-semibold text-white">Prompt Suggestions</h2>
<p className="text-xs text-lyria-muted mt-0.5 truncate max-w-[280px]">{userInput}</p>
<p className="text-xs text-kithara-muted mt-0.5 truncate max-w-[280px]">{userInput}</p>
</div>
<button
onClick={onClose}
className="text-lyria-muted hover:text-white transition-colors"
className="text-kithara-muted hover:text-white transition-colors"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
@@ -74,7 +74,7 @@ export default function SuggestionsModal({ isOpen, onClose, onSelect, userInput
<div className="px-6 py-5 overflow-y-auto">
{loading && (
<div className="flex items-center justify-center py-8">
<svg className="animate-spin w-6 h-6 text-lyria-accent" fill="none" viewBox="0 0 24 24">
<svg className="animate-spin w-6 h-6 text-kithara-accent" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
@@ -86,7 +86,7 @@ export default function SuggestionsModal({ isOpen, onClose, onSelect, userInput
<p className="text-sm text-red-400">{error}</p>
<button
onClick={fetchSuggestions}
className="mt-2 text-xs text-lyria-accent hover:text-lyria-accent/80"
className="mt-2 text-xs text-kithara-accent hover:text-kithara-accent/80"
>
Try again
</button>
@@ -95,10 +95,10 @@ export default function SuggestionsModal({ isOpen, onClose, onSelect, userInput
{!loading && !error && suggestions.length === 0 && (
<div className="text-center py-8">
<p className="text-sm text-lyria-muted">No suggestions yet. Click below to generate.</p>
<p className="text-sm text-kithara-muted">No suggestions yet. Click below to generate.</p>
<button
onClick={fetchSuggestions}
className="mt-3 px-4 py-2 text-sm bg-lyria-accent text-black rounded-md hover:bg-lyria-accent/90"
className="mt-3 px-4 py-2 text-sm bg-kithara-accent text-black rounded-md hover:bg-kithara-accent/90"
>
Get Suggestions
</button>
@@ -113,13 +113,13 @@ export default function SuggestionsModal({ isOpen, onClose, onSelect, userInput
onSelect(suggestion)
onClose()
}}
className="w-full text-left p-4 bg-white/5 hover:bg-white/10 border border-white/5 hover:border-lyria-accent/30 rounded-lg transition-all group"
className="w-full text-left p-4 bg-white/5 hover:bg-white/10 border border-white/5 hover:border-kithara-accent/30 rounded-lg transition-all group"
>
<div className="flex items-start gap-3">
<span className="text-lyria-accent font-mono text-sm shrink-0">{i + 1}.</span>
<span className="text-kithara-accent font-mono text-sm shrink-0">{i + 1}.</span>
<p className="text-sm text-white/90 leading-relaxed">{suggestion}</p>
</div>
<div className="mt-2 text-xs text-lyria-accent opacity-0 group-hover:opacity-100 transition-opacity">
<div className="mt-2 text-xs text-kithara-accent opacity-0 group-hover:opacity-100 transition-opacity">
Click to use this prompt →
</div>
</button>