diff --git a/blobs/0xae67138b61f522457e676b6271112bdce5f3b071559410a5fbd4ff53cdb3f2c9 b/blobs/0xae67138b61f522457e676b6271112bdce5f3b071559410a5fbd4ff53cdb3f2c9 new file mode 100644 index 0000000..2d2cc20 --- /dev/null +++ b/blobs/0xae67138b61f522457e676b6271112bdce5f3b071559410a5fbd4ff53cdb3f2c9 @@ -0,0 +1,130 @@ +diff --git a/tests/acceptance/3/CRITERIA.json b/tests/acceptance/3/CRITERIA.json +new file mode 100644 +index 0000000000000000000000000000000000000000..42f8409e63d54e2fd7595134440d5cbdb98b71c3 +--- /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 0000000000000000000000000000000000000000..67c9f2f26a193a7663c9c42772e17057637fc082 +--- /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 2a9564d1560e85c511188b49702ec8120c454fa1..800b113bca1bedf974cb64387373e14645442e90 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)' }); diff --git a/events/0000000205.json b/events/0000000205.json new file mode 100644 index 0000000..25a92aa --- /dev/null +++ b/events/0000000205.json @@ -0,0 +1 @@ +{"data":{"cites":[],"envelope":{"delegation":{"identity":"0xe0425bed061809c5ce17450fe47dc970090a0938","nonce":"1790923270","project":"0x0000000000000000000000000000000000000000000000000000000000000000","scopes":3,"sessionX":"0x965ee667b228d5242d2d86989eea6d4879c4ad1de748a7bcae861f2baff69398","sessionY":"0x4109e51c15fd0376e2f76a2b5cf293cdf9fb69f7ac1f617c13ddbb117fe34669","validFrom":"1790923270","validUntil":"1791528070"},"delegationSignature":"0x4322f7ca1df9e93982471f2e75c20c605df23e92aa5da74809aeaa8fc498579623912c8227ebe106c0529f958b988700ce2aa17a2667ef65de01df41d39cc5261c","kind":"session","signature":"0x2b4524422b2950ef807687c78f9f2af571481e9329be5db5b4c47f9e01a7aab9552f76287ef83a6adf762083719ba61379a3f09b04554f649fa5dc5ee17d4804"},"mainHead":null,"message":{"author":"0xe0425bed061809c5ce17450fe47dc970090a0938","baseCommit":"0x42a4a9a67cda0da050ec0e73e6294537cce1e541","basePartial":"0x0000000000000000000000000000000000000000000000000000000000000000","citesHash":"0x0000000000000000000000000000000000000000000000000000000000000000","issueId":"0x848d97f788f6a99d51bb5fed8b5045346c66a05c693b03fbe211bd811ee80443","issuedAt":"1790923838","lines":11,"nonce":"1790923838000","patchHash":"0xae67138b61f522457e676b6271112bdce5f3b071559410a5fbd4ff53cdb3f2c9","project":"0x16d6e75341c5f21003a4d9e89b416a545f7335094d7df139fd20942c9b7fb2de","repo":"0xd822e541c0173492ca0725a08935bcd8eaa756a9559600a9427d0daac625ead2"},"pr":"elffuss/code:item/0x652a4c8d8d614a77a0eafb2df15ecc122e7733c2d0939ac32325dd8c8b78002d"},"eventHash":"0x11fcdeedf7fa01c2f1d8f932f90435f49e5cf5e06b5a680c9f233f375255de09","gatewaySignature":"0x687b94888dea30c619db8cf507e89fc3b94d39dc5c5b11cf07510c80965f74cb48541789d697da21f76cc0d4d19c431c0f1fec3cc7f6801c7fcee5ac28b6ac2f1b","prev":"0x008ef3f90272893cd76d2e05e30deee5010601675f220c4e77f0e77269f70051","seq":205,"ts":1790923838,"type":"solution","v":2}