diff --git a/src/web/app.js b/src/web/app.js index f77268f..76634cd 100644 --- a/src/web/app.js +++ b/src/web/app.js @@ -22,6 +22,7 @@ const trainingError = document.getElementById('trainingError'); const trainingImportText = document.getElementById('trainingImportText'); const trainingImportBtn = document.getElementById('trainingImportBtn'); const trainingExportBtn = document.getElementById('trainingExportBtn'); +const trainingReloadBtn = document.getElementById('trainingReloadBtn'); const trainingAttemptStatus = document.getElementById('trainingAttemptStatus'); const trainingAnnotationText = document.getElementById('trainingAnnotationText'); const trainingSaveAnnotationBtn = document.getElementById('trainingSaveAnnotationBtn'); @@ -425,6 +426,9 @@ function setTrainingError(error) { } function markTrainingLoading() { + if (trainingReloadBtn) { + trainingReloadBtn.disabled = true; + } if (trainingCasesList) { trainingCasesList.setAttribute('aria-busy', 'true'); setText(trainingCasesList, 'Loading case list. Next step: review available cases or import a case package.'); @@ -433,9 +437,13 @@ function markTrainingLoading() { trainingProgress.setAttribute('aria-busy', 'true'); setText(trainingProgress, 'Loading progress. Next step: submit an attempt to update progress.'); } + setTrainingAttemptStatus('Training data loading. Next step: wait for cases and progress, then choose a case or import a package.'); } function markTrainingLoaded() { + if (trainingReloadBtn) { + trainingReloadBtn.disabled = false; + } if (trainingCasesList) trainingCasesList.setAttribute('aria-busy', 'false'); if (trainingProgress) trainingProgress.setAttribute('aria-busy', 'false'); } @@ -638,6 +646,29 @@ function renderTrainingProgress(items) { }); } +function renderTrainingLoadFailure(error) { + if (trainingCasesList) { + clearNode(trainingCasesList); + const empty = document.createElement('div'); + empty.className = 'metric training-empty training-state-error'; + empty.setAttribute('role', 'listitem'); + setText(empty, 'Case list unavailable. Next step: reload training data or import a case package.'); + trainingCasesList.appendChild(empty); + } + if (trainingProgress) { + clearNode(trainingProgress); + const empty = document.createElement('div'); + empty.className = 'metric training-empty training-state-error'; + empty.setAttribute('role', 'listitem'); + setText(empty, 'Progress unavailable. Next step: reload training data, then submit an attempt to update progress.'); + trainingProgress.appendChild(empty); + } + renderTrainingCaseDetail(null); + setSelectedTrainingCase(null); + setTrainingAttemptControls(false); + setTrainingAttemptStatus('Training data request failed. Next step: reload training data or import a case package.'); +} + async function loadTrainingDashboard() { if (!trainingCasesList || !trainingProgress) return; @@ -651,7 +682,9 @@ async function loadTrainingDashboard() { renderTrainingCases(cases.items || []); renderTrainingProgress(progress.items || []); + setTrainingAttemptStatus('Training data loaded. Next step: choose View detail or Start attempt, or import a case package if the list is empty.'); } catch (error) { + renderTrainingLoadFailure(error); setTrainingError(error); } finally { markTrainingLoaded(); @@ -889,6 +922,9 @@ if (trainingImportBtn) { if (trainingExportBtn) { trainingExportBtn.addEventListener('click', exportTrainingCases); } +if (trainingReloadBtn) { + trainingReloadBtn.addEventListener('click', loadTrainingDashboard); +} configureAutoRefresh(); loadTrainingDashboard(); refreshDashboard(); diff --git a/src/web/index.html b/src/web/index.html index 55ee831..3031738 100644 --- a/src/web/index.html +++ b/src/web/index.html @@ -70,7 +70,8 @@