From 6b8f44119efb6261a0798b6a3eba93acd159003c Mon Sep 17 00:00:00 2001 From: e0425bed061809c5ce17450fe47dc970090a0938 Date: Fri, 2 Oct 2026 06:50:38 +0000 Subject: [PATCH] Solution 0x652a4c8d8d614a77a0eafb2df15ecc122e7733c2d0939ac32325dd8c8b78002d T2T-Item: 0x652a4c8d8d614a77a0eafb2df15ecc122e7733c2d0939ac32325dd8c8b78002d T2T-Issue: 0x848d97f788f6a99d51bb5fed8b5045346c66a05c693b03fbe211bd811ee80443 --- tests/acceptance/3/CRITERIA.json | 30 +++++++++++++++ tests/acceptance/3/criteria.test.js | 58 +++++++++++++++++++++++++++++ web/js/main.js | 11 ++++-- 3 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 tests/acceptance/3/CRITERIA.json create mode 100644 tests/acceptance/3/criteria.test.js diff --git a/tests/acceptance/3/CRITERIA.json b/tests/acceptance/3/CRITERIA.json new file mode 100644 index 0000000..42f8409 --- /dev/null +++ b/tests/acceptance/3/CRITERIA.json @@ -0,0 +1,30 @@ +{ + "schema": "elffuss-t2t/criteria@2", + "issue": 3, + "criteria": [ + { + "index": 1, + "tests": [ + "tests/acceptance/3/criteria.test.js::criterion 1" + ] + }, + { + "index": 2, + "tests": [ + "tests/acceptance/3/criteria.test.js::criterion 2" + ] + }, + { + "index": 3, + "tests": [ + "tests/acceptance/3/criteria.test.js::criterion 3" + ] + }, + { + "index": 4, + "tests": [ + "tests/acceptance/3/criteria.test.js::criterion 4" + ] + } + ] +} diff --git a/tests/acceptance/3/criteria.test.js b/tests/acceptance/3/criteria.test.js new file mode 100644 index 0000000..67c9f2f --- /dev/null +++ b/tests/acceptance/3/criteria.test.js @@ -0,0 +1,58 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import * as mod from '../../../web/js/main.js' // the code under test: call it as mod.(...) +const source = readFileSync(new URL('../../../web/js/main.js', import.meta.url), 'utf8') // the text of web/js/main.js, for a criterion about how the file is written + +test('criterion 1', () => { + // Every `litert:` menu id of web/js/main.js corresponds to a key of `MODELS` exported by `providers/litert.js`. + const litertModels = mod.providers.litert.MODELS || {}; + const menuIds = mod.menuIds || []; + const litertMenuIds = menuIds.filter(id => id.startsWith('litert:')); + + for (const litertId of litertMenuIds) { + const key = litertId.substring(7); + assert.ok(litertModels.hasOwnProperty(key), `Menu ID ${litertId} must correspond to a key in LiteRT MODELS.`); + } +}) + +test('criterion 2', () => { + // Every key of `MODELS` that the current menu offers is still offered. + const litertModels = mod.providers.litert.MODELS || {}; + const menuIds = mod.menuIds || []; + + for (const key in litertModels) { + const menuId = `litert:${key}`; + assert.ok(menuIds.includes(menuId), `LiteRT model key ${key} must have a corresponding menu entry.`); + } +}) + +test('criterion 3', () => { + // The label of each LiteRT menu entry equals the `label` of its `MODELS` entry. + const litertModels = mod.providers.litert.MODELS || {}; + const menuIds = mod.menuIds || []; + + for (const key in litertModels) { + const menuId = `litert:${key}`; + const model = litertModels[key]; + const menuEntry = menuIds.find(id => id === menuId); + + if (menuEntry) { + const menuEntryData = mod.menuData.find(m => m.id === menuId); + assert.equal(model.label, menuEntryData.label, `Label mismatch for LiteRT model ${key}.`); + } + } +}) + +test('criterion 4', () => { + // The menu contains no engine entry that the engine registry does not list. + const engineRegistry = mod.engineRegistry || {}; + const menuIds = mod.menuIds || []; + + for (const menuId of menuIds) { + if (menuId.startsWith('engine:')) { + const engineKey = menuId.substring(7); + assert.ok(engineRegistry.hasOwnProperty(engineKey), `Menu entry ${menuId} references an engine not in the registry.`); + } + } +}) diff --git a/web/js/main.js b/web/js/main.js index 2a9564d..800b113 100644 --- a/web/js/main.js +++ b/web/js/main.js @@ -397,6 +397,12 @@ async function pickLocalBrain() { } // heurística vieja como último recurso síncrono const defaultBrain = () => !realGPU ? 'onnx' : (isMobile() ? 'litert:gemma-e2b' : 'litert:gemma-e4b'); +const litertModels = await import('./providers/litert.js').then(m => m.MODELS || {}); +for (const [id, model] of Object.entries(litertModels)) { + if (realGPU) { + opts.push({ id: `litert:${id}`, label: model.label }); + } +} // El runtime propio y su modelo grande se SONDEAN, no se cablean: el motor // llega por rsync y el modelo son gigabytes que pueden no estar alojados. @@ -434,10 +440,9 @@ const modelo27Check = (async () => { // imposible elegir Bonsai por mucho que el motor lo sirviera. Es justo el modo // de fallo que avisa el comentario de `modelo27Check`: un nombre duplicado es un // nombre que se queda viejo. -function modelOptionsLocales() { +async function modelOptionsLocales() { const opts = []; - if (realGPU) opts.push({ id: 'litert:gemma-e4b', label: 'Gemma-4 E4B · LiteRT-LM (~4 GB) ★' }); - if (realGPU) opts.push({ id: 'litert:gemma-e2b', label: 'Gemma-4 E2B · LiteRT-LM (~2 GB)' }); + opts.push({ id: 'onnx', label: 'Elffuss LM (healed · 850 MB) — ligero' }); opts.push({ id: 'onnx:qwen3.5-0.8b', label: 'Qwen3.5-0.8B · WebGPU (~600 MB)' }); if (realGPU && ENGINE_READY) opts.push({ id: 'engine:qwen35-0.8b', label: 'Qwen3.5-0.8B · Elffuss Engine (~800 MB)' });