tone3000: IR downloads, model detail, arch filter, metadata

- NAM/IR tab toggle in Downloads overlay
- Architecture filter (All/Nano/Feather/Standard) - filters via
  Supabase architecture_version column
- Model detail overlay: tap a search result to see full description,
  gear info, author, pack, architecture, thumbnail before downloading
- IR search + install with .meta.json metadata saving
- Backend: search_models/get_trending_models accept arch param
- Backend: tone_gear field added to model search response
This commit is contained in:
2026-06-16 19:59:38 -04:00
parent 5cdc751312
commit 177d412c84
3 changed files with 117 additions and 23 deletions
+82 -14
View File
@@ -226,7 +226,18 @@ body{display:flex;flex-direction:column;max-height:100vh;user-select:none;-webki
<!-- Downloads Overlay -->
<div class="overlay" id="overlayDownloads">
<div class="overlay-hdr"><span class="overlay-title">⬇ Downloads</span><button class="overlay-close" id="closeDownloads"></button></div>
<div class="overlay-body">
<!-- Tab + filter nav -->
<div class="settings-nav">
<button class="sn-btn active" id="dlTabNam" onclick="switchDlTab('nam')">🎛 NAM</button>
<button class="sn-btn" id="dlTabIr" onclick="switchDlTab('ir')">📦 IR</button>
</div>
<div class="settings-nav" style="border-bottom:1px solid var(--border);padding-top:0">
<button class="sn-btn active" data-arch="" onclick="setArch(this,'')">All</button>
<button class="sn-btn" data-arch="nano" onclick="setArch(this,'nano')">Nano</button>
<button class="sn-btn" data-arch="feather" onclick="setArch(this,'feather')">Feather</button>
<button class="sn-btn" data-arch="standard" onclick="setArch(this,'standard')">Standard</button>
</div>
<div class="overlay-body" style="padding-top:4px">
<div style="display:flex;gap:6px;margin-bottom:6px">
<input id="dlSearch" placeholder="Search Tone3000..." style="flex:1;padding:6px 8px;border-radius:6px;border:1px solid var(--border);background:var(--surface);color:var(--text);font-family:var(--font);font-size:11px;outline:none;min-height:36px">
<button class="sr-action primary" id="dlSearchBtn">🔍</button>
@@ -235,6 +246,14 @@ body{display:flex;flex-direction:column;max-height:100vh;user-select:none;-webki
</div>
</div>
<!-- Model Detail Overlay -->
<div class="overlay" id="overlayDetail">
<div class="overlay-hdr"><span class="overlay-title" id="dlDetailTitle">Model</span><button class="overlay-close"></button></div>
<div class="overlay-body">
<div id="dlDetailContent"></div>
</div>
</div>
<!-- Settings Overlay -->
<div class="overlay" id="overlaySettings">
<div class="overlay-hdr"><span class="overlay-title">⚙ Settings</span><button class="overlay-close" id="closeSettings"></button></div>
@@ -1006,31 +1025,80 @@ function init(){
try{await API.saveSnapshot(currentChannel,slot);toast('Saved to slot '+slot);loadSnapshots();}catch(e){toast(e.message);}
};
// Downloads search
document.getElementById('dlSearchBtn').onclick=async()=>{
// Downloads - state
let dlTab='nam', dlArch='';
function switchDlTab(tab){
dlTab=tab;
document.getElementById('dlTabNam').className='sn-btn'+(tab==='nam'?' active':'');
document.getElementById('dlTabIr').className='sn-btn'+(tab==='ir'?' active':'');
const archRow=document.querySelectorAll('#overlayDownloads > .settings-nav')[1];
if(archRow)archRow.style.display=tab==='nam'?'flex':'none';
doDlSearch();
}
function setArch(btn,arch){
document.querySelectorAll('#overlayDownloads .settings-nav')[1].querySelectorAll('.sn-btn').forEach(b=>b.classList.remove('active'));
btn.classList.add('active');
dlArch=arch;
doDlSearch();
}
async function doDlSearch(){
const q=document.getElementById('dlSearch').value.trim();
if(!q)return;
const res=document.getElementById('dlResults');
res.innerHTML='<div style="text-align:center;padding:16px"><span class="spinner"></span></div>';
try{
const data=await API._fetch('/api/models/tonedownload/search?q='+encodeURIComponent(q)+'&page=0');
const endpoint=dlTab==='nam'?'/api/models/tonedownload/search':'/api/irs/tonedownload/search';
const params='?q='+encodeURIComponent(q)+(dlArch?'&arch='+dlArch:'')+'&page=0';
const data=await API._fetch(endpoint+params);
const items=data.results||[];
res.innerHTML='';
if(items.length===0){res.innerHTML='<div style="text-align:center;padding:16px;color:var(--text-dim);font-size:11px">No results</div>';return;}
if(items.length===0){res.innerHTML='<div style="text-align:center;padding:16px;color:var(--text-dim);font-size:11px">No results'+(q?'':' \u2014 tap Search or type a query')+'</div>';return;}
items.forEach(item=>{
const d=document.createElement('div');
d.className='preset-card';
d.style.cursor='default';
d.innerHTML='<div class="pc-name">'+(item.name||'Unknown')+'</div><div class="pc-num">'+(item.author||'')+(item.pack_name?' · '+item.pack_name:'')+'</div><div style="font-size:8px;color:var(--text-dim);margin-top:2px">'+(item.size_display||'')+(item.architecture?' · '+item.architecture:'')+'</div><button class="sr-action primary" style="margin-top:4px;align-self:flex-end">Get</button>';
d.querySelector('button').onclick=async()=>{
d.querySelector('button').textContent='...';
try{await API._fetch('/api/models/tonedownload/install',{method:'POST',body:JSON.stringify({id:item.id,download_url:item.download_url,name:item.name,filename:item.filename,size:item.size,author:item.author,author_avatar:item.author_avatar,thumbnail:item.thumbnail,tone_description:item.tone_description,pack_name:item.pack_name,architecture:item.architecture,created_at:item.created_at}),timeout:60000});toast('Downloaded!');d.querySelector('button').textContent='✓';}catch(e){toast(e.message);d.querySelector('button').textContent='Retry';}
};
d.style.cursor='pointer';
const meta=[];
if(item.size_display)meta.push(item.size_display);
if(item.architecture)meta.push(item.architecture);
if(item.tone_gear&&item.tone_gear!=='nam')meta.push(item.tone_gear);
d.innerHTML='<div class="pc-name">'+(item.name||'Unknown')+'</div><div class="pc-num">'+(item.author||'')+(item.pack_name?' \u00b7 '+item.pack_name:'')+'</div><div style="font-size:8px;color:var(--text-dim);margin-top:2px">'+meta.join(' \u00b7 ')+'</div>';
d.onclick=()=>showDlDetail(item);
res.appendChild(d);
});
}catch(e){res.innerHTML='<div style="text-align:center;padding:16px;color:var(--text-dim)">Error</div>';}
};
document.getElementById('dlSearch').onkeydown=(e)=>{if(e.key==='Enter')document.getElementById('dlSearchBtn').click();};
}
function showDlDetail(item){
const c=document.getElementById('dlDetailContent');
const isIr=dlTab==='ir';
let html='<div style="margin-bottom:8px">';
if(item.thumbnail)html+='<img src="'+item.thumbnail+'" style="width:100%;border-radius:8px;margin-bottom:6px;max-height:160px;object-fit:cover">';
html+='<div style="font-size:14px;font-weight:700;margin-bottom:4px">'+(item.name||'Unknown')+'</div>';
html+='<div style="font-size:11px;color:var(--text-sec);margin-bottom:6px">by '+(item.author||'Unknown')+'</div>';
if(item.pack_name)html+='<div style="font-size:10px;color:var(--text-dim);margin-bottom:4px">Pack: '+item.pack_name+'</div>';
if(item.architecture)html+='<div style="font-size:10px;color:var(--text-dim);margin-bottom:4px">Arch: '+item.architecture+'</div>';
if(item.size_display)html+='<div style="font-size:10px;color:var(--text-dim);margin-bottom:4px">Size: '+item.size_display+'</div>';
if(item.tone_gear)html+='<div style="font-size:10px;color:var(--text-dim);margin-bottom:4px">Gear: '+item.tone_gear+'</div>';
if(item.tone_description)html+='<div style="font-size:10px;color:var(--text2);margin-top:6px;padding:6px 8px;background:var(--surface);border-radius:6px;border:1px solid var(--border);line-height:1.4">'+(item.tone_description||'')+'</div>';
html+='</div>';
html+='<button class="sr-action primary" id="dlDetailBtn" style="width:100%;min-height:42px;font-size:13px">Download'+(isIr?' IR':'')+'</button>';
c.innerHTML=html;
document.getElementById('dlDetailTitle').textContent=(item.name||'Model');
document.getElementById('overlayDetail').classList.add('open');
document.getElementById('dlDetailBtn').onclick=async()=>{
const btn=document.getElementById('dlDetailBtn');
btn.textContent='...';
try{
const endpoint=isIr?'/api/irs/tonedownload/install':'/api/models/tonedownload/install';
await API._fetch(endpoint,{method:'POST',body:JSON.stringify({id:item.id,download_url:item.download_url,name:item.name,filename:item.filename,size:item.size,author:item.author,author_avatar:item.author_avatar,thumbnail:item.thumbnail,tone_description:item.tone_description,pack_name:item.pack_name,architecture:item.architecture,created_at:item.created_at}),timeout:60000});
toast('Downloaded!');
btn.textContent='Downloaded';
btn.className='sr-action';
}catch(e){toast(e.message);btn.textContent='Retry';}
};
}
// Wire downloads search
document.getElementById('dlSearchBtn').onclick=doDlSearch;
document.getElementById('dlSearch').onkeydown=(e)=>{if(e.key==='Enter')doDlSearch();};
// Start polling
startPolling();