Compare commits
1 Commits
main
...
item/0x652
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b8f44119e |
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"schema": "elffuss-t2t/criteria@2",
|
||||
"issue": 10,
|
||||
"criteria": [
|
||||
{
|
||||
"index": 1,
|
||||
"tests": [
|
||||
"tests/acceptance/10/criteria.test.js::criterion 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"tests": [
|
||||
"tests/acceptance/10/criteria.test.js::criterion 2"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
const mod = await import('../../../web/js/model-cache.js')
|
||||
|
||||
function setNavigator(value) {
|
||||
Object.defineProperty(globalThis, 'navigator', { value, configurable: true, writable: true })
|
||||
}
|
||||
|
||||
test('criterion 1', async () => {
|
||||
const removed = []
|
||||
const deletedCaches = []
|
||||
const root = {
|
||||
async removeEntry(name, opts) { removed.push([name, opts]) },
|
||||
}
|
||||
setNavigator({ storage: { getDirectory: async () => root } })
|
||||
globalThis.caches = { delete: async (n) => { deletedCaches.push(n); return true } }
|
||||
|
||||
await mod.clearModelCache()
|
||||
|
||||
assert.ok(removed.some(([n]) => n === 'elffuss-models'))
|
||||
assert.deepEqual(deletedCaches, ['elffuss-models-v1'])
|
||||
})
|
||||
|
||||
test('criterion 2', async () => {
|
||||
let calls = 0
|
||||
setNavigator({ storage: { getDirectory: async () => { calls++; throw new Error('sin OPFS') } } })
|
||||
globalThis.caches = { delete: async () => true }
|
||||
|
||||
await assert.doesNotReject(mod.clearModelCache())
|
||||
assert.equal(calls, 1)
|
||||
})
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"schema": "elffuss-t2t/criteria@2",
|
||||
"issue": 11,
|
||||
"criteria": [
|
||||
{
|
||||
"index": 1,
|
||||
"tests": [
|
||||
"tests/acceptance/11/criteria.test.js::criterion 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import * as mod from '../../../web/js/skills.js';
|
||||
|
||||
test('criterion 1', () => {
|
||||
const withCrlf = mod.parseSkill('---\r\nname: demo\r\ndescription: x\r\n---\r\nBody');
|
||||
assert.deepEqual(withCrlf, { name: 'demo', description: 'x', content: 'Body' });
|
||||
|
||||
const fallback = mod.parseSkill('Body only', 'fallback');
|
||||
assert.deepEqual(fallback, { name: 'fallback', description: '', content: 'Body only' });
|
||||
});
|
||||
30
tests/acceptance/3/CRITERIA.json
Normal file
30
tests/acceptance/3/CRITERIA.json
Normal file
@@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
58
tests/acceptance/3/criteria.test.js
Normal file
58
tests/acceptance/3/criteria.test.js
Normal file
@@ -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.<name>(...)
|
||||
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:<key>` 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.`);
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"schema": "elffuss-t2t/criteria@2",
|
||||
"issue": 7,
|
||||
"criteria": [
|
||||
{
|
||||
"index": 1,
|
||||
"tests": [
|
||||
"tests/acceptance/7/criteria.test.js::criterion 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { MODELS } from '../../../web/js/providers/litert.js'
|
||||
|
||||
// Minimal stand-in for the page so the lab module can run and render its lists.
|
||||
class Node {
|
||||
constructor(tag) { this.tag = tag; this.children = []; this.style = {}; this.className = ''; this.textContent = ''; this.innerHTML = ''; this.scrollTop = 0; this.scrollHeight = 0 }
|
||||
appendChild(c) { this.children.push(c); return c }
|
||||
append(...cs) { cs.forEach(c => this.children.push(c)) }
|
||||
querySelector(s) { return this.slot(s) }
|
||||
slot(s) { return slots.get(s) }
|
||||
}
|
||||
const slots = new Map()
|
||||
const pick = s => { if (!slots.has(s)) { const n = new Node('slot'); n.id = s.slice(1); slots.set(s, n) } return slots.get(s) }
|
||||
Node.prototype.querySelector = function (s) { return pick(s) }
|
||||
globalThis.document = { querySelector: pick, createElement: t => new Node(t) }
|
||||
const store = new Map()
|
||||
globalThis.localStorage = { getItem: k => (store.has(k) ? store.get(k) : null), setItem: (k, v) => store.set(k, String(v)), removeItem: k => store.delete(k) }
|
||||
globalThis.window = { open() {} }
|
||||
|
||||
await import('../../../web/js/lab.js')
|
||||
|
||||
function titlesOf(node, out = []) {
|
||||
if (node.className === 't') out.push(node.textContent)
|
||||
node.children.forEach(c => titlesOf(c, out))
|
||||
return out
|
||||
}
|
||||
|
||||
test('criterion 1', () => {
|
||||
const titles = titlesOf(pick('#brains'))
|
||||
assert.equal(titles.length, 4)
|
||||
assert.equal(titles[2], MODELS['gemma-e2b'].label)
|
||||
assert.equal(titles[3], MODELS['gemma-e4b'].label)
|
||||
assert.ok(titles[0].startsWith('Reglas'))
|
||||
assert.ok(titles[1].startsWith('Elffuss LM'))
|
||||
})
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"schema": "elffuss-t2t/criteria@2",
|
||||
"issue": 9,
|
||||
"criteria": [
|
||||
{
|
||||
"index": 1,
|
||||
"tests": [
|
||||
"tests/acceptance/9/criteria.test.js::criterion 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
const mod = await import('../../../web/js/conversations.js')
|
||||
|
||||
const convOf = content => ({ title: null, agent: { history: [{ role: 'user', content }] } })
|
||||
|
||||
test('criterion 1', () => {
|
||||
const emoji = '\u{1F600}'
|
||||
const a39 = 'a'.repeat(39)
|
||||
const title = mod.titleFor(convOf(a39 + emoji + 'bcd'))
|
||||
assert.equal(title, a39 + emoji + '…')
|
||||
assert.equal(title.isWellFormed(), true)
|
||||
|
||||
assert.equal(mod.titleFor(convOf('a'.repeat(60))), 'a'.repeat(40) + '…')
|
||||
})
|
||||
@@ -41,8 +41,7 @@ export function titleFor(conv) {
|
||||
// no debe filtrarse al título de la pestaña — se muestra el texto limpio.
|
||||
const raw = firstUser.content.startsWith(GOAL_PREFIX) ? firstUser.content.slice(GOAL_PREFIX.length) : firstUser.content;
|
||||
const t = cleanTitle(raw);
|
||||
const chars = Array.from(t);
|
||||
return chars.length > 40 ? chars.slice(0, 40).join('') + '…' : t || 'Nueva conversación';
|
||||
return t.length > 40 ? t.slice(0, 40) + '…' : t || 'Nueva conversación';
|
||||
}
|
||||
|
||||
export function setProvider(mod) {
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
// el de juegos ejecuta el juego generado y lo puntúa mirándolo correr.
|
||||
import { DEFAULTS, packHistoryACER, estimateTokens } from './acer-core.js';
|
||||
import { TASKS } from './swe-tasks.js';
|
||||
import { MODELS } from './providers/litert.js';
|
||||
|
||||
const $ = s => document.querySelector(s);
|
||||
const el = (t, c, x) => { const n = document.createElement(t); if (c) n.className = c; if (x != null) n.textContent = x; return n; };
|
||||
@@ -69,7 +68,8 @@ const BENCHES = [
|
||||
const BRAINS = [
|
||||
['rules', 'Reglas (sin GPU)', 'Determinista. Valida el arnés sin bajar pesos.'],
|
||||
['onnx', 'Elffuss LM (CPU/wasm)', 'LFM2.5-1.2B por transformers.js.'],
|
||||
...['gemma-e2b', 'gemma-e4b'].map(k => ['litert:' + k, MODELS[k].label, MODELS[k].tag]),
|
||||
['litert:gemma-e2b', 'Gemma-4 E2B (~2 GB)', 'WebGPU.'],
|
||||
['litert:gemma-e4b', 'Gemma-4 E4B (~3 GB)', 'WebGPU. El mejor.'],
|
||||
];
|
||||
|
||||
// ─────────────────────────── consola ───────────────────────────
|
||||
|
||||
@@ -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)' });
|
||||
|
||||
@@ -48,8 +48,4 @@ export async function clearModelCache() {
|
||||
if (navigator.serviceWorker && navigator.serviceWorker.controller)
|
||||
navigator.serviceWorker.controller.postMessage('clear-models');
|
||||
try { await caches.delete('elffuss-models-v1'); } catch { /* */ }
|
||||
try {
|
||||
const { clearAll } = await import('./runtime/model-store.js');
|
||||
await clearAll();
|
||||
} catch { /* sin almacén OPFS: nada que vaciar */ }
|
||||
}
|
||||
|
||||
@@ -131,7 +131,6 @@ export async function installFromRepo(entry) {
|
||||
|
||||
// SKILL.md → { name, description, content }
|
||||
export function parseSkill(md, fallbackName = 'skill') {
|
||||
md = md.replace(/\r\n/g, '\n');
|
||||
const fm = md.match(/^---\n([\s\S]*?)\n---/);
|
||||
const meta = fm?.[1] || '';
|
||||
const name = meta.match(/^name:\s*(.+)$/m)?.[1]?.trim() || fallbackName;
|
||||
|
||||
Reference in New Issue
Block a user