fix: enable Laya language detection and harden live analysis (#18 #19 #20)

Ensure Docker images keep their container-built native dependencies, wait for the Laya sidecar healthcheck, expose accurate Laya readiness, and treat empty AI language batches as fallback failures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
blomios
2026-10-05 23:10:31 +02:00
parent 9cc86485cb
commit 1abc56c90e
7 changed files with 258 additions and 5 deletions

View File

@ -8,6 +8,7 @@ const MAX_BATCH_BYTES = 64 * 1024;
const MAX_TEXT_LENGTH = 4096;
const ALLOWED_MODELS = new Set(['english', 'multilingual', 'typed-decisions']);
const DOWNLOAD_TIMEOUT_MS = 30 * 60 * 1000;
const BATCH_TIMEOUT_MS = 2 * 60 * 1000;
class LayaRequestError extends Error {
constructor(message, statusCode = 503) {
@ -128,6 +129,9 @@ class LayaService {
? require('../db/sqlite').layaLanguageCache
: options.cache;
this.timeoutMs = options.timeoutMs || 3000;
this.batchTimeoutMs = options.batchTimeoutMs
|| Number(process.env.LAYA_BATCH_TIMEOUT_MS)
|| BATCH_TIMEOUT_MS;
this.downloadTimeoutMs = options.downloadTimeoutMs || DOWNLOAD_TIMEOUT_MS;
this.progressIntervalMs = options.progressIntervalMs || 500;
this.downloadState = initialDownloadState();
@ -152,6 +156,9 @@ class LayaService {
return data;
} catch (error) {
if (error instanceof LayaRequestError) throw error;
if (error?.name === 'AbortError') {
throw new LayaRequestError(`Laya runtime request timed out after ${timeoutMs} ms`, 504);
}
throw new LayaRequestError('Laya runtime is unavailable');
} finally {
clearTimeout(timeout);
@ -168,9 +175,27 @@ class LayaService {
if (!this.downloadState.downloading && this.downloadState.phase !== 'failed' && modelPresent) {
this.downloadState.phase = 'ready';
}
return { ...this.downloadState, runtimeAvailable: true, modelPresent, sizeBytes, runtime };
return {
...this.downloadState,
runtimeAvailable: true,
modelPresent,
ready: modelPresent,
sizeBytes,
runtime,
runtimeError: null
};
} catch (error) {
return { ...this.downloadState, runtimeAvailable: false, modelPresent, sizeBytes };
return {
...this.downloadState,
runtimeAvailable: false,
modelPresent,
ready: false,
sizeBytes,
runtimeError: {
message: error.message,
detail: 'Start the Laya sidecar and verify LAYA_BASE_URL.'
}
};
}
}
@ -287,6 +312,7 @@ class LayaService {
if (misses.length) {
const response = await this.request('/v1/systemone/batch', {
method: 'POST',
timeoutMs: this.batchTimeoutMs,
body: JSON.stringify({
states: misses.map(({ item }) => item.text),
model,
@ -323,6 +349,7 @@ class LayaService {
}
const response = await this.request('/v1/systemone/batch', {
method: 'POST',
timeoutMs: this.batchTimeoutMs,
body: JSON.stringify({
states: pairs.map(pair => ({
left: sanitizeIdentityDescriptor(pair.left),
@ -355,6 +382,7 @@ class LayaService {
module.exports = {
ALLOWED_MODELS,
BATCH_TIMEOUT_MS,
LayaRequestError,
LayaService,
MODEL_DIR,