@@ -282,14 +282,55 @@ <h1>SAM Audio Pipeline</h1>
282282 return `<div class="card similarity-panel"><div class="similarity-head"><div><div class="eyebrow">Reconstruction similarity</div><strong>${ formatScore ( summary . mean ) } mean</strong></div><span>${ records . length } joined source(s) · median ${ formatScore ( summary . median ) } <br>sample-aligned · phase + level sensitive</span></div><svg class="similarity-chart" viewBox="0 0 ${ width } ${ height } " role="img" aria-label="${ escapeHtml ( description ) } "><title>${ escapeHtml ( description ) } </title>${ ticks } <line class="axis" x1="${ left } " y1="${ top + plotHeight } " x2="${ width - right } " y2="${ top + plotHeight } "></line>${ bars } <line class="mean-line" x1="${ meanX } " y1="${ top - 4 } " x2="${ meanX } " y2="${ top + plotHeight } "></line><text x="${ Math . min ( meanX + 4 , width - 70 ) } " y="${ top - 8 } ">mean ${ formatScore ( summary . mean ) } </text></svg></div>` ;
283283 }
284284
285- function show ( view ) {
285+ function routeState ( pathname = window . location . pathname ) {
286+ const segments = pathname . split ( "/" ) . filter ( Boolean ) . map ( segment => decodeURIComponent ( segment ) ) ;
287+ if ( segments [ 0 ] === "review" ) return { view : "review" } ;
288+ if ( segments [ 0 ] !== "data" ) return { view : "dashboard" } ;
289+ return {
290+ view : "data" ,
291+ datasetId : segments [ 1 ] || null ,
292+ jobId : segments [ 2 ] === "jobs" ? segments [ 3 ] || null : null ,
293+ sourceId : segments [ 4 ] === "sources" ? segments [ 5 ] || null : null
294+ } ;
295+ }
296+
297+ function dataRoutePath ( datasetId , jobId = null , sourceId = null ) {
298+ if ( ! datasetId ) return "/data" ;
299+ let path = `/data/${ encodeURIComponent ( datasetId ) } ` ;
300+ if ( jobId ) path += `/jobs/${ encodeURIComponent ( jobId ) } ` ;
301+ if ( jobId && sourceId ) path += `/sources/${ encodeURIComponent ( sourceId ) } ` ;
302+ return path ;
303+ }
304+
305+ function setView ( view ) {
286306 activeView = view ;
287307 for ( const name of [ "dashboard" , "data" , "review" ] ) {
288308 $ ( `#${ name } ` ) . hidden = view !== name ;
289309 $ ( `#${ name } -tab` ) . classList . toggle ( "active" , view === name ) ;
290310 }
291- if ( view === "review" ) loadReview ( ) ;
292- if ( view === "data" ) loadDataset ( ) ;
311+ }
312+
313+ async function navigateTo ( path ) {
314+ if ( window . location . pathname !== path ) history . pushState ( { } , "" , path ) ;
315+ await renderRoute ( ) ;
316+ }
317+
318+ async function navigateToSource ( jobId , sourceId ) {
319+ const datasetId = currentDataset ?. dataset ?. dataset_id || $ ( "#data-dataset" ) . value ;
320+ const path = dataRoutePath ( datasetId , jobId , sourceId ) ;
321+ if ( window . location . pathname !== path ) history . pushState ( { } , "" , path ) ;
322+ await loadSource ( jobId , sourceId ) ;
323+ }
324+
325+ async function renderRoute ( ) {
326+ const route = routeState ( ) ;
327+ currentSourceId = route . sourceId || null ;
328+ currentJobId = route . jobId || null ;
329+ await loadDatasets ( route . datasetId ) ;
330+ setView ( route . view ) ;
331+ if ( route . view === "review" ) return loadReview ( ) ;
332+ if ( route . view === "data" ) return loadDataset ( route . sourceId , route . jobId ) ;
333+ return refresh ( ) ;
293334 }
294335
295336 async function refresh ( ) {
@@ -321,7 +362,10 @@ <h1>SAM Audio Pipeline</h1>
321362 const options = datasets . map ( dataset => `<option value="${ escapeHtml ( dataset . dataset_id ) } ">${ escapeHtml ( dataset . name ) } </option>` ) . join ( "" ) ;
322363 $ ( "#dataset" ) . innerHTML = options ;
323364 $ ( "#data-dataset" ) . innerHTML = options ;
324- const value = selected || $ ( "#dataset" ) . value || datasets [ 0 ] ?. dataset_id ;
365+ const requested = selected || $ ( "#dataset" ) . value ;
366+ const value = datasets . some ( dataset => dataset . dataset_id === requested )
367+ ? requested
368+ : datasets [ 0 ] ?. dataset_id ;
325369 if ( value ) {
326370 $ ( "#dataset" ) . value = value ;
327371 $ ( "#data-dataset" ) . value = value ;
@@ -377,7 +421,7 @@ <h1>SAM Audio Pipeline</h1>
377421 return `<div class="card"><div class="eyebrow">${ escapeHtml ( label ) } </div><div class="metric-value">${ escapeHtml ( value ) } </div><div class="metric-detail">${ escapeHtml ( detail ) } </div></div>` ;
378422 }
379423
380- async function loadDataset ( preferredSource ) {
424+ async function loadDataset ( preferredSource , preferredJob ) {
381425 const datasetId = $ ( "#data-dataset" ) . value || $ ( "#dataset" ) . value ;
382426 if ( ! datasetId ) return ;
383427 $ ( "#dataset" ) . value = datasetId ;
@@ -401,22 +445,21 @@ <h1>SAM Audio Pipeline</h1>
401445 const skipped = source . skip_reason === "non_stereo_input" ? " · skipped: input is not stereo" : "" ;
402446 const similarity = source . similarity_score == null ? "" : ` · ${ formatScore ( source . similarity_score ) } joined` ;
403447 const audioProfile = audioProfileText ( source . audio_profile ) ;
404- return `<button class="source-item ${ source . source_id === currentSourceId ? "active" : "" } " data-source="${ escapeHtml ( source . source_id ) } " onclick="loadSource ('${ escapeHtml ( source . job_id ) } ', '${ escapeHtml ( source . source_id ) } ')">
448+ return `<button class="source-item ${ source . source_id === currentSourceId ? "active" : "" } " data-source="${ escapeHtml ( source . source_id ) } " onclick="navigateToSource ('${ escapeHtml ( source . job_id ) } ', '${ escapeHtml ( source . source_id ) } ')">
405449 <strong>${ escapeHtml ( source . filename ) } </strong>
406450 <small>${ escapeHtml ( source . pipeline_status ) } · ${ formatDuration ( source . duration_seconds ) } · ${ source . stem_count } stem(s)${ escapeHtml ( route ) } ${ escapeHtml ( skipped ) } ${ escapeHtml ( similarity ) } ${ needsReview ? ` · ${ needsReview } need review` : "" } </small>
407451 ${ audioProfile ? `<small class="source-audio-profile">${ escapeHtml ( audioProfile ) } </small>` : "" }
408452 </button>` ;
409453 } ) . join ( "" ) || '<div class="empty">No uploaded sounds yet.</div>' ;
410454 const target = preferredSource || currentSourceId ;
411- const source = currentDataset . sources . find ( item => item . source_id === target ) || currentDataset . sources [ 0 ] ;
455+ const source = currentDataset . sources . find ( item => item . source_id === target )
456+ || currentDataset . sources . find ( item => item . job_id === preferredJob )
457+ || currentDataset . sources [ 0 ] ;
412458 if ( source ) await loadSource ( source . job_id , source . source_id ) ;
413459 }
414460
415461 async function openJob ( jobId , datasetId ) {
416- await loadDatasets ( datasetId ) ;
417- show ( "data" ) ;
418- const source = currentDataset ?. sources . find ( item => item . job_id === jobId ) ;
419- if ( source ) await loadSource ( source . job_id , source . source_id ) ;
462+ await navigateTo ( dataRoutePath ( datasetId , jobId ) ) ;
420463 }
421464
422465 function trackRow ( kind , label , subtitle , rawUrl , stereoUrl = null ) {
@@ -558,22 +601,23 @@ <h2>${escapeHtml(item.assertion)}</h2>
558601 await loadReview ( ) ;
559602 }
560603
561- $ ( "#dashboard-tab" ) . onclick = ( ) => show ( "dashboard ") ;
562- $ ( "#data-tab" ) . onclick = ( ) => show ( " data" ) ;
563- $ ( "#review-tab" ) . onclick = ( ) => show ( " review") ;
604+ $ ( "#dashboard-tab" ) . onclick = ( ) => navigateTo ( "/ ") ;
605+ $ ( "#data-tab" ) . onclick = ( ) => navigateTo ( dataRoutePath ( $ ( "# data-dataset" ) . value || $ ( "#dataset" ) . value ) ) ;
606+ $ ( "#review-tab" ) . onclick = ( ) => navigateTo ( "/ review") ;
564607 $ ( "#submit" ) . onclick = submitFiles ;
565608 $ ( "#new-dataset" ) . onclick = newDataset ;
566- $ ( "#refresh-dataset" ) . onclick = ( ) => loadDataset ( currentSourceId ) ;
609+ $ ( "#refresh-dataset" ) . onclick = ( ) => loadDataset ( currentSourceId , currentJobId ) ;
567610 $ ( "#dataset" ) . onchange = event => { $ ( "#data-dataset" ) . value = event . target . value ; } ;
568- $ ( "#data-dataset" ) . onchange = event => { $ ( "#dataset" ) . value = event . target . value ; currentSourceId = null ; loadDataset ( ) ; } ;
611+ $ ( "#data-dataset" ) . onchange = event => navigateTo ( dataRoutePath ( event . target . value ) ) ;
612+ window . addEventListener ( "popstate" , ( ) => renderRoute ( ) ) ;
569613 document . addEventListener ( "keydown" , event => {
570614 if ( activeView !== "review" || ! currentReview || event . repeat ) return ;
571615 if ( event . key === "1" ) decide ( "pass" ) ;
572616 if ( event . key === "2" ) decide ( "fail" ) ;
573617 if ( event . key === "3" ) decide ( "pending" ) ;
574618 if ( event . key === " " ) { event . preventDefault ( ) ; const audio = $ ( "#review-audio" ) ; audio . paused ? audio . play ( ) : audio . pause ( ) ; }
575619 } ) ;
576- loadDatasets ( ) . then ( refresh ) ;
620+ renderRoute ( ) ;
577621 setInterval ( ( ) => {
578622 if ( activeView === "dashboard" ) refresh ( ) ;
579623 if ( activeView === "review" ) loadReviewStats ( ) ;
0 commit comments