feat(wave-v2): add LLM reranking, mood modes, auto-rotation scheduler and Navidrome auth
This commit is contained in:
1 parent
3b3d591bfd
commit
fb1a5e01dd
11 files changed
+736
-37
No files matched your search
+31
-3
@@ -307,17 +307,31 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
const waveLoadingText = document.getElementById("waveLoadingText");
|
||||
const waveGenerateBtn = document.getElementById("waveGenerateBtn");
|
||||
const waveRotateBtn = document.getElementById("waveRotateBtn");
|
||||
const waveMoodSelect = document.getElementById("waveMoodSelect");
|
||||
const waveContextLabel = document.getElementById("waveContextLabel");
|
||||
|
||||
fetchWaveStatus();
|
||||
|
||||
if (waveGenerateBtn) {
|
||||
waveGenerateBtn.addEventListener("click", async () => {
|
||||
waveGenerateBtn.disabled = true;
|
||||
if (waveLoadingText) waveLoadingText.style.display = "inline-block";
|
||||
const mood = waveMoodSelect ? waveMoodSelect.value : "auto";
|
||||
if (waveLoadingText) {
|
||||
waveLoadingText.innerHTML = `<span class="spinner-sm"></span> Qwen 2.5 отбирает треки под вайб...`;
|
||||
waveLoadingText.style.display = "inline-block";
|
||||
}
|
||||
try {
|
||||
const res = await fetch("api/wave/generate", { method: "POST" });
|
||||
const res = await fetch("api/wave/generate", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ mood: mood })
|
||||
});
|
||||
if (res.status === 401) {
|
||||
window.location.href = "login";
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
alert(`Волна обновлена! Добавлено: ${data.added_count || 0} треков.`);
|
||||
alert(`🌊 Волна обновлена под настроение [${data.mood_label || mood}]!\nДобавлено новых треков: ${data.added_count || 0}.`);
|
||||
await fetchWaveStatus();
|
||||
} catch (err) {
|
||||
alert("Ошибка генерации волны: " + err);
|
||||
@@ -333,6 +347,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
waveRotateBtn.disabled = true;
|
||||
try {
|
||||
const res = await fetch("api/wave/rotate", { method: "POST" });
|
||||
if (res.status === 401) {
|
||||
window.location.href = "login";
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
const promMsg = (data.promoted_count > 0) ? `⭐ Перенесено в библиотеку: ${data.promoted_count}` : "Нет лайкнутых для переноса";
|
||||
const purMsg = (data.purged_count > 0) ? `🗑️ Удалено устаревших: ${data.purged_count}` : "Нет устаревших";
|
||||
@@ -349,6 +367,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
async function fetchWaveStatus() {
|
||||
try {
|
||||
const res = await fetch("api/wave/status");
|
||||
if (res.status === 401) {
|
||||
window.location.href = "login";
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
renderWaveStatus(data);
|
||||
} catch (err) {
|
||||
@@ -360,6 +382,12 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
if (waveCountLabel) waveCountLabel.textContent = `${data.count || 0} / ${data.capacity || 25} треков`;
|
||||
if (waveSizeLabel) waveSizeLabel.textContent = `${data.total_size_mb || 0} МБ`;
|
||||
|
||||
// Context label
|
||||
if (waveContextLabel && data.current_context) {
|
||||
waveContextLabel.textContent = data.current_context.label || "🕒 Авто";
|
||||
waveContextLabel.title = data.current_context.desc || "";
|
||||
}
|
||||
|
||||
// Anchors
|
||||
if (waveAnchorsContainer && waveAnchorsList) {
|
||||
const anchors = data.anchor_artists || [];
|
||||
|
||||
@@ -731,3 +731,83 @@ input[type="text"]:focus {
|
||||
vertical-align: middle;
|
||||
margin-right: 0.3rem;
|
||||
}
|
||||
|
||||
/* Auth & User Profile */
|
||||
.badge-user {
|
||||
background-color: rgba(99, 102, 241, 0.2);
|
||||
color: #a5b4fc;
|
||||
border: 1px solid rgba(99, 102, 241, 0.4);
|
||||
}
|
||||
|
||||
.btn-logout {
|
||||
background-color: rgba(239, 68, 68, 0.15);
|
||||
color: #fca5a5;
|
||||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||
padding: 0.25rem 0.6rem;
|
||||
border-radius: 6px;
|
||||
text-decoration: none;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-logout:hover {
|
||||
background-color: rgba(239, 68, 68, 0.3);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* Phase 2: LLM & Mood */
|
||||
.badge-llm {
|
||||
background-color: rgba(168, 85, 247, 0.2);
|
||||
color: #c084fc;
|
||||
border: 1px solid rgba(168, 85, 247, 0.4);
|
||||
}
|
||||
|
||||
.wave-mood-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
margin: 0.8rem 0;
|
||||
flex-wrap: wrap;
|
||||
background-color: rgba(15, 23, 42, 0.5);
|
||||
padding: 0.6rem 0.8rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.wave-mood-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
.wave-mood-label {
|
||||
font-size: 0.82rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.wave-mood-select {
|
||||
background-color: #0f172a;
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
padding: 0.35rem 0.7rem;
|
||||
font-size: 0.85rem;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.wave-mood-select:focus {
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.badge-context {
|
||||
background-color: rgba(14, 165, 233, 0.15);
|
||||
color: #38bdf8;
|
||||
border: 1px solid rgba(14, 165, 233, 0.35);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user