Files
AI-Profile-Router/experiments/yue2-3b/community-webui-patches/patch_webui.py
T

168 lines
7.1 KiB
Python

"""Small Linux integration fixes for the pinned community WebUI.
Keep these transformations explicit and fail the image build if upstream moves
the expected code. That prevents a future upstream update from silently
producing a half-patched console.
"""
from pathlib import Path
import sys
root = Path(sys.argv[1])
app_js = root / "static" / "app.js"
index_html = root / "static" / "index.html"
server_py = root / "server.py"
def replace_once(path: Path, old: str, new: str) -> None:
text = path.read_text(encoding="utf-8")
if text.count(old) != 1:
raise RuntimeError(f"expected exactly one patch marker in {path}: {old[:80]!r}")
path.write_text(text.replace(old, new, 1), encoding="utf-8")
# The published UI calls refreshArt() during startup, but does not define it.
# Athena does not install the Windows-only stable-diffusion.cpp helper, so show
# that state honestly and keep the optional controls inert instead of crashing
# the whole page.
art_marker = ''' $("artDirAdd").addEventListener("click", function () {
'''
art_fix = ''' function refreshArt() {
return api("/api/art/status").then(function (data) {
STATE.art = data;
var installed = !!data.installed;
var supported = !!data.supported;
$("artState").textContent = data.busy || (installed ? "installed" :
(supported ? "not installed" : "not available on Linux"));
$("artState").dataset.s = data.busy ? "busy" : (installed ? "ready" : "missing");
$("artInstall").disabled = !!data.busy || !supported;
$("artInstall").textContent = installed ? "Reinstall" : "Install";
$("setArtAuto").checked = !!data.auto;
$("setArtAuto").disabled = !installed;
$("setArtModel").innerHTML = (data.models || []).length
? data.models.map(function (m) {
return '<option value="' + escape(m.path) + '">' + escape(m.name || m.file) + '</option>';
}).join("")
: '<option value="">no art model available</option>';
if (data.selected) $("setArtModel").value = data.selected;
$("setArtModel").disabled = !installed;
$("artDir").disabled = !supported;
$("artDirAdd").disabled = !supported;
$("artDirs").textContent = supported
? ((data.dirs || []).length ? "Also scanning: " + data.dirs.join(" · ") : "No extra folders configured.")
: "The community release only provides the cover-art installer for Windows; song generation is unaffected.";
$("artCatalog").innerHTML = "";
return data;
}).catch(function (error) {
$("artState").textContent = "unavailable";
$("artInstall").disabled = true;
$("artDirs").textContent = error.message;
});
}
$("artDirAdd").addEventListener("click", function () {
'''
replace_once(app_js, art_marker, art_fix)
# YuE2 natively supports an empty lyric string for true instrumentals. The
# community server rejected that valid request even though the browser did not.
replace_once(
server_py,
''' if not body.style.strip() or not body.lyrics.strip():
raise HTTPException(400, "A style prompt and lyrics are both required")
''',
''' if not body.style.strip():
raise HTTPException(400, "A style prompt is required")
''',
)
replace_once(
index_html,
'''<span class="label">Lyrics <em>section tags on their own line</em></span>''',
'''<span class="label">Lyrics <em>section tags on their own line · leave empty for a true instrumental</em></span>''',
)
# The backend already ships a complete SheetSage2 upload endpoint but hides it
# from the published console. Surface it beside the existing take-to-take
# remix controls so uploaded songs can seed a new melody/arrangement.
replace_once(
index_html,
''' <p class="row-hint" id="coverStatus"></p>
</div>
</details>
''',
''' <p class="row-hint" id="coverStatus"></p>
<h4>Remix an uploaded recording</h4>
<p class="hint">SheetSage2 listens to an uploaded song and writes an editable melody
and chord score for YuE2. This is structural transcription, not a sample or a copy of
the original sound. After analysis, choose a new style and generate normally.</p>
<div class="field-row">
<label class="field grow">
<span class="label">Source audio</span>
<input id="coverAudio" type="file" accept=".wav,.flac,.mp3,.m4a,.ogg,.opus,.aac,audio/*" />
</label>
<div class="field reset-cell">
<button type="button" class="btn ghost" id="coverFromAudio">Analyse uploaded melody</button>
</div>
</div>
<label class="check"><input type="checkbox" id="coverAudioMelodyOnly" checked />
<span>Keep the melody, but let the new style rebuild the harmony</span></label>
<p class="row-hint" id="coverAudioStatus">Checking SheetSage2…</p>
</div>
</details>
''',
)
replace_once(
app_js,
''' $("coverStatus").textContent = takes.length
? takes.length + " takes carry a score you can remix."
: "Make a song in Full plan or Melody only mode first; Direct mode keeps no score.";
}).catch(function () {});
''',
''' $("coverStatus").textContent = takes.length
? takes.length + " takes carry a score you can remix."
: "Make a song in Full plan or Melody only mode first; Direct mode keeps no score.";
var sheetsage = data.sheetsage || {};
$("coverFromAudio").disabled = !sheetsage.available;
$("coverAudioStatus").textContent = sheetsage.available
? "SheetSage2 is ready. Analysis temporarily parks YuE2 because both use the GPU."
: "SheetSage2 is not installed yet; uploaded-audio remix is unavailable.";
}).catch(function (error) {
$("coverFromAudio").disabled = true;
$("coverAudioStatus").textContent = error.message;
});
''',
)
replace_once(
app_js,
''' /* -------------------------------------------------------------- compose */
''',
''' $("coverFromAudio").addEventListener("click", function () {
var file = $("coverAudio").files[0];
if (!file) return toast("Choose an audio file first", "bad");
var button = $("coverFromAudio");
var body = new FormData();
body.append("file", file, file.name);
body.append("melody_only", $("coverAudioMelodyOnly").checked ? "true" : "false");
button.disabled = true;
button.textContent = "Analysing…";
$("coverAudioStatus").textContent = "Listening to “" + file.name + "”… this can take several minutes.";
api("/api/cover/from-audio", { method: "POST", body: body }).then(function (data) {
applyCoverScore(data.abc, "Melody analysed from “" + data.source + "” in " + data.seconds + " s. Choose the new style and generate.");
$("coverAudioStatus").textContent = "Analysis complete in " + data.seconds + " s. The editable score is loaded below.";
}).catch(function (error) {
$("coverAudioStatus").textContent = error.message;
toast(error.message, "bad");
}).then(function () {
button.textContent = "Analyse uploaded melody";
refreshCover();
});
});
/* -------------------------------------------------------------- compose */
''',
)