1import { r as reactExports, j as jsxRuntimeExports } from "./vendor-react-Bay9Y7Lb.js"; 2import { S as ScoreMusicXmlViewer } from "./ScoreMusicXmlViewer-2usqegaw.js"; 3import { aX as ArrowLeft, cc as Save, e9 as CloudUpload, dP as GitBranch, e2 as Bot, eu as Terminal, bi as Sparkles, bU as Play, b1 as CircleAlert } from "./vendor-dVNaLJx_.js"; 4import "./vendor-i18n-R-bMoYp_.js"; 5function AlgorithmEditor({ currentUser, onAuthRequested, id, onNavigate }) { 6 const [algo, setAlgo] = reactExports.useState(null); 7 const [name, setName] = reactExports.useState(""); 8 const [description, setDescription] = reactExports.useState(""); 9 const [content, setContent] = reactExports.useState(""); 10 const [benchmarks, setBenchmarks] = reactExports.useState([]); 11 const [selectedBenchmark, setSelectedBenchmark] = reactExports.useState("basic_scale"); 12 const [customMidiId, setCustomMidiId] = reactExports.useState(""); 13 const [generatedXml, setGeneratedXml] = reactExports.useState(""); 14 const [testDiagnostics, setTestDiagnostics] = reactExports.useState(null); 15 const [isTesting, setIsTesting] = reactExports.useState(false); 16 const [isSaving, setIsSaving] = reactExports.useState(false); 17 const isNew = id === "new"; 18 reactExports.useEffect(() => { 19 if (!currentUser) return onAuthRequested(); 20 fetch("/api/algorithms/benchmarks").then((r) => r.json()).then((data) => { 21 if (Array.isArray(data)) setBenchmarks(data); 22 }).catch((err) => console.error("Failed to load benchmarks:", err)); 23 if (isNew) { 24 setName("My Quantization Standard"); 25 setDescription("Custom community quantization logic for unquantized MIDIs."); 26 setContent(`function processScore(midiData) { 27 // 1. You receive midiData as a parsed JSON object (via @tonejs/midi format) 28 // 2. You must return a valid MusicXML 3.1 string 29 30 // Example: Generate basic score with staves 31 return \`<?xml version="1.0" encoding="UTF-8"?> 32<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.1 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd"> 33<score-partwise version="3.1"> 34 <part-list> 35 <score-part id="P1"><part-name>Piano</part-name></score-part> 36 </part-list> 37 <part id="P1"> 38 <measure number="1"> 39 <attributes> 40 <divisions>4</divisions> 41 <key><fifths>0</fifths></key> 42 <time><beats>4</beats><beat-type>4</beat-type></time> 43 <clef><sign>G</sign><line>2</line></clef> 44 </attributes> 45 <note> 46 <pitch><step>C</step><octave>4</octave></pitch> 47 <duration>4</duration> 48 <type>quarter</type> 49 </note> 50 <note> 51 <pitch><step>E</step><octave>4</octave></pitch> 52 <duration>4</duration> 53 <type>quarter</type> 54 </note> 55 <note> 56 <pitch><step>G</step><octave>4</octave></pitch> 57 <duration>4</duration> 58 <type>quarter</type> 59 </note> 60 <note> 61 <pitch><step>C</step><octave>5</octave></pitch> 62 <duration>4</duration> 63 <type>quarter</type> 64 </note> 65 </measure> 66 </part> 67</score-partwise>\`; 68}`); 69 } else { 70 fetch(`/api/algorithms/${id}`).then((r) => r.json()).then((data) => { 71 if (data.error) { 72 alert(`Error: ${data.error}`); 73 return onNavigate("algorithm-studio"); 74 } 75 setAlgo(data); 76 setName(data.name || ""); 77 setDescription(data.description || ""); 78 setContent(data.content || ""); 79 }).catch((err) => console.error("Failed to load algorithm:", err)); 80 } 81 }, [id, currentUser, isNew, onAuthRequested, onNavigate]); 82 const isOwner = isNew ? true : algo ? String(algo.authorId) === String(currentUser == null ? void 0 : currentUser.id) : true; 83 const isDraft = isNew || algo && algo.status === "DRAFT"; 84 const isReadOnly = !isDraft || !isOwner; 85 const handleSave = async () => { 86 if (!name.trim()) return alert("Algorithm name is required"); 87 setIsSaving(true); 88 try { 89 if (isNew) { 90 const res = await fetch("/api/algorithms", { 91 method: "POST", 92 headers: { "Content-Type": "application/json" }, 93 body: JSON.stringify({ name, description, content }) 94 }); 95 const data = await res.json(); 96 if (data.error) { 97 alert(`Error creating draft: ${data.error}`); 98 return; 99 } 100 alert("Draft created successfully!"); 101 onNavigate("algorithm-editor", data.id); 102 } else { 103 const res = await fetch(`/api/algorithms/${id}`, { 104 method: "PUT", 105 headers: { "Content-Type": "application/json" }, 106 body: JSON.stringify({ name, description, content }) 107 }); 108 const data = await res.json(); 109 if (data.error) { 110 alert(`Error saving draft: ${data.error}`); 111 return; 112 } 113 alert("Draft saved successfully!"); 114 } 115 } catch (e) { 116 alert(`Save failed: ${e.message}`); 117 } finally { 118 setIsSaving(false); 119 } 120 }; 121 const handlePublish = async () => { 122 if (!name.trim()) return alert("Algorithm name is required before publishing"); 123 if (!window.confirm("Publish to Phase 1 Proposals? This will submit your draft to the community and open upvoting.")) return; 124 setIsSaving(true); 125 try { 126 let targetId = id; 127 if (isNew) { 128 const saveRes = await fetch("/api/algorithms", { 129 method: "POST", 130 headers: { "Content-Type": "application/json" }, 131 body: JSON.stringify({ name, description, content }) 132 }); 133 const saveData = await saveRes.json(); 134 if (saveData.error) { 135 alert(`Error saving draft: ${saveData.error}`); 136 return; 137 }
138 targetId = saveData.id; 139 } else { 140 await fetch(`/api/algorithms/${id}`, { 141 method: "PUT", 142 headers: { "Content-Type": "application/json" }, 143 body: JSON.stringify({ name, description, content }) 144 }); 145 } 146 const pubRes = await fetch(`/api/algorithms/${targetId}/publish`, { 147 method: "POST", 148 headers: { "Content-Type": "application/json" } 149 }); 150 const pubData = await pubRes.json(); 151 if (pubData.error) { 152 alert(`Error publishing: ${pubData.error}`); 153 return; 154 } 155 alert("Successfully published to Phase 1 Proposals!"); 156 onNavigate("algorithm-studio"); 157 } catch (e) { 158 alert(`Publish failed: ${e.message}`); 159 } finally { 160 setIsSaving(false); 161 } 162 }; 163 const handleBranch = async () => { 164 if (!currentUser) return onAuthRequested(); 165 try { 166 const res = await fetch("/api/algorithms", { 167 method: "POST", 168 headers: { "Content-Type": "application/json" }, 169 body: JSON.stringify({ 170 name: `${name} (Branch)`, 171 description: `Branched from ${name}`, 172 content, 173 parentAlgorithmId: algo == null ? void 0 : algo.id 174 }) 175 }); 176 const data = await res.json(); 177 if (data.error) return alert(`Branch error: ${data.error}`); 178 onNavigate("algorithm-editor", data.id); 179 } catch (e) { 180 alert(`Branch failed: ${e.message}`); 181 } 182 }; 183 const handleTest = async () => { 184 setIsTesting(true); 185 setTestDiagnostics(null); 186 const startTime = Date.now(); 187 try { 188 let payload = { scriptContent: content }; 189 if (selectedBenchmark === "custom") { 190 if (!customMidiId.trim()) { 191 alert("Please enter a valid MIDI track ID to test."); 192 setIsTesting(false); 193 return; 194 } 195 const trackNum = parseInt(customMidiId.trim(), 10); 196 const groupFolder = Math.floor(trackNum / 1e3); 197 const midiRes = await fetch(`/storage/tracks/midi/${groupFolder}/${trackNum}.mid`); 198 if (!midiRes.ok) throw new Error(`MIDI track #${trackNum} not found in public storage`); 199 const arrayBuffer = await midiRes.arrayBuffer(); 200 const base64String = btoa( 201 new Uint8Array(arrayBuffer).reduce((data2, byte) => data2 + String.fromCharCode(byte), "") 202 ); 203 payload.midiBase64 = base64String; 204 } else { 205 payload.benchmarkId = selectedBenchmark; 206 } 207 const res = await fetch("/api/algorithms/sandbox-test", { 208 method: "POST", 209 headers: { "Content-Type": "application/json" }, 210 body: JSON.stringify(payload) 211 }); 212 const data = await res.json(); 213 const elapsed = Date.now() - startTime; 214 if (data.error) { 215 setTestDiagnostics({ elapsedMs: elapsed, error: data.error }); 216 setGeneratedXml(""); 217 } else { 218 setGeneratedXml(data.xml || ""); 219 setTestDiagnostics({ elapsedMs: elapsed }); 220 } 221 } catch (e) { 222 setTestDiagnostics({ elapsedMs: Date.now() - startTime, error: e.message }); 223 setGeneratedXml(""); 224 } finally { 225 setIsTesting(false); 226 } 227 }; 228 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-7xl mx-auto p-6 h-screen flex flex-col animate-in fade-in", children: [ 229 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-4 shrink-0 flex-wrap gap-4", children: [
230 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4", children: [ 231 /* @__PURE__ */ jsxRuntimeExports.jsx( 232 "button", 233 { 234 type: "button", 235 onClick: () => onNavigate("algorithm-studio"), 236 className: "p-2 bg-zinc-100 hover:bg-zinc-200 dark:bg-zinc-800 dark:hover:bg-zinc-700 rounded-full transition-colors", 237 title: "Back to Studio", 238 children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowLeft, { size: 20 }) 239 } 240 ), 241 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 242 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2.5", children: [ 243 /* @__PURE__ */ jsxRuntimeExports.jsx( 244 "input", 245 { 246 value: name, 247 onChange: (e) => setName(e.target.value), 248 placeholder: "Algorithm Name", 249 disabled: isReadOnly, 250 className: "text-2xl font-black bg-transparent outline-none placeholder-zinc-400 text-zinc-900 dark:text-zinc-100" 251 } 252 ),
253 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `px-2.5 py-0.5 rounded-full text-[11px] font-bold uppercase tracking-wider ${isNew || (algo == null ? void 0 : algo.status) === "DRAFT" ? "bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/30" : (algo == null ? void 0 : algo.status) === "PHASE1" ? "bg-orange-500/10 text-orange-600 dark:text-orange-400 border border-orange-500/30" : (algo == null ? void 0 : algo.status) === "PHASE2" ? "bg-blue-500/10 text-blue-600 dark:text-blue-400 border border-blue-500/30" : "bg-green-500/10 text-green-600 dark:text-green-400 border border-green-500/30"}`, children: isNew ? "New Draft" : (algo == null ? void 0 : algo.status) || "Draft" }) 254 ] }), 255 /* @__PURE__ */ jsxRuntimeExports.jsx( 256 "input", 257 { 258 value: description, 259 onChange: (e) => setDescription(e.target.value), 260 placeholder: "Brief description of the quantization approach...", 261 disabled: isReadOnly, 262 className: "block w-full text-zinc-500 bg-transparent outline-none text-sm mt-1" 263 } 264 ) 265 ] }) 266 ] }),
267 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [ 268 isDraft && isOwner && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 269 /* @__PURE__ */ jsxRuntimeExports.jsxs( 270 "button", 271 { 272 type: "button", 273 onClick: handleSave, 274 disabled: isSaving, 275 className: "px-4 py-2 bg-zinc-200 hover:bg-zinc-300 dark:bg-zinc-800 dark:hover:bg-zinc-700 text-zinc-800 dark:text-zinc-200 rounded-xl font-bold flex items-center gap-2 text-sm transition-colors", 276 children: [ 277 /* @__PURE__ */ jsxRuntimeExports.jsx(Save, { size: 16 }), 278 " ", 279 isSaving ? "Saving..." : "Save Draft" 280 ] 281 } 282 ), 283 /* @__PURE__ */ jsxRuntimeExports.jsxs( 284 "button", 285 { 286 type: "button", 287 onClick: handlePublish, 288 disabled: isSaving, 289 className: "px-5 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-xl font-bold flex items-center gap-2 text-sm shadow-md shadow-indigo-600/20 transition-colors", 290 children: [
291 /* @__PURE__ */ jsxRuntimeExports.jsx(CloudUpload, { size: 16 }), 292 " Submit to Phase 1" 293 ] 294 } 295 ) 296 ] }), 297 !isDraft && algo && /* @__PURE__ */ jsxRuntimeExports.jsxs( 298 "button", 299 { 300 type: "button", 301 onClick: handleBranch, 302 className: "px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-xl font-bold flex items-center gap-2 text-sm shadow-md transition-colors", 303 children: [ 304 /* @__PURE__ */ jsxRuntimeExports.jsx(GitBranch, { size: 16 }), 305 " Branch New Draft" 306 ] 307 } 308 ) 309 ] }) 310 ] }), 311 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-indigo-50 dark:bg-indigo-950/20 border border-indigo-200 dark:border-indigo-900/40 rounded-xl p-3 mb-4 shrink-0 flex items-start gap-3", children: [ 312 /* @__PURE__ */ jsxRuntimeExports.jsx(Bot, { className: "text-indigo-500 shrink-0 mt-0.5", size: 18 }), 313 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
314 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-indigo-900 dark:text-indigo-300 font-semibold mb-1", children: "Develop with AI Assistance:" }), 315 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "bg-white dark:bg-zinc-900 border border-indigo-100 dark:border-indigo-900/50 p-2 rounded text-[11px] font-mono text-zinc-600 dark:text-zinc-400 select-all overflow-x-auto", children: "Write a Javascript function called `processScore(midiData)` that takes a parsed @tonejs/midi JSON object and returns a valid MusicXML 3.1 string. The function should iterate over `midiData.tracks`, align notes to the nearest 16th note grid, and group durations into standard musical measures assuming 4/4 meter." }) 316 ] }) 317 ] }), 318 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-4 flex-1 min-h-0", children: [ 319 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-1/2 flex flex-col bg-zinc-950 rounded-xl border border-zinc-800 overflow-hidden shadow-inner", children: [
320 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-900 px-4 py-2 border-b border-zinc-800 text-xs font-bold text-zinc-400 font-mono flex items-center justify-between", children: [ 321 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 322 /* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { size: 14, className: "text-indigo-400" }), 323 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "index.js" }) 324 ] }), 325 isReadOnly && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-zinc-500 uppercase tracking-wider font-semibold", children: "Read-Only (Published)" }) 326 ] }), 327 /* @__PURE__ */ jsxRuntimeExports.jsx( 328 "textarea", 329 { 330 value: content, 331 onChange: (e) => setContent(e.target.value), 332 disabled: isReadOnly, 333 className: "flex-1 w-full bg-transparent text-zinc-300 font-mono text-xs p-4 outline-none resize-none leading-relaxed select-text", 334 spellCheck: false 335 } 336 ) 337 ] }),
338 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-1/2 flex flex-col gap-3 min-h-0", children: [ 339 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-900 rounded-xl border border-zinc-200 dark:border-zinc-800 p-3 shrink-0 shadow-sm", children: [ 340 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between gap-3 mb-2", children: [ 341 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs font-bold text-zinc-700 dark:text-zinc-200 flex items-center gap-1.5", children: [ 342 /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 14, className: "text-amber-500" }), 343 "Benchmark Test Runner" 344 ] }), 345 (testDiagnostics == null ? void 0 : testDiagnostics.elapsedMs) !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[11px] font-mono text-zinc-500", children: [ 346 "Latency: ", 347 /* @__PURE__ */ jsxRuntimeExports.jsxs("strong", { className: "text-zinc-700 dark:text-zinc-300", children: [ 348 testDiagnostics.elapsedMs, 349 "ms" 350 ] }) 351 ] }) 352 ] }),
353 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 354 /* @__PURE__ */ jsxRuntimeExports.jsxs( 355 "select", 356 { 357 value: selectedBenchmark, 358 onChange: (e) => setSelectedBenchmark(e.target.value), 359 className: "flex-1 px-3 py-1.5 bg-zinc-100 dark:bg-zinc-950 text-zinc-800 dark:text-zinc-200 border border-zinc-300 dark:border-zinc-800 rounded-lg outline-none text-xs font-medium cursor-pointer", 360 children: [ 361 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "basic_scale", className: "bg-white dark:bg-zinc-900 text-zinc-800 dark:text-zinc-200", children: "Standard: C Major Scale Baseline (4/4)" }), 362 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "twinkle_twinkle", className: "bg-white dark:bg-zinc-900 text-zinc-800 dark:text-zinc-200", children: "Standard: Twinkle Twinkle (Melody Benchmark)" }), 363 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "polyphonic_invention", className: "bg-white dark:bg-zinc-900 text-zinc-800 dark:text-zinc-200", children: "Standard: Two-Voice Counterpoint (Polyphonic)" }),
364 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "syncopated_rhythm", className: "bg-white dark:bg-zinc-900 text-zinc-800 dark:text-zinc-200", children: "Standard: Syncopated Rhythm Test" }), 365 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "custom", className: "bg-white dark:bg-zinc-900 text-zinc-800 dark:text-zinc-200", children: "Custom MIDI Track ID..." }) 366 ] 367 } 368 ), 369 selectedBenchmark === "custom" && /* @__PURE__ */ jsxRuntimeExports.jsx( 370 "input", 371 { 372 type: "text", 373 placeholder: "MIDI ID (e.g. 878)", 374 value: customMidiId, 375 onChange: (e) => setCustomMidiId(e.target.value), 376 className: "w-32 px-2.5 py-1.5 bg-zinc-100 dark:bg-zinc-950 text-zinc-800 dark:text-zinc-200 border border-zinc-300 dark:border-zinc-800 rounded-lg outline-none text-xs font-mono" 377 } 378 ), 379 /* @__PURE__ */ jsxRuntimeExports.jsxs( 380 "button", 381 { 382 type: "button", 383 onClick: handleTest, 384 disabled: isTesting, 385 className: "px-4 py-1.5 bg-green-600 hover:bg-green-700 disabled:opacity-50 text-white rounded-lg font-bold flex items-center gap-1.5 text-xs transition-colors shrink-0 shadow-sm", 386 children: [
387 /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { size: 13 }), 388 " ", 389 isTesting ? "Running..." : "Run Sandbox" 390 ] 391 } 392 ) 393 ] }), 394 (testDiagnostics == null ? void 0 : testDiagnostics.error) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 p-2 rounded-lg bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-900/50 text-red-600 dark:text-red-400 text-xs flex items-start gap-2", children: [ 395 /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { size: 14, className: "shrink-0 mt-0.5" }), 396 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 font-mono text-[11px] break-words", children: testDiagnostics.error }) 397 ] }) 398 ] }), 399 /* @__PURE__ */ jsxRuntimeExports.jsx( 400 ScoreMusicXmlViewer, 401 { 402 xmlString: generatedXml, 403 isLoading: isTesting, 404 title: "Sandbox Output", 405 className: "flex-1 min-h-0" 406 } 407 ) 408 ] }) 409 ] }) 410 ] }); 411} 412export { 413 AlgorithmEditor as default 414};
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.