Qnet match v upload queue — auto-prepoznavanje pesmi takoj ko izbere\u0161 fajle (parallel POST /api/qnet/match-batch). \u010ce baza prepozna komad, prikaz Artist \u2014 Title z station badge namesto 'Brez razvidnega imena'.

This commit is contained in:
OpenClaw Agent 2026-05-02 10:47:51 +00:00
parent b938d1e4d8
commit 24e1b53aa8
2 changed files with 62 additions and 17 deletions

View File

@ -1772,6 +1772,27 @@ async def qnet_match_filename(filename: str, user: str = Depends(check_auth)):
return qnet_match.match_filename(filename)
class QnetBatchMatchRequest(BaseModel):
filenames: list[str]
min_confidence: float = 0.85
@app.post("/api/qnet/match-batch")
async def qnet_match_batch(payload: QnetBatchMatchRequest, user: str = Depends(check_auth)):
"""Batch match — kliče client ob izbiri datotek za live preview v queue."""
results = {}
for fn in payload.filenames:
if not fn:
continue
r = qnet_match.match_filename(fn)
# Vrni samo zadetke nad minimum confidence; sicer null
if r["matched"] and r["confidence"] >= payload.min_confidence:
results[fn] = r
else:
results[fn] = None
return {"results": results}
@app.post("/api/qnet/sync")
async def qnet_sync(background: BackgroundTasks, user: str = Depends(check_auth)):
"""Sproži sync (fetch Songs.txt iz vseh playerjev). Async background task."""

View File

@ -504,28 +504,45 @@
const newItems = [];
for (const f of files) {
const [artist, title] = parseArtistTitle(f.name);
newItems.push({ file: f, artist, title, dedup: null });
newItems.push({ file: f, artist, title, dedup: null, qnetMatch: null });
}
// Dedup check pred dodanjem v queue
const filenames = newItems.map(i => i.file.name);
const tvStation = $("#tv-station-input").value || "FOLX SLOVENIJA";
try {
const r = await fetch("/api/dedup/check", {
// Vzporedno: dedup check + Qnet match (oba endpointa, neodvisna)
const [dedupRes, qnetRes] = await Promise.all([
fetch("/api/dedup/check", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ filenames: newItems.map(i => i.file.name), tv_station: tvStation }),
body: JSON.stringify({ filenames, tv_station: tvStation }),
}).then(r => r.ok ? r.json() : null).catch(e => { console.warn("Dedup check failed:", e); return null; }),
fetch("/api/qnet/match-batch", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ filenames, min_confidence: 0.85 }),
}).then(r => r.ok ? r.json() : null).catch(e => { console.warn("Qnet match failed:", e); return null; }),
]);
// Apply dedup
if (dedupRes && dedupRes.results) {
newItems.forEach(item => {
const m = dedupRes.results[item.file.name];
if (m) item.dedup = m;
});
}
// Apply Qnet match — če baza prepozna komad, prepiši artist+title
if (qnetRes && qnetRes.results) {
newItems.forEach(item => {
const m = qnetRes.results[item.file.name];
if (m && m.matched) {
item.qnetMatch = m;
// Auto-fill artist+title iz Qnet baze (clean podatki)
item.artist = m.artist;
item.title = m.title;
}
});
if (r.ok) {
const data = await r.json();
newItems.forEach(item => {
const match = data.results[item.file.name];
if (match) {
item.dedup = match; // {normalized_name, tv_station, filename_orig, job_id, nextcloud_url, file_size_mb, uploaded_at}
}
});
}
} catch (e) {
console.warn("Dedup check failed:", e);
}
pendingFiles.push(...newItems);
@ -579,7 +596,14 @@
div.className = "file-queue-item";
const sizeMB = (item.file.size / 1024 / 1024).toFixed(1);
let nameHtml;
if (item.artist && item.title) {
if (item.qnetMatch) {
// Prepoznano iz Qnet player baze — clean artist+title + station badge
const m = item.qnetMatch;
const stationBadge = `<span style="font-size:9px; padding:1px 5px; background:rgba(74,222,128,0.18); border:1px solid rgba(74,222,128,0.5); border-radius:3px; color:#4ade80; font-weight:600; margin-left:6px; vertical-align:middle;">${escapeHtml(m.station)}</span>`;
const confLabel = m.confidence >= 0.95 ? "" : ` <span style="color:var(--muted); font-size:10px;">(${Math.round(m.confidence*100)}%)</span>`;
nameHtml = `<b>${escapeHtml(m.artist)} — ${escapeHtml(m.title)}</b>${stationBadge}` +
`<div style="font-size:10px;color:var(--muted)">🎵 prepoznano iz Qnet baze${confLabel} · ${escapeHtml(item.file.name)}</div>`;
} else if (item.artist && item.title) {
nameHtml = `<b>${escapeHtml(item.artist)} — ${escapeHtml(item.title)}</b>` +
`<div style="font-size:10px;color:var(--muted)">${escapeHtml(item.file.name)}</div>`;
} else {