1import { r as reactExports, j as jsxRuntimeExports, R as React } from "./vendor-react-Bay9Y7Lb.js"; 2import { a as api } from "./index-D0Bmh-UX.js"; 3import { s as synth, p as parseMidiInstruments } from "./synth-BE1TSTD4.js"; 4import { b as Drawer, a as DataTable } from "./AdminShared-P_hor2hS.js"; 5import { h as formatFileSize, g as getColorsFromHash } from "./utils-BYCVoSTN.js"; 6import { fR as Speaker, aW as X, bV as RefreshCcw, bv as CircleCheckBig, cL as RefreshCw, b2 as Upload, cj as Layers, bQ as ExternalLink, aZ as Mail, a$ as Shield, b3 as FileHeadphone, d8 as Trash, eu as Terminal, cB as ChevronDown, b1 as CircleAlert, d6 as SquarePen, cp as Settings2, ch as Database, bA as Eye, cf as Activity, dg as Square, bU as Play, fS as Brain, cA as ChevronUp } from "./vendor-dVNaLJx_.js"; 7import { T as TagAutocomplete, u as useWaveform, W as Waveform } from "./Waveform-MxtdejK7.js"; 8import { U as UserAvatar } from "./UserAvatar-BQN4jlY-.js"; 9import { G as GlobeFlag } from "./GlobeFlag-V1uaxVEK.js"; 10import "./vendor-i18n-R-bMoYp_.js"; 11import "./vendor-react-three-MDLA5jog.js"; 12import "./wasm-synth-adapter-BrqgBX1y.js"; 13import "./vendor-spessasynth-DH8n6o_V.js"; 14import "./vendor-audio-DH3w0BzW.js"; 15import "./vendor-framer-C8eEgkdL.js"; 16const SoundFontModal = ({ isOpen, onClose }) => { 17 const [soundfonts, setSoundfonts] = reactExports.useState([]); 18 const [activeFont, setActiveFont] = reactExports.useState(null); 19 const [uploading, setUploading] = reactExports.useState(false); 20 reactExports.useEffect(() => { 21 if (isOpen) loadSoundfonts(); 22 }, [isOpen]); 23 const loadSoundfonts = async () => { 24 const res = await api.getSoundFonts(); 25 setSoundfonts(res.files || []); 26 setActiveFont(res.active); 27 }; 28 const handleUpload = async (e) => { 29 var _a; 30 if ((_a = e.target.files) == null ? void 0 : _a[0]) { 31 setUploading(true);
32 try { 33 await api.uploadSoundFont(e.target.files[0]); 34 await loadSoundfonts(); 35 } catch (e2) { 36 alert("Upload failed."); 37 } finally { 38 setUploading(false); 39 } 40 } 41 }; 42 const handleSetActive = async (file) => { 43 await api.setActiveSoundFont(file); 44 setActiveFont(file); 45 }; 46 if (!isOpen) return null; 47 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fixed inset-0 z-[100] flex items-center justify-center p-4", children: [ 48 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-sm", onClick: onClose }), 49 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full max-w-lg bg-white dark:bg-zinc-900 rounded-xl shadow-2xl p-6 border border-zinc-200 dark:border-zinc-800 animate-in zoom-in-95", children: [ 50 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-6", children: [ 51 /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-xl font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [
52 /* @__PURE__ */ jsxRuntimeExports.jsx(Speaker, { className: "text-indigo-500" }), 53 " SoundFont Manager" 54 ] }), 55 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 20, className: "text-zinc-500" }) }) 56 ] }), 57 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-50 dark:bg-zinc-800/50 p-4 rounded-lg border border-zinc-200 dark:border-zinc-800 mb-4 flex justify-between items-center", children: [ 58 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 59 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 mb-1", children: "Active SoundFont:" }), 60 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-sm font-bold text-zinc-900 dark:text-white truncate max-w-[250px]", children: activeFont || "Default (System setting)" }) 61 ] }), 62 activeFont && /* @__PURE__ */ jsxRuntimeExports.jsxs( 63 "button", 64 { 65 onClick: () => handleSetActive(null), 66 className: "text-xs text-indigo-600 dark:text-indigo-400 font-bold hover:underline flex items-center gap-1", 67 children: [
68 /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCcw, { size: 12 }), 69 " Reset to Default" 70 ] 71 } 72 ) 73 ] }), 74 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2 max-h-60 overflow-y-auto mb-4 custom-scrollbar pr-1", children: [ 75 soundfonts.map((sf) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center p-3 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-lg group transition-colors border border-transparent hover:border-zinc-200 dark:hover:border-zinc-700", children: [ 76 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col min-w-0", children: [ 77 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-medium text-zinc-900 dark:text-white truncate", children: sf.path.split("/").pop() }), 78 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] text-zinc-500 truncate font-mono", children: [ 79 sf.path, 80 " ⢠", 81 formatFileSize(sf.size) 82 ] }) 83 ] }), 84 activeFont === sf.path ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-emerald-500 text-xs font-bold flex items-center gap-1 shrink-0 ml-2", children: [
85 /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheckBig, { size: 14 }), 86 " Active" 87 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => handleSetActive(sf.path), className: "px-3 py-1 bg-zinc-200 dark:bg-zinc-700 hover:bg-indigo-100 dark:hover:bg-indigo-900/30 text-zinc-600 dark:text-zinc-300 hover:text-indigo-600 dark:hover:text-indigo-400 rounded text-xs font-bold opacity-0 group-hover:opacity-100 transition-all shrink-0 ml-2", children: "Set Active" }) 88 ] }, sf.path)), 89 soundfonts.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-center text-sm text-zinc-400 py-4", children: "No custom SoundFonts uploaded." }) 90 ] }), 91 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: `w-full py-3 border-2 border-dashed border-zinc-300 dark:border-zinc-700 rounded-lg flex items-center justify-center gap-2 text-sm font-bold text-zinc-500 hover:border-indigo-500 hover:text-indigo-500 cursor-pointer transition-colors ${uploading ? "opacity-50 pointer-events-none" : ""}`, children: [
92 uploading ? /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "animate-spin", size: 16 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { size: 16 }), 93 uploading ? "Uploading..." : "Upload .sf2 / .sfArk", 94 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "file", className: "hidden", accept: ".sf2,.sfArk", onChange: handleUpload }) 95 ] }), 96 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-zinc-400 mt-2 text-center", children: "Note: .sfArk files require additional conversion. .sf2 is recommended." }) 97 ] }) 98 ] }); 99}; 100const BulkReprocessModal = ({ onClose, onComplete }) => { 101 const [startId, setStartId] = reactExports.useState(0); 102 const [limit, setLimit] = reactExports.useState(100); 103 const [isProcessing, setIsProcessing] = reactExports.useState(false); 104 const handleExecute = async () => { 105 setIsProcessing(true); 106 try { 107 await api.reprocessTracks({ startId, limit }); 108 onComplete(); 109 } catch (e) { 110 alert("Failed to queue jobs."); 111 } finally { 112 setIsProcessing(false); 113 } 114 }; 115 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fixed inset-0 z-[100] flex items-center justify-center p-4", children: [ 116 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-sm", onClick: onClose }), 117 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full max-w-sm bg-white dark:bg-zinc-900 rounded-xl shadow-2xl p-6 border border-zinc-200 dark:border-zinc-800 animate-in zoom-in-95", children: [
118 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-4", children: [ 119 /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [ 120 /* @__PURE__ */ jsxRuntimeExports.jsx(Layers, { size: 18, className: "text-indigo-500" }), 121 " Bulk Reprocess" 122 ] }), 123 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 18, className: "text-zinc-500" }) }) 124 ] }), 125 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs text-zinc-600 dark:text-zinc-400 mb-6 bg-zinc-50 dark:bg-zinc-800/50 p-3 rounded-lg border border-zinc-100 dark:border-zinc-800", children: [ 126 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mb-2 font-bold text-zinc-800 dark:text-zinc-200", children: "This will fully regenerate:" }), 127 /* @__PURE__ */ jsxRuntimeExports.jsxs("ul", { className: "list-disc list-inside space-y-1 ml-1", children: [
128 /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: "Opus Audio & Waveforms" }), 129 /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: "Sheet Music (SVG)" }), 130 /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: "Piano Roll & Event Data (JSON)" }), 131 /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: "Track Metadata (Key, Difficulty)" }) 132 ] }), 133 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-2 opacity-75", children: "Processing relies on the original uploaded MIDI file." }) 134 ] }), 135 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [ 136 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 137 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-1", children: "Start MIDI ID" }), 138 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", value: startId, onChange: (e) => setStartId(parseInt(e.target.value)), className: "w-full bg-zinc-50 dark:bg-black border border-zinc-300 dark:border-zinc-700 rounded p-2 text-sm outline-none focus:ring-2 focus:ring-indigo-500 text-zinc-900 dark:text-white" }) 139 ] }),
140 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 141 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-1", children: "Limit (Batch Size)" }), 142 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", value: limit, onChange: (e) => setLimit(parseInt(e.target.value)), className: "w-full bg-zinc-50 dark:bg-black border border-zinc-300 dark:border-zinc-700 rounded p-2 text-sm outline-none focus:ring-2 focus:ring-indigo-500 text-zinc-900 dark:text-white" }) 143 ] }) 144 ] }), 145 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2 mt-6", children: [ 146 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "px-4 py-2 text-xs font-bold text-zinc-500 hover:text-zinc-900 dark:hover:text-white", children: "Cancel" }), 147 /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: handleExecute, disabled: isProcessing, className: "px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg text-xs font-bold flex items-center gap-2 disabled:opacity-50", children: [
148 isProcessing ? /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 14 }), 149 " Queue Jobs" 150 ] }) 151 ] }) 152 ] }) 153 ] }); 154}; 155const BatchEditModal = ({ trackIds, onClose, onComplete }) => { 156 const [updates, setUpdates] = reactExports.useState({ status: "", artist: "", tags: "" }); 157 const [isSaving, setIsSaving] = reactExports.useState(false); 158 const handleSave = async () => { 159 try { 160 setIsSaving(true); 161 const finalUpdates = {}; 162 if (updates.status) finalUpdates.status = updates.status; 163 if (updates.artist) finalUpdates.artist = updates.artist; 164 if (updates.tags) { 165 finalUpdates.tags = updates.tags.split(",").map((t) => t.trim()); 166 } 167 await api.bulkUpdateTrackMetadata(trackIds, finalUpdates); 168 onComplete(); 169 } catch (err) { 170 console.error(err); 171 alert(err.message || "Failed to save batch changes."); 172 } finally { 173 setIsSaving(false); 174 } 175 }; 176 const handleReprocessAudio = async () => { 177 if (!confirm(`Queue ${trackIds.length} tracks for full regeneration (Audio, Sheet, JSON)?`)) return; 178 try { 179 setIsSaving(true); 180 await api.reprocessTracks({ ids: trackIds }); 181 onComplete(); 182 } catch (err) { 183 console.error(err); 184 alert(err.message || "Failed to reprocess tracks."); 185 } finally { 186 setIsSaving(false); 187 } 188 }; 189 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fixed inset-0 z-[100] flex items-center justify-center p-4", children: [ 190 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-sm", onClick: onClose }), 191 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full max-w-lg bg-white dark:bg-zinc-900 rounded-xl shadow-2xl p-6 border border-zinc-200 dark:border-zinc-800 animate-in zoom-in-95", children: [
192 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "absolute top-4 right-4 p-2 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-full text-zinc-500", children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 20 }) }), 193 /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-xl font-bold mb-6 text-zinc-900 dark:text-white", children: [ 194 "Batch Edit ", 195 trackIds.length, 196 " Items" 197 ] }), 198 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4 mb-6", children: [ 199 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 200 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase", children: "Artist" }), 201 /* @__PURE__ */ jsxRuntimeExports.jsx(TagAutocomplete, { placeholder: "Leave blank to keep unchanged", value: updates.artist, onChange: (val) => setUpdates({ ...updates, artist: val }) }) 202 ] }), 203 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
204 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase", children: "Tags" }), 205 /* @__PURE__ */ jsxRuntimeExports.jsx(TagAutocomplete, { placeholder: "Leave blank to keep unchanged", value: updates.tags, onChange: (val) => setUpdates({ ...updates, tags: val }) }) 206 ] }), 207 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 208 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase", children: "Status" }), 209 /* @__PURE__ */ jsxRuntimeExports.jsxs("select", { className: "w-full bg-zinc-50 dark:bg-black border border-zinc-300 dark:border-zinc-700 rounded p-2 text-sm mt-1 text-zinc-900 dark:text-white", value: updates.status, onChange: (e) => setUpdates({ ...updates, status: e.target.value }), children: [ 210 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "-- Unchanged --" }), 211 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "completed", children: "Completed" }),
212 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "pending", children: "Queued" }), 213 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "rejected", children: "Rejected" }), 214 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "suspended", children: "Suspended" }) 215 ] }) 216 ] }) 217 ] }), 218 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-indigo-50 dark:bg-indigo-900/10 rounded-lg border border-indigo-100 dark:border-indigo-800 mb-6 flex items-center justify-between", children: [ 219 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 220 /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "font-bold text-sm text-indigo-900 dark:text-indigo-200", children: "Full Pipeline Reprocess" }), 221 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-indigo-700 dark:text-indigo-300", children: "Regenerate Audio, Sheets, and JSON Data." }) 222 ] }), 223 /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: handleReprocessAudio, disabled: isSaving, className: "px-3 py-1.5 bg-indigo-600 hover:bg-indigo-700 text-white rounded text-xs font-bold shadow-md flex items-center gap-2", children: [
224 /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 12, className: isSaving ? "animate-spin" : "" }), 225 " Reprocess" 226 ] }) 227 ] }), 228 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2 border-t border-zinc-200 dark:border-zinc-800 pt-4", children: [ 229 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "px-4 py-2 rounded-lg text-zinc-600 hover:bg-zinc-100 dark:text-zinc-400 dark:hover:bg-zinc-800 font-bold text-sm", children: "Cancel" }), 230 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: handleSave, disabled: isSaving, className: "px-4 py-2 rounded-lg bg-zinc-900 dark:bg-white text-white dark:text-black font-bold text-sm hover:opacity-90", children: isSaving ? "Saving..." : "Apply Metadata" }) 231 ] }) 232 ] }) 233 ] }); 234}; 235const MediaInspector = ({ track, onClose, onRefresh }) => { 236 var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k; 237 const [formData, setFormData] = reactExports.useState(track); 238 const [tagString, setTagString] = reactExports.useState(track.tags.map((t) => typeof t === "string" ? t : t.name).join(", ")); 239 const [saving, setSaving] = reactExports.useState(false); 240 const [edits, setEdits] = reactExports.useState([]); 241 const { waveform } = useWaveform(formData.id, formData.waveformJsonPath, formData.waveform); 242 React.useEffect(() => { 243 if (track && track.userId === -1) { 244 fetch(`/api/admin/tracks/${track.id}/edits`).then((res) => res.json()).then((data) => { 245 if (Array.isArray(data)) setEdits(data); 246 }).catch(console.error); 247 } 248 }, [track]); 249 if (!formData) return null; 250 const handleSave = async () => { 251 try { 252 setSaving(true); 253 await api.updateTrackMetadata(formData.id, { ...formData, tags: tagString.split(",").map((t) => t.trim()).filter(Boolean) }); 254 onRefresh(); 255 onClose(); 256 } catch (err) { 257 console.error(err); 258 alert(err.message || "Failed to save changes."); 259 } finally { 260 setSaving(false); 261 } 262 }; 263 const handleDelete = async () => { 264 if (confirm("Are you sure you want to delete this track? This action cannot be undone.")) { 265 await api.deleteTrack(formData.id); 266 onRefresh(); 267 onClose(); 268 } 269 }; 270 return /* @__PURE__ */ jsxRuntimeExports.jsx(Drawer, { isOpen: !!track, onClose, title: "Media Inspector", width: "max-w-5xl", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full", children: [ 271 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-8 pb-20", children: [
272 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [ 273 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 274 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-500 uppercase", children: "Title" }), 275 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { className: "w-full bg-zinc-100 dark:bg-zinc-800 border-none rounded p-2 mt-1 text-zinc-900 dark:text-white", value: formData.title, onChange: (e) => setFormData({ ...formData, title: e.target.value }) }) 276 ] }), 277 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 278 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-500 uppercase", children: "Artist" }), 279 /* @__PURE__ */ jsxRuntimeExports.jsx(TagAutocomplete, { value: formData.artist, onChange: (val) => setFormData({ ...formData, artist: val }), placeholder: "Andrew Lloyd Webber, Joe Hisaishi..." }) 280 ] }), 281 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
282 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-500 uppercase", children: "Description" }), 283 /* @__PURE__ */ jsxRuntimeExports.jsx("textarea", { className: "w-full bg-zinc-100 dark:bg-zinc-800 border-none rounded p-2 mt-1 h-32 resize-none text-zinc-900 dark:text-white", value: formData.description, onChange: (e) => setFormData({ ...formData, description: e.target.value }) }) 284 ] }), 285 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 286 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-500 uppercase", children: "Tags" }), 287 /* @__PURE__ */ jsxRuntimeExports.jsx(TagAutocomplete, { value: tagString, onChange: setTagString, placeholder: "piano, virtuoso, 8-bit" }) 288 ] }), 289 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-zinc-100 dark:bg-zinc-800 rounded-lg", children: [ 290 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-500 uppercase mb-3 block", children: "Uploader Information" }),
291 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [ 292 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [ 293 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(UserAvatar, { src: (_a = formData.uploader) == null ? void 0 : _a.avatar, name: ((_b = formData.uploader) == null ? void 0 : _b.name) || "User", size: 40, className: "border border-zinc-200 dark:border-zinc-700" }) }), 294 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0 flex-1", children: [ 295 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 296 formData.userId === -1 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 297 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-zinc-900 dark:text-white truncate", children: "Community Contributor" }), 298 formData.countryCode && /* @__PURE__ */ jsxRuntimeExports.jsx(GlobeFlag, { countryCode: formData.countryCode, size: 16 }) 299 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs( 300 "a", 301 { 302 href: `/admin/users?userId=${((_c = formData.uploader) == null ? void 0 : _c.id) || formData.userId}`, 303 target: "_blank", 304 rel: "noopener noreferrer", 305 className: "font-bold text-indigo-600 dark:text-indigo-400 hover:underline flex items-center gap-1 truncate", 306 children: [ 307 ((_d = formData.uploader) == null ? void 0 : _d.name) || `User #${formData.userId}`, 308 ((_e = formData.uploader) == null ? void 0 : _e.showCountryBadge) && ((_f = formData.uploader) == null ? void 0 : _f.country) && /* @__PURE__ */ jsxRuntimeExports.jsx(GlobeFlag, { countryCode: formData.uploader.country, size: 16 }),
309 /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { size: 12 }) 310 ] 311 } 312 ), 313 formData.userId === -1 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-1.5 py-0.5 bg-amber-100 text-amber-700 text-[9px] uppercase font-bold rounded", children: "Wiki" }) 314 ] }), 315 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-[10px] text-zinc-500 font-mono", children: [ 316 "ID: ", 317 formData.userId === -1 ? "N/A" : ((_g = formData.uploader) == null ? void 0 : _g.id) || formData.userId 318 ] }) 319 ] }) 320 ] }), 321 formData.userId !== -1 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-2 pt-2 border-t border-zinc-200 dark:border-zinc-700", children: [ 322 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-xs text-zinc-600 dark:text-zinc-400", children: [
323 /* @__PURE__ */ jsxRuntimeExports.jsx(Mail, { size: 12, className: "shrink-0" }), 324 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", title: (_h = formData.uploader) == null ? void 0 : _h.email, children: ((_i = formData.uploader) == null ? void 0 : _i.email) || "No Email" }) 325 ] }), 326 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-xs", children: [ 327 /* @__PURE__ */ jsxRuntimeExports.jsx(Shield, { size: 12, className: Number(((_j = formData.uploader) == null ? void 0 : _j.trustScore) || 0) < 50 ? "text-red-500" : "text-emerald-500" }), 328 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-bold text-zinc-700 dark:text-zinc-300", children: [ 329 "Trust: ", 330 ((_k = formData.uploader) == null ? void 0 : _k.trustScore) ?? "??" 331 ] }) 332 ] }) 333 ] }) 334 ] }) 335 ] }), 336 track.userId === -1 && /* @__P
336URE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-zinc-100 dark:bg-zinc-800 rounded-lg", children: [ 337 /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-xs font-bold text-zinc-500 uppercase mb-3 text-amber-600 dark:text-amber-500", children: "Public Wiki Edits" }), 338 edits.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500", children: "No edits yet." }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-3 max-h-48 overflow-y-auto custom-scrollbar", children: edits.map((edit) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-sm border-l-2 border-amber-500 pl-3", children: [ 339 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "font-bold text-zinc-700 dark:text-zinc-300", children: [ 340 edit.userName, 341 " ", 342 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-normal text-zinc-500 ml-2", children: new Date(edit.timestamp).toLocaleString() }) 343 ] }),
344 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-600 dark:text-zinc-400 text-xs mt-1", children: edit.details }) 345 ] }, edit.id)) }) 346 ] }) 347 ] }), 348 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [ 349 (() => { 350 const hashColors = getColorsFromHash(formData.title + (formData.artist || "")); 351 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative group rounded-xl overflow-hidden bg-zinc-100 dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800", children: [ 352 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-40 relative overflow-hidden", children: [ 353 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { background: hashColors.gradient }, className: "w-full h-full absolute inset-0 transition-transform duration-500 group-hover:scale-105 opacity-65" }), 354 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-gradient-to-t from-black/85 via-black/20 to-black/60 pointer-events-none" }),
355 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 flex items-center justify-center p-4", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full h-16", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Waveform, { data: waveform, active: true }) }) }) 356 ] }), 357 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-2 bg-zinc-50/80 dark:bg-zinc-950/80 backdrop-blur-sm border-t border-zinc-200 dark:border-zinc-800 flex items-center justify-between text-[10px] font-mono text-zinc-500 px-4", children: [ 358 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [ 359 "DURATION: ", 360 formData.duration ? `${Math.floor(formData.duration / 60)}:${(formData.duration % 60).toString().padStart(2, "0")}` : "N/A" 361 ] }), 362 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [ 363 "TYPE: ", 364 formData.type || "MIDI" 365 ] }) 366 ] }) 367 ] }); 368 })(),
369 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-zinc-100 dark:bg-zinc-800 rounded-lg", children: [ 370 /* @__PURE__ */ jsxRuntimeExports.jsxs("h4", { className: "text-xs font-bold text-zinc-500 uppercase mb-3 flex items-center gap-2", children: [ 371 /* @__PURE__ */ jsxRuntimeExports.jsx(Shield, { size: 14, className: "text-indigo-500" }), 372 " Moderation & Level" 373 ] }), 374 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [ 375 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 376 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-400 uppercase mb-2 block", children: "Difficulty" }), 377 /* @__PURE__ */ jsxRuntimeExports.jsxs("select", { className: "w-full bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-700 rounded p-2 text-sm text-zinc-900 dark:text-white outline-none focus:ring-1 focus:ring-indigo-500", value: formData.difficulty, onChange: (e) => setFormData({ ...formData, difficulty: e.target.value }), children: [
378 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { children: "Beginner" }), 379 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { children: "Intermediate" }), 380 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { children: "Advanced" }), 381 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { children: "Virtuoso" }) 382 ] }) 383 ] }), 384 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 385 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-400 uppercase mb-2 block", children: "Worker Queue Status" }), 386 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-3 gap-2", children: ["pending", "processing", "completed", "paused", "failed"].map((s) => /* @__PURE__ */ jsxRuntimeExports.jsx( 387 "button", 388 { 389 type: "button", 390 onClick: () => setFormData({ ...formData, status: s }), 391 className: `px-2 py-1.5 text-[10px] font-bold rounded capitalize border transition-all ${formData.status === s ? "bg-indigo-600 border-indigo-600 text-white" : "bg-transparent border-zinc-300 dark:border-zinc-700 text-zinc-500 hover:border-indigo-500 hover:text-indigo-500"}`, 392 children: s === "pending" ? "Queued" : s 393 }, 394 s 395 )) }) 396 ] }),
397 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-4", children: [ 398 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-400 uppercase mb-2 block", children: "Moderation Visibility" }), 399 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [ 400 ["active", "suspended", "rejected", "user-deleted"].map((v) => /* @__PURE__ */ jsxRuntimeExports.jsx( 401 "button", 402 { 403 type: "button", 404 onClick: () => setFormData({ ...formData, visibility: v }), 405 className: `px-2 py-1.5 text-[10px] font-bold rounded capitalize border transition-all ${formData.visibility === v ? "bg-indigo-600 border-indigo-600 text-white" : "bg-transparent border-zinc-300 dark:border-zinc-700 text-zinc-500 hover:border-indigo-500 hover:text-indigo-500"}`, 406 children: v.replace("-", " ") 407 }, 408 v 409 )),
410 /* @__PURE__ */ jsxRuntimeExports.jsx( 411 "button", 412 { 413 type: "button", 414 onClick: () => { 415 const presets = ["active", "suspended", "rejected", "user-deleted"]; 416 if (presets.includes(formData.visibility)) { 417 setFormData({ ...formData, visibility: "" }); 418 } 419 }, 420 className: `px-2 py-1.5 text-[10px] font-bold rounded capitalize border transition-all ${!["active", "suspended", "rejected", "user-deleted"].includes(formData.visibility) ? "bg-indigo-600 border-indigo-600 text-white" : "bg-transparent border-zinc-300 dark:border-zinc-700 text-zinc-500 hover:border-indigo-500 hover:text-indigo-500"}`, 421 children: "Custom" 422 } 423 ) 424 ] }), 425 !["active", "suspended", "rejected", "user-deleted"].includes(formData.visibility) && /* @__PURE__ */ jsxRuntimeExports.jsx( 426 "input", 427 { 428 type: "text", 429 value: formData.visibility || "", 430 onChange: (e) => setFormData({ ...formData, visibility: e.target.value }), 431 placeholder: "Enter custom visibility (e.g. copyright-hold)", 432 className: "w-full bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-700 rounded p-2 text-sm text-zinc-900 dark:text-white outline-none focus:ring-1 focus:ring-indigo-500 mt-2 font-mono text-xs" 433 } 434 ),
435 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-[10px] text-zinc-400 dark:text-zinc-500 mt-2 leading-relaxed bg-zinc-50 dark:bg-zinc-900/50 p-2 rounded border border-zinc-150 dark:border-zinc-800", children: [ 436 formData.visibility === "active" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-emerald-600 dark:text-emerald-400 font-medium", children: "Active: Publicly visible, playable, and searchable across the site." }), 437 formData.visibility === "suspended" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-amber-600 dark:text-amber-400 font-medium", children: "Suspended: Temporarily hidden from public. Original files remain in place. Best for pending reviews or copyright holds." }), 438 formData.visibility === "rejected" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-600 dark:text-red-400 font-medium", children: "Rejected: Permanently banned and physically quarantined off the public asset directory. Used for copyright hits." }), 439 formData.visibility === "user-deleted" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-indigo-600 dark:text-indigo-400 font-medium", children: "User Deleted: Soft-deleted by the original uploader. Completely hidden from public view and uploader menus." }), 440 !["active", "suspended", "rejected", "user-deleted"].includes(formData.visibility) && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-indigo-600 dark:text-indigo-400 font-medium", children: [ 441 "Custom Visibility: '", 442 formData.visibility || "", 443 "'. Visible according to custom DB status mapping." 444 ] }) 445 ] }) 446 ] }) 447 ] }) 448 ] }),
449 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-zinc-100 dark:bg-zinc-800 rounded-lg", children: [ 450 /* @__PURE__ */ jsxRuntimeExports.jsxs("h4", { className: "text-xs font-bold text-zinc-500 uppercase mb-3 flex items-center gap-2", children: [ 451 /* @__PURE__ */ jsxRuntimeExports.jsx(FileHeadphone, { size: 14, className: "text-indigo-500" }), 452 " File Assets" 453 ] }), 454 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [ 455 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center text-xs", children: [ 456 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2 text-zinc-700 dark:text-zinc-300", children: [ 457 /* @__PURE__ */ jsxRuntimeExports.jsx(FileHeadphone, { size: 12 }), 458 " Opus Preview" 459 ] }), 460 formData.mp3Path ? /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheckBig, { className: "text-emerald-500", size: 14 }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-400 text-[10px] uppercase font-bold text-xs", children: "Pending" }) 461 ] }),
462 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center text-xs", children: [ 463 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2 text-zinc-700 dark:text-zinc-300", children: [ 464 /* @__PURE__ */ jsxRuntimeExports.jsx(FileHeadphone, { size: 12 }), 465 " Clean MIDI" 466 ] }), 467 formData.cleanMidiPath ? /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheckBig, { className: "text-emerald-500", size: 14 }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-400 text-[10px] uppercase font-bold", children: "Pending" }) 468 ] }), 469 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center text-xs", children: [ 470 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2 text-zinc-700 dark:text-zinc-300", children: [ 471 /* @__PURE__ */ jsxRuntimeExports.jsx(FileHeadphone, { size: 12 }), 472 " Original MIDI" 473 ] }), 474 formData.midiPath ? /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheckBig, { className: "text-emerald-500", size: 14 }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-400 text-[10px] uppercase font-bold", children: "N/A" }) 475 ] }),
476 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 pt-3 border-t border-zinc-200 dark:border-zinc-700", children: [ 477 /* @__PURE__ */ jsxRuntimeExports.jsx("a", { href: formData.cleanMidiPath || "#", download: true, className: "flex-1 text-center py-2 bg-white dark:bg-zinc-900 rounded border border-zinc-200 dark:border-zinc-700 text-[10px] font-bold text-zinc-700 dark:text-zinc-300 hover:bg-zinc-50 dark:hover:bg-zinc-800 transition-colors", children: "CLEAN MIDI" }), 478 /* @__PURE__ */ jsxRuntimeExports.jsx("a", { href: formData.midiPath || "#", download: true, className: "flex-1 text-center py-2 bg-white dark:bg-zinc-900 rounded border border-zinc-200 dark:border-zinc-700 text-[10px] font-bold text-zinc-700 dark:text-zinc-300 hover:bg-zinc-50 dark:hover:bg-zinc-800 transition-colors", children: "ORIGINAL" }) 479 ] }), 480 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pt-3 border-t border-zinc-200 dark:border-zinc-700", children: [
481 /* @__PURE__ */ jsxRuntimeExports.jsx( 482 "input", 483 { 484 type: "file", 485 accept: ".mid,.midi", 486 onChange: async (e) => { 487 var _a2; 488 const file = (_a2 = e.target.files) == null ? void 0 : _a2[0]; 489 if (!file) return; 490 if (!confirm(`Are you sure you want to replace the MIDI file for track ID #${formData.id}? This will purge all old assets and queue it for full reprocessing.`)) { 491 e.target.value = ""; 492 return; 493 } 494 try { 495 setSaving(true); 496 const res = await api.replaceMidi(Number(formData.id), file); 497 alert(res.message || "MIDI file replaced successfully!"); 498 onRefresh(); 499 onClose(); 500 } catch (err) { 501 alert(`Replacement failed: ${err.message}`); 502 } finally { 503 setSaving(false); 504 } 505 }, 506 style: { display: "none" }, 507 id: "replace-midi-file-input" 508 } 509 ),
510 /* @__PURE__ */ jsxRuntimeExports.jsxs( 511 "button", 512 { 513 type: "button", 514 onClick: () => { 515 var _a2; 516 return (_a2 = document.getElementById("replace-midi-file-input")) == null ? void 0 : _a2.click(); 517 }, 518 disabled: saving, 519 className: "w-full text-center py-2 bg-indigo-50 dark:bg-indigo-950/20 text-indigo-600 dark:text-indigo-400 hover:bg-indigo-100 dark:hover:bg-indigo-900/30 rounded border border-indigo-150 dark:border-indigo-900/40 text-[10px] font-bold transition-colors flex items-center justify-center gap-1.5", 520 children: [ 521 /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { size: 12 }), 522 "REPLACE MIDI FILE" 523 ] 524 } 525 ) 526 ] }) 527 ] }) 528 ] }) 529 ] }) 530 ] }), 531 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute bottom-0 left-0 right-0 p-6 bg-white/80 dark:bg-[#1a1a1a]/80 backdrop-blur-md border-t border-zinc-200 dark:border-zinc-800 flex gap-4 z-20", children: [
532 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: handleDelete, className: "px-6 py-3 bg-red-100 text-red-600 hover:bg-red-200 rounded-xl font-bold transition-colors", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash, { size: 20 }) }), 533 /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: handleSave, disabled: saving, className: "flex-1 bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3 rounded-xl flex items-center justify-center gap-2 shadow-lg shadow-indigo-500/20 transition-all disabled:opacity-50", children: [ 534 saving ? /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "animate-spin", size: 20 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheckBig, { size: 20 }), 535 "Save Changes" 536 ] }) 537 ] }) 538 ] }) }); 539}; 540const WorkerConsole = ({ isOpen, onClose }) => { 541 const [status, setStatus] = reactExports.useState({ pending: 0, processing: 0, completed: 0, metadataOnly: 0, rejected: 0, lastHeartbeat: null }); 542 const [isLoading, setIsLoading] = reactExports.useState(true); 543 const intervalRef = reactExports.useRef(null); 544 const pollStatus = async () => { 545 try { 546 const res = await api.getWorkerStatus(); 547 setStatus(res); 548 } catch (e) { 549 } finally { 550 setIsLoading(false); 551 } 552 }; 553 reactExports.useEffect(() => { 554 if (isOpen) { 555 setIsLoading(true); 556 pollStatus(); 557 intervalRef.current = setInterval(pollStatus, 2e3); 558 } else { 559 if (intervalRef.current) clearInterval(intervalRef.current); 560 } 561 return () => { 562 if (intervalRef.current) clearInterval(intervalRef.current); 563 }; 564 }, [isOpen]); 565 const hb = status.lastHeartbeat; 566 const hbTime = hb ? new Date(hb.endsWith("Z") ? hb : hb + "Z").getTime() : 0; 567 const isWorkerOnline = hb && Date.now() - hbTime < 6e4; 568 const lastSeen = hb ? new Date(hb.endsWith("Z") ? hb : hb + "Z").toLocaleTimeString() : "Never"; 569 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `border-t border-zinc-200 dark:border-zinc-800 bg-black transition-all duration-300 ${isOpen ? "h-48" : "h-0 overflow-hidden"}`, children: [ 570 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-8 flex items-center justify-between px-4 bg-zinc-900 border-b border-zinc-800", children: [
571 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-[10px] font-bold text-zinc-500 uppercase flex items-center gap-2", children: [ 572 /* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { size: 10 }), 573 " Worker Console" 574 ] }), 575 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 576 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: pollStatus, title: "Ping Worker", children: /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 12, className: "text-zinc-500 hover:text-white" }) }), 577 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 14, className: "text-zinc-500" }) }) 578 ] }) 579 ] }), 580 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 flex gap-8", children: [ 581 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center", children: [ 582 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-2xl font-bold text-white", children: status.pending }),
583 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] uppercase text-zinc-500", children: "Pending" }) 584 ] }), 585 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center", children: [ 586 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-2xl font-bold text-indigo-500 animate-pulse", children: status.processing }), 587 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] uppercase text-zinc-500", children: "Processing" }) 588 ] }), 589 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center", children: [ 590 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-2xl font-bold text-emerald-500", children: status.completed }), 591 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] uppercase text-zinc-500", children: "Completed" }) 592 ] }), 593 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center", children: [
594 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-2xl font-bold text-teal-500", children: status.metadataOnly || 0 }), 595 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] uppercase text-zinc-500", children: "Metadata Only" }) 596 ] }), 597 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center", children: [ 598 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-2xl font-bold text-red-500", children: status.rejected }), 599 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] uppercase text-zinc-500", children: "Failed" }) 600 ] }), 601 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 border-l border-zinc-800 pl-8 flex flex-col justify-center", children: [ 602 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mb-1", children: [ 603 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `w-2 h-2 rounded-full ${isLoading ? "bg-zinc-500 animate-pulse" : isWorkerOnline ? "bg-emerald-500 animate-pulse" : "bg-red-500"}` }),
604 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-sm font-bold ${isWorkerOnline ? "text-emerald-500" : "text-red-500"}`, children: isLoading ? "Loading..." : isWorkerOnline ? "Worker Online" : "Worker Offline" }) 605 ] }), 606 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-zinc-500 font-mono", children: isLoading ? "Checking worker..." : `Last Seen: ${lastSeen}` }), 607 !isLoading && !isWorkerOnline && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 text-[10px] text-amber-500 flex items-center gap-1", children: [ 608 /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { size: 10 }), 609 " Check container logs." 610 ] }) 611 ] }) 612 ] }) 613 ] }); 614}; 615const MediaLibrary = ({ refreshTrigger, onUploadClick }) => { 616 const [inspectingTrack, setInspectingTrack] = reactExports.useState(null); 617 const [localRefresh, setLocalRefresh] = reactExports.useState(0); 618 const [selectedIds, setSelectedIds] = reactExports.useState([]); 619 const [isBatchEditOpen, setIsBatchEditOpen] = reactExports.useState(false); 620 const [isSoundFontModalOpen, setIsSoundFontModalOpen] = reactExports.useState(false); 621 const [isBulkReprocessOpen, setIsBulkReprocessOpen] = reactExports.useState(false); 622 const [isConsoleOpen, setIsConsoleOpen] = reactExports.useState(false); 623 const [playingTrackId, setPlayingTrackId] = reactExports.useState(null); 624 const [playbackEngine, setPlaybackEngine] = reactExports.useState(null); 625 const audioRef = reactExports.useRef(null); 626 const activePreviewTrackIdRef = reactExports.useRef(null); 627 reactExports.useEffect(() => { 628 const params = new URLSearchParams(window.location.search); 629 const id = params.get("id"); 630 if (id) { 631 api.getAdminTrack(id).then(setInspectingTrack).catch(console.error); 632 } 633 return () => { 634 if (audioRef.current) { 635 audioRef.current.pause(); 636 } 637 synth.stop(); 638 }; 639 }, []); 640 const refresh = () => setLocalRefresh((p) => p + 1); 641 const fetcher = reactExports.useCallback((params) => api.getAdminTracks(params), []); 642 const handleReprocessComplete = () => { 643 refresh(); 644 setIsBulkReprocessOpen(false); 645 setIsConsoleOpen(true); 646 }; 647 const handleSingleReprocess = async (id) => { 648 try { 649 await api.reprocessTracks({ ids: [id] }); 650 refresh(); 651 } catch (e) { 652 console.error("Failed to queue job", e); 653 } 654 }; 655 const handleAnalyzeStructure = async (id) => { 656 try { 657 await api.analyzeTrackStructure(id); 658 alert("Structural analysis queued for track " + id); 659 refresh(); 660 } catch (e) { 661 console.error("Failed to queue analysis", e); 662 alert("Analysis failed: " + e.message); 663 } 664 }; 665 const togglePreview = async (track) => { 666 if (playingTrackId === track.id) { 667 activePreviewTrackIdRef.current = null; 668 if (audioRef.current) { 669 audioRef.current.pause(); 670 } 671 synth.stop(); 672 setPlayingTrackId(null); 673 setPlaybackEngine(null); 674 } else { 675 activePreviewTrackIdRef.current = track.id; 676 if (audioRef.current) { 677 audioRef.current.pause(); 678 } 679 synth.stop(); 680 if (track.opusPath) { 681 const audio = new Audio(track.opusPath); 682 audio.onended = () => { 683 if (activePreviewTrackIdRef.current === track.id) { 684 setPlayingTrackId(null); 685 setPlaybackEngine(null); 686 activePreviewTrackIdRef.current = null; 687 } 688 }; 689 audio.play().catch((e) => { 690 console.error("Failed to play preview", e); 691 if (activePreviewTrackIdRef.current === track.id) { 692 setPlayingTrackId(null); 693 setPlaybackEngine(null); 694 activePreviewTrackIdRef.current = null; 695 } 696 }); 697 audioRef.current = audio; 698 setPlayingTrackId(track.id); 699 setPlaybackEngine("Opus/MP3 Audio"); 700 } else { 701 const midiUrl = track.cleanMidiPath || track.midiPath; 702 if (midiUrl) { 703 setPlayingTrackId(track.id); 704 setPlaybackEngine("HTML5 MIDI Synth (Loading...)"); 705 try { 706 const parsed = await parseMidiInstruments([midiUrl]); 707 if (activePreviewTrackIdRef.current !== track.id) { 708 return; 709 } 710 await synth.loadTracks( 711 parsed.tracks, 712 parsed.tickDiv, 713 parsed.tempo, 714 "free", 715 "GeneralUser-GS", 716 [midiUrl], 717 () => { 718 }, 719 true, 720 "GeneralUser-GS", 721 true 722 ); 723 if (activePreviewTrackIdRef.current !== track.id) { 724 synth.stop(); 725 return; 726 } 727 await synth.play(); 728 setPlaybackEngine("HTML5 MIDI Synth"); 729 } catch (e) { 730 console.error("Failed to play MIDI preview", e); 731 if (activePreviewTrackIdRef.current === track.id) { 732 alert("Failed to play MIDI preview: " + e.message); 733 setPlayingTrackId(null); 734 setPlaybackEngine(null); 735 activePreviewTrackIdRef.current = null; 736 } 737 } 738 } else { 739 alert("No audio preview or MIDI file available"); 740 } 741 } 742 } 743 }; 744 const actions = /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 items-center", children: [
745 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `transition-opacity flex gap-2 ${selectedIds.length > 0 ? "opacity-100" : "opacity-0 pointer-events-none"}`, children: /* @__PURE__ */ jsxRuntimeExports.jsxs( 746 "button", 747 { 748 onClick: () => setIsBatchEditOpen(true), 749 className: "flex items-center gap-2 px-3 py-1.5 rounded-lg bg-indigo-600 text-white hover:bg-indigo-700 text-xs font-bold", 750 children: [ 751 /* @__PURE__ */ jsxRuntimeExports.jsx(SquarePen, { size: 14 }), 752 " Batch Edit (", 753 selectedIds.length, 754 ")" 755 ] 756 } 757 ) }), 758 /* @__PURE__ */ jsxRuntimeExports.jsxs( 759 "button", 760 { 761 onClick: () => setIsBulkReprocessOpen(true), 762 className: "flex items-center gap-2 px-3 py-1.5 rounded-lg bg-zinc-100 dark:bg-zinc-800 text-zinc-700 dark:text-zinc-200 hover:bg-zinc-200 dark:hover:bg-zinc-700 text-xs font-bold border border-zinc-200 dark:border-zinc-700", 763 title: "Reprocess MIDI ID Range", 764 children: [
765 /* @__PURE__ */ jsxRuntimeExports.jsx(Layers, { size: 14 }), 766 " Bulk Reprocess" 767 ] 768 } 769 ), 770 /* @__PURE__ */ jsxRuntimeExports.jsxs( 771 "button", 772 { 773 onClick: () => setIsSoundFontModalOpen(true), 774 className: "flex items-center gap-2 px-3 py-1.5 rounded-lg bg-zinc-100 dark:bg-zinc-800 text-zinc-700 dark:text-zinc-200 hover:bg-zinc-200 dark:hover:bg-zinc-700 text-xs font-bold border border-zinc-200 dark:border-zinc-700", 775 children: [ 776 /* @__PURE__ */ jsxRuntimeExports.jsx(Settings2, { size: 14 }), 777 " SoundFonts" 778 ] 779 } 780 ), 781 /* @__PURE__ */ jsxRuntimeExports.jsxs( 782 "button", 783 { 784 onClick: onUploadClick, 785 className: "flex items-center gap-2 px-3 py-1.5 rounded-lg bg-zinc-100 dark:bg-zinc-800 text-zinc-700 dark:text-zinc-200 hover:bg-zinc-200 dark:hover:bg-zinc-700 text-xs font-bold", 786 children: [ 787 /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { size: 14 }), 788 " New Upload" 789 ] 790 } 791 ) 792 ] }); 793 const COLUMNS = [ 794 { label: "MIDI ID", key: "id" }, 795 { label: "Track", key: "title" }, 796 // Maps to title for sorting 797 { label: "User ID", key: "userId" }, 798 { label: "Description", key: "description" }, 799 { label: "Views", key: "views" }, 800 { label: "Health", key: "healthScore" }, 801 { label: "Created", key: "createdAt" }, 802 { label: "Processed", key: "lastProcessedAt" }, 803 { label: "Status", key: "status" }, 804 { label: "Actions", key: "actions" } 805 ]; 806 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full", children: [ 807 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx( 808 DataTable, 809 { 810 title: "Media Library", 811 icon: Database, 812 placeholder: "Search tracks...", 813 filters: [ 814 { key: "status", label: "Status", options: [ 815 { label: "Completed", value: "completed" }, 816 { label: "Queued", value: "pending" }, 817 { label: "Processing", value: "processing" }, 818 { label: "Paused", value: "paused" }, 819 { label: "Failed", value: "failed" }, 820 { label: "Suspended", value: "suspended" }, 821 { label: "Rejected", value: "rejected" }, 822 { label: "User Deleted", value: "user-deleted" } 823 ] }, 824 { key: "id", label: "MIDI ID", type: "text", placeholder: "MIDI ID" }, 825 { key: "userId", label: "User ID", type: "text", placeholder: "User ID" } 826 ], 827 onFetch: fetcher, 828 reloadTrigger: refreshTrigger + localRefresh, 829 onRowClick: setInspectingTrack, 830 initialSearch: new URLSearchParams(window.location.search).get("id") || "", 831 initialFilters: new URLSearchParams(window.location.search).get("id") ? { id: new URLSearchParams(window.location.search).get("id") } : {}, 832 columnHeaders: COLUMNS, 833 dense: true, 834 columns: (track) => { 835 var _a; 836 return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
837 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-2 py-2 text-zinc-500 font-mono text-xs w-[7ch] text-center", children: track.id }), 838 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-4 py-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 839 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-6 h-6 rounded bg-zinc-100 dark:bg-zinc-800 overflow-hidden shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: track.thumbnail, className: "w-full h-full object-cover" }) }), 840 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [ 841 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-bold text-zinc-900 dark:text-white truncate text-xs max-w-[150px]", title: track.title, children: track.title }), 842 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-zinc-500 truncate max-w-[150px]", children: track.artist }) 843 ] }) 844 ] }) }),
845 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-4 py-2 text-xs font-mono text-zinc-600 dark:text-zinc-400", title: (_a = track.uploader) == null ? void 0 : _a.name, children: String(track.userId || "Unknown") }), 846 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-4 py-2 text-xs text-zinc-500 truncate max-w-[200px]", title: track.description, children: track.description ? track.description.length > 50 ? track.description.substring(0, 50) + "..." : track.description : "-" }), 847 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-4 py-2 text-xs font-mono text-zinc-600 dark:text-zinc-400", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [ 848 /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { size: 10 }), 849 " ", 850 track.views 851 ] }) }), 852 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-4 py-2 text-xs", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex items-center gap-1 font-bold ${Number(track.healthScore || 0) < 50 ? "text-red-500" : "text-emerald-500"}`, children: [
853 /* @__PURE__ */ jsxRuntimeExports.jsx(Activity, { size: 10 }), 854 " ", 855 String(track.healthScore || 0), 856 "%" 857 ] }) }), 858 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-4 py-2 text-xs text-zinc-500 whitespace-nowrap", children: new Date(track.createdAt || track.uploadedAt).toLocaleDateString() }), 859 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-4 py-2 text-[10px] text-zinc-500 whitespace-nowrap font-mono", children: track.lastProcessedAt ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 860 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-bold text-zinc-600 dark:text-zinc-400", children: new Date(track.lastProcessedAt).toLocaleDateString() }), 861 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "opacity-70", children: new Date(track.lastProcessedAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) }) 862 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "opacity-30 italic", children: "Never" }) }),
863 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-4 py-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1 items-start", children: [ 864 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `px-1.5 py-0.5 rounded text-[9px] font-bold uppercase tracking-wide leading-none ${track.status === "completed" ? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400" : track.status === "pending" ? "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400" : track.status === "processing" ? "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400 animate-pulse" : track.status === "paused" ? "bg-zinc-100 text-zinc-700 dark:bg-zinc-800/50 dark:text-zinc-400" : "bg-rose-100 dark:bg-rose-900/30 text-rose-700 dark:text-rose-400"}`, children: track.status === "pending" ? "queued" : track.status }), 865 track.visibility !== "active" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `px-1.5 py-0.5 rounded text-[9px] font-bold uppercase tracking-wide leading-none ${track.visibility === "suspended" ? "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400" : track.visibility === "rejected" ? "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400" : "bg-zinc-100 text-zinc-700 dark:bg-zinc-800/50 dark:text-zinc-400"}`, children: track.visibility === "user-deleted" ? "deleted" : track.visibility }) 866 ] }) }),
867 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-4 py-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [ 868 /* @__PURE__ */ jsxRuntimeExports.jsx( 869 "button", 870 { 871 onClick: (e) => { 872 e.stopPropagation(); 873 togglePreview(track); 874 }, 875 className: `p-1.5 rounded transition-colors ${playingTrackId === track.id ? "text-indigo-600 dark:text-indigo-400 hover:bg-indigo-100 dark:hover:bg-indigo-950/40 bg-indigo-50 dark:bg-indigo-950/20 animate-pulse" : "text-zinc-500 hover:text-indigo-600 hover:bg-indigo-50 dark:hover:bg-indigo-900/20"}`, 876 title: playingTrackId === track.id ? `Stop (${playbackEngine})` : "Play Preview", 877 children: playingTrackId === track.id ? /* @__PURE__ */ jsxRuntimeExports.jsx(Square, { size: 14, fill: "currentColor" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { size: 14 }) 878 } 879 ),
880 /* @__PURE__ */ jsxRuntimeExports.jsx( 881 "a", 882 { 883 href: `/watch?v=${track.id}`, 884 target: "_blank", 885 rel: "noopener noreferrer", 886 onClick: (e) => e.stopPropagation(), 887 className: "p-1.5 text-zinc-500 hover:text-indigo-600 hover:bg-indigo-50 dark:hover:bg-indigo-900/20 rounded transition-colors", 888 title: "Open Watch Page", 889 children: /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { size: 14 }) 890 } 891 ), 892 /* @__PURE__ */ jsxRuntimeExports.jsx( 893 "button", 894 { 895 onClick: (e) => { 896 e.stopPropagation(); 897 handleSingleReprocess(track.id); 898 }, 899 className: "p-1.5 text-zinc-500 hover:text-indigo-600 hover:bg-indigo-50 dark:hover:bg-indigo-900/20 rounded transition-colors", 900 title: "Reprocess Track", 901 children: /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 14 }) 902 } 903 ),
904 /* @__PURE__ */ jsxRuntimeExports.jsx( 905 "button", 906 { 907 onClick: (e) => { 908 e.stopPropagation(); 909 handleAnalyzeStructure(track.id); 910 }, 911 className: "p-1.5 text-zinc-500 hover:text-amber-600 hover:bg-amber-50 dark:hover:bg-amber-900/20 rounded transition-colors", 912 title: "Request AI Structural Analysis", 913 children: /* @__PURE__ */ jsxRuntimeExports.jsx(Brain, { size: 14 }) 914 } 915 ) 916 ] }) }) 917 ] }); 918 }, 919 selectable: true, 920 onSelectionChange: setSelectedIds, 921 actions 922 } 923 ) }), 924 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-8 bg-zinc-100 dark:bg-zinc-900 border-t border-zinc-200 dark:border-zinc-800 flex justify-between items-center px-4 cursor-pointer hover:bg-zinc-200 dark:hover:bg-zinc-800", onClick: () => setIsConsoleOpen(!isConsoleOpen), children: [ 925 /* @__P
925URE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs font-bold text-zinc-500 flex items-center gap-2", children: [ 926 /* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { size: 12 }), 927 " Worker Status" 928 ] }), 929 playingTrackId && playbackEngine && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs font-semibold text-indigo-600 dark:text-indigo-400 flex items-center gap-1.5 animate-pulse", children: [ 930 /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { size: 10, fill: "currentColor" }), 931 " Previewing Track ", 932 playingTrackId, 933 " via ", 934 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "underline", children: playbackEngine }) 935 ] }), 936 isConsoleOpen ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 14, className: "text-zinc-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronUp, { size: 14, className: "text-zinc-500" }) 937 ] }), 938 /* @__PURE__ */ jsxRuntimeExports.jsx(WorkerConsole, { isOpen: isConsoleOpen, onClose: () => setIsConsoleOpen(false) }),
939 inspectingTrack && /* @__PURE__ */ jsxRuntimeExports.jsx(MediaInspector, { track: inspectingTrack, onClose: () => setInspectingTrack(null), onRefresh: refresh }, inspectingTrack.id), 940 isBatchEditOpen && /* @__PURE__ */ jsxRuntimeExports.jsx(BatchEditModal, { trackIds: selectedIds, onClose: () => setIsBatchEditOpen(false), onComplete: () => { 941 refresh(); 942 setIsBatchEditOpen(false); 943 } }), 944 isBulkReprocessOpen && /* @__PURE__ */ jsxRuntimeExports.jsx(BulkReprocessModal, { onClose: () => setIsBulkReprocessOpen(false), onComplete: handleReprocessComplete }), 945 /* @__PURE__ */ jsxRuntimeExports.jsx(SoundFontModal, { isOpen: isSoundFontModalOpen, onClose: () => setIsSoundFontModalOpen(false) }) 946 ] }); 947}; 948export { 949 MediaLibrary as default 950};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.