From f39ee6ebd8f2978951a420d6582625f19c54f967 Mon Sep 17 00:00:00 2001 From: blomios Date: Tue, 6 Oct 2026 20:31:15 +0200 Subject: [PATCH] feat(laya): clarify unavailable runtime download flow (#28) Explain how to start the optional Laya runtime when model download fails because the service is unavailable, while keeping generic failures concise. Co-Authored-By: Claude Opus 4.8 --- public/index.html | 2 +- public/js/services/layaStatusPresentation.js | 9 +++++-- test/layaStatusPresentation.test.js | 25 ++++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/public/index.html b/public/index.html index e6d5c5a..5562954 100644 --- a/public/index.html +++ b/public/index.html @@ -1212,7 +1212,7 @@ - + diff --git a/public/js/services/layaStatusPresentation.js b/public/js/services/layaStatusPresentation.js index e017165..3da9859 100644 --- a/public/js/services/layaStatusPresentation.js +++ b/public/js/services/layaStatusPresentation.js @@ -3,6 +3,7 @@ if (typeof module === 'object' && module.exports) module.exports = service; if (root) root.LayaStatusPresentation = service; })(typeof globalThis !== 'undefined' ? globalThis : this, function () { + const START_COMMAND = 'docker compose --profile laya up -d'; const PHASE_LABELS = { idle: 'Model not downloaded', checking: 'Checking local AI model…', @@ -39,6 +40,10 @@ const failed = phase === 'failed'; const modelPresent = status.modelPresent === true; const runtimeAvailable = status.runtimeAvailable === true; + const runtimeUnavailable = status.runtimeAvailable === false; + const runtimeHelp = runtimeUnavailable + ? `Nodecast cannot reach the optional Laya runtime. Standard matching remains available.\nStart the Laya service, then try again:\n${START_COMMAND}` + : ''; const ready = modelPresent && runtimeAvailable && !busy && !failed; const statusLabel = ready ? 'Ready' @@ -64,13 +69,13 @@ totalBytes: status.totalBytes !== null && status.totalBytes !== undefined && Number.isFinite(Number(status.totalBytes)) ? Number(status.totalBytes) : null, errorMessage: failed ? (error.message || 'The local AI model could not be downloaded.') : '', - errorDetail: failed ? error.detail : '', + errorDetail: failed ? [error.detail, runtimeHelp].filter(Boolean).join('\n\n') : '', downloadLabel: failed ? 'Retry download' : 'Download model', showDownload: !busy && !modelPresent && (phase === 'idle' || failed), showRemove: !busy && modelPresent, showUseStandard: failed && mode !== 'algo', showCheckAgain: !busy && modelPresent && !runtimeAvailable, - showStartCommand: !busy && modelPresent && !runtimeAvailable, + showStartCommand: !busy && runtimeUnavailable, availabilityMessage: modelPresent && !runtimeAvailable ? 'The model is installed, but the Local AI service is not running. Standard channel matching is being used.' : '' diff --git a/test/layaStatusPresentation.test.js b/test/layaStatusPresentation.test.js index 79cc08a..ebb7c75 100644 --- a/test/layaStatusPresentation.test.js +++ b/test/layaStatusPresentation.test.js @@ -36,6 +36,31 @@ test('failed downloads expose retry, safe details and Standard fallback', () => assert.equal(view.errorDetail, 'The optional Laya runtime is unavailable.'); }); +test('failed Laya downloads explain how to start the unavailable runtime', () => { + const view = presentLayaStatus({ + phase: 'failed', + modelPresent: false, + runtimeAvailable: false, + error: { message: 'Model download failed', detail: 'The optional Laya runtime is unavailable.' } + }, 'hybrid'); + assert.match(view.errorDetail, /Nodecast cannot reach the optional Laya runtime/); + assert.match(view.errorDetail, /Standard matching remains available/); + assert.match(view.errorDetail, /Start the Laya service, then try again:/); + assert.match(view.errorDetail, /docker compose --profile laya up -d/); + assert.equal(view.showStartCommand, true); +}); + +test('generic Laya failures do not suggest Docker Compose', () => { + const view = presentLayaStatus({ + phase: 'failed', + modelPresent: false, + runtimeAvailable: true, + error: { message: 'Model download failed', detail: 'Laya model operation failed (HTTP 500).' } + }, 'hybrid'); + assert.doesNotMatch(view.errorDetail, /docker compose/); + assert.equal(view.showStartCommand, false); +}); + test('ready models expose removal without download actions', () => { const view = presentLayaStatus({ phase: 'idle', modelPresent: true, runtimeAvailable: true, sizeBytes: 678323814 }); assert.equal(view.statusLabel, 'Ready');