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