-
+
NAM
No Model Loaded
โ€”
@@ -228,20 +228,39 @@ body{display:flex;flex-direction:column;max-height:100vh;user-select:none;-webki
โฌ‡ Downloads
- - + + +
- - - - + + + + +
+ +
+ + + + + +
-
+
+
+ Sort: + +
@@ -371,6 +390,7 @@ body{display:flex;flex-direction:column;max-height:100vh;user-select:none;-webki
๐Ÿ’พ Diskโ€”
๐Ÿง  RAMโ€”
โ†บ Reset
+
๐ŸŽ› NAM Enginecpp
๐Ÿ”„ Service
โป Reboot
@@ -422,9 +442,9 @@ const API = { btStatus:()=>API._fetch('/api/bluetooth/status'), btPaired:()=>API._fetch('/api/bluetooth/paired'), btMidiStatus:()=>API._fetch('/api/bluetooth/midi-status'), - btMidiEnable:()=>API._fetch('/api/bluetooth/midi-enable',{method:'POST'}), - btMidiDisable:()=>API._fetch('/api/bluetooth/midi-disable',{method:'POST'}), - btDiscoverableSet:(on)=>API._fetch('/api/bluetooth/discoverable',{method:'POST',body:JSON.stringify({on})}), + btMidiEnable:()=>API._fetch('/api/bluetooth/midi-enable',{method:'POST',timeout:15000}), + btMidiDisable:()=>API._fetch('/api/bluetooth/midi-disable',{method:'POST',timeout:15000}), + btDiscoverableSet:(on)=>API._fetch('/api/bluetooth/discoverable',{method:'POST',body:JSON.stringify({on}),timeout:15000}), listAudioProfiles:()=>API._fetch('/api/audio/profiles'), getAudioProfile:()=>API._fetch('/api/audio/profile'), setAudioProfile:(p)=>{ @@ -439,7 +459,7 @@ const API = { getSettings:()=>API._fetch('/api/settings'), getSystem:()=>API._fetch('/api/system'), getSystemLogs:(n)=>API._fetch('/api/system/logs?lines='+(n||50)), - setHostname:(h)=>API._fetch('/api/system/hostname',{method:'POST',body:JSON.stringify({hostname:h})}), + setHostname:(h)=>API._fetch('/api/system/hostname',{method:'POST',body:JSON.stringify({hostname:h}),timeout:15000}), getNetwork:()=>API._fetch('/api/network'), setNetworkStatic:(d)=>API._fetch('/api/network/static',{method:'POST',body:JSON.stringify(d),timeout:25000}), setNetworkDhcp:()=>API._fetch('/api/network/dhcp',{method:'POST',timeout:25000}), @@ -518,16 +538,19 @@ async function loadAudioDevices(){ async function setAudioInputDevice(id){ if(!id)return; showLoading('Switching input...'); - await API.saveSettings({input_device:id}).catch(()=>{}); + try{ + await API.saveSettings({input_device:id}); + toast('Input: '+id); + }catch(e){toast('Input switch failed');} hideLoading(); - toast('Input: '+id); } async function setAudioOutputDevice(id){ if(!id)return; showLoading('Switching output...'); - await API.saveSettings({output_device:id}).catch(()=>{}); - hideLoading(); - toast('Output: '+id); + try{ + await API.saveSettings({output_device:id}); + toast('Output: '+id); + }catch(e){toast('Output switch failed');} } async function setChOutput(ch,port){ try{ @@ -567,7 +590,7 @@ function setChInstrument(idx,val){ const arr=JSON.parse(localStorage.getItem('chInstruments')||'["guitar","bass"]'); arr[idx]=val; localStorage.setItem('chInstruments',JSON.stringify(arr)); - API.saveSettings({['ch'+(idx+1)+'_instrument']:val}).catch(()=>{}); + API.saveSettings({['ch'+(idx+1)+'_instrument']:val}).catch(e=>toast('Instrument failed')); } function toggleChPower(idx){ const btn=document.getElementById('ch'+(idx+1)+'Power'); @@ -576,7 +599,7 @@ function toggleChPower(idx){ btn.style.background=on?'rgba(160,160,188,.1)':'rgba(58,184,122,.2)'; btn.style.borderColor=on?'rgba(160,160,188,.2)':'rgba(58,184,122,.4)'; btn.style.color=on?'var(--text3)':'var(--green)'; - API.bypassToggle(channels[idx]||'guitar').catch(()=>{}); + API.bypassToggle(channels[idx]||'guitar').catch(e=>toast('Channel toggle failed')); } function loadChInstrumentState(){ @@ -604,8 +627,8 @@ function initVolSlider(){ track.addEventListener('touchstart',(e)=>{volDragging=true;upd(e);},{passive:true}); document.addEventListener('mousemove',(e)=>{if(volDragging){e.preventDefault();upd(e);}}); document.addEventListener('touchmove',(e)=>{if(volDragging)upd(e);},{passive:true}); - document.addEventListener('mouseup',()=>{if(!volDragging)return;volDragging=false;API.setVolume(currentChannel,parseFloat($('#volFill').style.width)/100).catch(()=>{});}); - document.addEventListener('touchend',()=>{if(!volDragging)return;volDragging=false;API.setVolume(currentChannel,parseFloat($('#volFill').style.width)/100).catch(()=>{});}); + document.addEventListener('mouseup',()=>{if(!volDragging)return;volDragging=false;API.setVolume(currentChannel,parseFloat($('#volFill').style.width)/100).catch(e=>toast('Volume failed'));}); + document.addEventListener('touchend',()=>{if(!volDragging)return;volDragging=false;API.setVolume(currentChannel,parseFloat($('#volFill').style.width)/100).catch(e=>toast('Volume failed'));}); } // โ•โ•โ• State Loading โ•โ•โ• @@ -704,10 +727,10 @@ async function toggleBlock(idx){ const b=blocks[idx]; if(!b)return; const newEn=!(b.enabled!==false); - await API.toggleBlock(currentChannel,b.block_id||idx,newEn).catch(()=>{}); + await API.toggleBlock(currentChannel,b.block_id||idx,newEn).catch(e=>toast('Block failed')); loadState(); } -function showAddBlock(){openOverlay('Downloads');} +function showAddBlock(){document.querySelector('.tab-btn[data-view="downloads"]').click();} // โ•โ•โ• Presets โ•โ•โ• async function loadPresets(){ @@ -731,9 +754,9 @@ async function loadPresets(){ } grid.appendChild(div); } - }catch(e){} + if(!bank||slots.length===0)grid.innerHTML='
No presets
'; + }catch(e){toast('Failed to load presets');} } - // โ•โ•โ• Snapshots โ•โ•โ• async function loadSnapshots(){ try{ @@ -751,7 +774,7 @@ async function loadSnapshots(){ else{div.innerHTML='
'+i+'
Empty
';} grid.appendChild(div); } - }catch(e){} + }catch(e){toast('Failed to load snapshots');} } // โ•โ•โ• Settings Loaders โ•โ•โ• @@ -764,7 +787,7 @@ async function loadNetworkInfo(){ document.getElementById('netGateway').textContent=n.ethernet.gateway||'โ€”'; } if(n.dns)document.getElementById('netDns').textContent=n.dns.join(', '); - }catch(e){} + }catch(e){toast('Network info failed');} try{ const w=await API.wifiStatus(); if(w.ssid)document.getElementById('wifiStatus').textContent=w.ssid+(w.signal?' ('+w.signal+'dB)':''); @@ -804,7 +827,7 @@ async function setAudioParams(){ const btn=document.querySelector('.sn-btn[data-section="audio"]'); if(btn)btn.textContent='โณ Applying...'; try{ - await API._fetch('/api/audio/profile',{method:'POST',body:JSON.stringify({period,rate}),timeout:15000}); + await API._fetch('/api/audio/profile',{method:'POST',body:JSON.stringify({period,rate}),timeout:30000}); toast('Audio: '+rate+'Hz / '+period+'fr'); loadAudioParams(); }catch(e){toast('Failed: '+e.message);} @@ -914,17 +937,17 @@ function init(){ initVolSlider(); // Footswitch - document.getElementById('footswitch').onclick=()=>API.bypassToggle(currentChannel).catch(()=>{}); + document.getElementById('footswitch').onclick=()=>API.bypassToggle(currentChannel).catch(e=>toast('Bypass failed')); // Tab bar navigation document.querySelectorAll('.tab-btn[data-view]').forEach(btn=>{ btn.onclick=()=>{ const view=btn.dataset.view; if(view==='main'){document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));btn.classList.add('active');} - else if(view==='settings'){openOverlay('Settings');loadNetworkInfo();loadSystemInfo();loadAudioParams();loadAudioDevices();loadChOutput();loadBackingSource();loadHotspotStatus();loadBtStatus();loadBtMidi();return;} + else if(view==='settings'){openOverlay('Settings');loadNetworkInfo();loadSystemInfo();loadAudioParams();loadAudioDevices();loadChOutput();loadBackingSource();loadHotspotStatus();loadBtStatus();loadBtMidi();loadNamEngine();return;} else if(view==='presets'){openOverlay('Presets');loadPresets();} else if(view==='snapshots'){openOverlay('Snapshots');loadSnapshots();} - else if(view==='downloads')openOverlay('Downloads'); + else if(view==='downloads'){openOverlay('Downloads');if(dlTab==='local')doDlSearch();} }; }); @@ -940,6 +963,7 @@ function init(){ loadHotspotStatus(); loadBtStatus(); loadBtMidi(); + loadNamEngine(); }; // Close all overlays @@ -1004,18 +1028,38 @@ function init(){ document.getElementById('btnRestartSvc').onclick=()=>{toast('Restarting service...');fetch('/api/settings',{method:'POST',body:JSON.stringify({_restart:true})}).catch(()=>{});}; document.getElementById('btnReboot').onclick=()=>{if(confirm('Reboot pedal?')){toast('Rebooting...');fetch('/api/settings',{method:'POST',body:JSON.stringify({_reboot:true})}).catch(()=>{});}}; + // NAM engine toggle + document.getElementById('btnToggleNamEngine').onclick=async()=>{ + const label=document.getElementById('namEngineLabel'); + const cur=label.textContent; + const next=cur==='cpp'?'pytorch':'cpp'; + label.textContent='...'; + try{ + await API._fetch('/api/nam/engine',{method:'POST',body:JSON.stringify({engine_mode:next}),timeout:30000}); + label.textContent=next; + toast('NAM engine: '+next); + }catch(e){label.textContent=cur;toast('Switch failed: '+e.message);} + }; + // Load current engine on settings open + async function loadNamEngine(){ + try{ + const r=await API._fetch('/api/nam/engine'); + document.getElementById('namEngineLabel').textContent=r.engine_mode||'cpp'; + }catch(e){} + } + // Tuner document.getElementById('tabTuner').onclick=async()=>{ state.tuner_enabled=!state.tuner_enabled; if(state.tuner_enabled){document.getElementById('tunerView').classList.add('open');startTunerPolling();} else{document.getElementById('tunerView').classList.remove('open');stopTunerPolling();} - API.tunerToggle(currentChannel,state.tuner_enabled).catch(()=>{}); + API.tunerToggle(currentChannel,state.tuner_enabled).catch(e=>toast('Tuner failed')); }; document.getElementById('tunerExit').onclick=()=>{ state.tuner_enabled=false; document.getElementById('tunerView').classList.remove('open'); stopTunerPolling(); - API.tunerToggle(currentChannel,false).catch(()=>{}); + API.tunerToggle(currentChannel,false).catch(e=>toast('Tuner failed')); }; // Snapshots save @@ -1026,34 +1070,103 @@ function init(){ try{await API.saveSnapshot(currentChannel,slot);toast('Saved to slot '+slot);loadSnapshots();}catch(e){toast(e.message);} }; - // Downloads - state - let dlTab='nam', dlArch=''; + // Downloads state & wiring + // Downloads state & wiring + let dlTab='local', dlArch='', dlGear='', dlPage=0, dlSort='created_at.desc'; function switchDlTab(tab){ - dlTab=tab; + dlTab=tab; dlPage=0; + document.getElementById('dlTabLocal').className='sn-btn'+(tab==='local'?' active':''); document.getElementById('dlTabNam').className='sn-btn'+(tab==='nam'?' active':''); document.getElementById('dlTabIr').className='sn-btn'+(tab==='ir'?' active':''); + // Show/hide filter rows per tab const archRow=document.querySelectorAll('#overlayDownloads > .settings-nav')[1]; - if(archRow)archRow.style.display=tab==='nam'?'flex':'none'; + const gearRow=document.getElementById('dlGearRow'); + const sortRow=document.getElementById('dlSortRow'); + const searchRow=document.getElementById('dlSearchRow'); + const showNam=tab==='nam'; + if(archRow)archRow.style.display=showNam?'flex':'none'; + if(gearRow)gearRow.style.display=showNam?'flex':'none'; + if(sortRow)sortRow.style.display=showNam?'flex':'none'; + if(searchRow)searchRow.style.display=(tab==='nam'||tab==='ir')?'flex':'none'; doDlSearch(); } + document.getElementById('dlTabLocal').onclick=()=>switchDlTab('local'); + document.getElementById('dlTabNam').onclick=()=>switchDlTab('nam'); + document.getElementById('dlTabIr').onclick=()=>switchDlTab('ir'); + function setArch(btn,arch){ - document.querySelectorAll('#overlayDownloads .settings-nav')[1].querySelectorAll('.sn-btn').forEach(b=>b.classList.remove('active')); + document.querySelectorAll('#overlayDownloads > .settings-nav')[1].querySelectorAll('.sn-btn').forEach(b=>b.classList.remove('active')); btn.classList.add('active'); - dlArch=arch; + dlArch=arch; dlPage=0; doDlSearch(); } + document.querySelectorAll('#overlayDownloads > .settings-nav')[1].querySelectorAll('.sn-btn[data-arch]').forEach(btn=>{ + btn.onclick=()=>setArch(btn,btn.dataset.arch); + }); + + function setGear(btn,gear){ + document.getElementById('dlGearRow').querySelectorAll('.sn-btn').forEach(b=>b.classList.remove('active')); + btn.classList.add('active'); + dlGear=gear; dlPage=0; + doDlSearch(); + } + document.getElementById('dlGearRow').querySelectorAll('.sn-btn[data-gear]').forEach(btn=>{ + btn.onclick=()=>setGear(btn,btn.dataset.gear); + }); + + // Sort dropdown + document.getElementById('dlSortSelect').onchange=function(){ + dlSort=this.value; dlPage=0; doDlSearch(); + }; + async function doDlSearch(){ - const q=document.getElementById('dlSearch').value.trim(); const res=document.getElementById('dlResults'); res.innerHTML='
'; try{ + // Local tab: list installed models from /api/models + if(dlTab==='local'){ + const data=await API._fetch('/api/models'); + const items=data.models||[]; + const current=data.current||null; + res.innerHTML=''; + if(items.length===0){ + res.innerHTML='
No models installed โ€” download from the NAM tab
'; + return; + } + items.forEach(item=>{ + const d=document.createElement('div'); + d.className='preset-card'; + d.style.cursor='pointer'; + const isLoaded=item.path===current; + if(isLoaded)d.style.borderColor='var(--green)'; + const meta=[item.architecture||'?']; + if(item.size_mb)meta.push(item.size_mb+'MB'); + d.innerHTML='
'+(item.name||'Unknown')+'
'+(isLoaded?'โœ… Loaded':'')+'
'+meta.join(' ยท ')+'
'; + d.onclick=async()=>{ + toast('Loading...'); + try{ + await API._fetch('/api/models/load',{method:'POST',body:JSON.stringify({path:item.path}),timeout:10000}); + toast('Loaded: '+item.name); + doDlSearch(); + }catch(e){toast('Failed: '+e.message);} + }; + res.appendChild(d); + }); + return; + } + + // NAM / IR tab: Tone3000 search + const q=document.getElementById('dlSearch').value.trim(); const endpoint=dlTab==='nam'?'/api/models/tonedownload/search':'/api/irs/tonedownload/search'; - const params='?q='+encodeURIComponent(q)+(dlArch?'&arch='+dlArch:'')+'&page=0'; + let params='?q='+encodeURIComponent(q)+'&page='+dlPage; + if(dlArch)params+='&arch='+dlArch; + if(dlTab==='nam'&&dlGear)params+='&gear='+dlGear; + if(dlTab==='nam')params+='&sort='+dlSort; const data=await API._fetch(endpoint+params); const items=data.results||[]; res.innerHTML=''; - if(items.length===0){res.innerHTML='
No results'+(q?'':' \u2014 tap Search or type a query')+'
';return;} + if(items.length===0){res.innerHTML='
No results'+(q?'':' โ€” no models found')+'
';return;} items.forEach(item=>{ const d=document.createElement('div'); d.className='preset-card'; @@ -1062,10 +1175,20 @@ function init(){ 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='
'+(item.name||'Unknown')+'
'+(item.author||'')+(item.pack_name?' \u00b7 '+item.pack_name:'')+'
'+meta.join(' \u00b7 ')+'
'; + if(item.downloads>0)meta.push('โฌ‡'+item.downloads); + d.innerHTML='
'+(item.name||'Unknown')+'
'+(item.author||'')+(item.pack_name?' ยท '+item.pack_name:'')+'
'+meta.join(' ยท ')+'
'; d.onclick=()=>showDlDetail(item); res.appendChild(d); }); + // Pagination bar + const pbar=document.createElement('div'); + pbar.style.cssText='display:flex;align-items:center;justify-content:center;gap:8px;padding:8px 0;margin-top:4px;border-top:1px solid var(--border)'; + pbar.innerHTML=''+ + 'Page '+(dlPage+1)+''+ + ''; + res.appendChild(pbar); + document.getElementById('dlPrevBtn').onclick=()=>{if(dlPage>0){dlPage--;doDlSearch();}}; + document.getElementById('dlNextBtn').onclick=()=>{if(items.length>=20){dlPage++;doDlSearch();}}; }catch(e){res.innerHTML='
Error
';} } function showDlDetail(item){ @@ -1078,6 +1201,8 @@ function init(){ if(item.pack_name)html+='
Pack: '+item.pack_name+'
'; if(item.architecture)html+='
Arch: '+item.architecture+'
'; if(item.size_display)html+='
Size: '+item.size_display+'
'; + if(item.downloads>0)html+='
โฌ‡ Downloads: '+item.downloads+'
'; + if(item.likes>0)html+='
โค Likes: '+item.likes+'
'; if(item.tone_gear)html+='
Gear: '+item.tone_gear+'
'; if(item.tone_description)html+='
'+(item.tone_description||'')+'
'; html+='
'; @@ -1090,7 +1215,7 @@ function init(){ 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}); + 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,downloads:item.downloads,likes:item.likes}),timeout:60000}); toast('Downloaded!'); btn.textContent='Downloaded'; btn.className='sr-action';