feat(wave): implement Phase 1 Smart Wave recommendations and buffer rotation

This commit is contained in:
Antigravity committed 2026-10-12 14:43:29 +03:00
1 parent aa6c605d14
commit 9fbe372fab
10 files changed
+879

No files matched your search

+98
View File
@@ -297,4 +297,102 @@ document.addEventListener("DOMContentLoaded", () => {
const d = new Date(isoStr);
return d.toLocaleDateString() + " " + d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}
// --- Smart Wave Logic ---
const waveCountLabel = document.getElementById("waveCountLabel");
const waveSizeLabel = document.getElementById("waveSizeLabel");
const waveAnchorsContainer = document.getElementById("waveAnchorsContainer");
const waveAnchorsList = document.getElementById("waveAnchorsList");
const waveTracksList = document.getElementById("waveTracksList");
const waveLoadingText = document.getElementById("waveLoadingText");
const waveGenerateBtn = document.getElementById("waveGenerateBtn");
const waveRotateBtn = document.getElementById("waveRotateBtn");
fetchWaveStatus();
if (waveGenerateBtn) {
waveGenerateBtn.addEventListener("click", async () => {
waveGenerateBtn.disabled = true;
if (waveLoadingText) waveLoadingText.style.display = "inline-block";
try {
const res = await fetch("api/wave/generate", { method: "POST" });
const data = await res.json();
alert(`Волна обновлена! Добавлено: ${data.added_count || 0} треков.`);
await fetchWaveStatus();
} catch (err) {
alert("Ошибка генерации волны: " + err);
} finally {
waveGenerateBtn.disabled = false;
if (waveLoadingText) waveLoadingText.style.display = "none";
}
});
}
if (waveRotateBtn) {
waveRotateBtn.addEventListener("click", async () => {
waveRotateBtn.disabled = true;
try {
const res = await fetch("api/wave/rotate", { method: "POST" });
const data = await res.json();
const promMsg = (data.promoted_count > 0) ? `⭐ Перенесено в библиотеку: ${data.promoted_count}` : "Нет лайкнутых для переноса";
const purMsg = (data.purged_count > 0) ? `🗑️ Удалено устаревших: ${data.purged_count}` : "Нет устаревших";
alert(`Ротация завершена!\n${promMsg}\n${purMsg}`);
await fetchWaveStatus();
} catch (err) {
alert("Ошибка ротации: " + err);
} finally {
waveRotateBtn.disabled = false;
}
});
}
async function fetchWaveStatus() {
try {
const res = await fetch("api/wave/status");
const data = await res.json();
renderWaveStatus(data);
} catch (err) {
console.error("Wave status error:", err);
}
}
function renderWaveStatus(data) {
if (waveCountLabel) waveCountLabel.textContent = `${data.count || 0} / ${data.capacity || 25} треков`;
if (waveSizeLabel) waveSizeLabel.textContent = `${data.total_size_mb || 0} МБ`;
// Anchors
if (waveAnchorsContainer && waveAnchorsList) {
const anchors = data.anchor_artists || [];
if (anchors.length > 0) {
waveAnchorsContainer.style.display = "block";
waveAnchorsList.innerHTML = anchors.map(a => `<span class="tag-anchor">${escapeHtml(a)}</span>`).join("");
} else {
waveAnchorsContainer.style.display = "none";
}
}
// Tracks list
if (waveTracksList) {
const tracks = data.tracks || [];
if (tracks.length === 0) {
waveTracksList.innerHTML = `<p class="empty-state">Волна пуста. Нажмите «Обновить Волну» для генерации первого потока.</p>`;
} else {
waveTracksList.innerHTML = tracks.map(t => `
<div class="wave-track-item">
<div style="min-width: 0; display: flex; flex-direction: column; gap: 0.15rem;">
<strong style="font-size: 0.88rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${escapeHtml(t.title)}</strong>
<span style="font-size: 0.78rem; color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
${escapeHtml(t.artist)} • ${t.size_mb} МБ (${t.age_days} дн. назад)
</span>
</div>
<div style="display: flex; gap: 0.4rem; flex-shrink: 0;">
<button class="btn-play-preview" onclick="playPreviewTrack('${escapeAttr(t.path)}', '${escapeAttr(t.title)}', '${escapeAttr(t.artist)}', '', '', 'api/stream?url=' + encodeURIComponent('${escapeAttr(t.path)}'))">
▶ Слушать
</button>
</div>
</div>
`).join("");
}
}
}
});