+
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';