openvibers/openvibe.bot Commits

A machine's models say how fast they answer there (Node v0.29.0's ai_tokens_per_s: "about 15 tokens a second", Answering now; computer.ai_speed in the API); a link styled as a button centres its wrapped text (#191)

OP OpenVibers committed 17 days ago · ecbcac58e6 · 5 files, +18 −0

Changes

ddiff --git a/public/panel.css b/public/panel.css
iindex 652a57b..6d9959d 100644
---- a/public/panel.css
++++ b/public/panel.css
@@@ -25,6 +25,7 @@ button, .button, input, select { font: inherit; min-height: var(--tap); min-widt
button, .button {
display: inline-flex; align-items: center; justify-content: center; gap: 0.45rem; padding: 0 1rem;
border: 1px solid var(--line); background: var(--raised); color: var(--text); text-decoration: none; cursor: pointer;
++ text-align: center; /* a link styled as a button wraps its text like a button does */
touch-action: manipulation; -webkit-tap-highlight-color: transparent; transition: background 0.12s, border-color 0.12s, transform 0.06s;
}
button:hover:not(:disabled), .button:hover { border-color: #46505d; }
ddiff --git a/server/api/v1.js b/server/api/v1.js
iindex 3bb15a0..2dd29e2 100644
---- a/server/api/v1.js
++++ b/server/api/v1.js
@@@ -438,6 +438,9 @@ function v1Router({ domain, apiAuth, limits, hub, jobs: jobService, config, subm
waiting: Number.isInteger(sensors.computer_waiting) ? sensors.computer_waiting : null,
// Desks running now (the Node's telemetry): where an agent's next desk has room (Desks plan D6, placement).
desks_running: Number.isInteger(sensors.computer_desks) ? sensors.computer_desks : null,
++ // How fast its last model answered there (Node v0.29.0's ai plugin): for choosing where a model runs.
++ ai_speed: typeof sensors.ai_tokens_per_s === 'number' && sensors.ai_tokens_per_s > 0 && typeof sensors.ai_model === 'string'
++ ? { model: sensors.ai_model.slice(0, 120), tokens_per_s: sensors.ai_tokens_per_s } : null,
} : null,
};
};
ddiff --git a/server/web/render.js b/server/web/render.js
iindex 0f0035b..75b41d0 100644
---- a/server/web/render.js
++++ b/server/web/render.js
@@@ -753,7 +753,10 @@ function renderMachinesPage({ machines = [], pairing = null }) {
const computerRow = (m) => {
const c = computerOf(m);
if (!c) return '';
++ // "about 15 tokens a second" (with which model, when it offers several), measured there by the ai plugin.
++ const speed = m.ai_speed ? `about ${m.ai_speed.tokens_per_s >= 10 ? Math.round(m.ai_speed.tokens_per_s) : m.ai_speed.tokens_per_s} tokens a second${c.ai.models.length > 1 ? ` with ${m.ai_speed.model}` : ''}` : '';
const aiRow = c.ai ? `<div class="machine-ai"><span class="pill">${c.ai.ready ? 'Models' : 'Models: no server answers'}</span>${c.ai.models.length ? `<span class="muted">${esc(c.ai.models.join(', '))}</span>` : ''}`
++ + (m.ai_busy ? ' <span class="pill">Answering now</span>' : '') + (speed ? `<span class="muted">${esc(speed)}</span>` : '')
+ `<form method="post" action="/machines/${esc(m.id)}/ai-models"><input type="hidden" name="on" value="${m.ai_models ? '0' : '1'}">`
+ `<button type="submit" class="button quiet" aria-pressed="${m.ai_models ? 'true' : 'false'}">${m.ai_models ? 'Stop my AI using this computer\'s models' : 'Let my AI use this computer\'s models'}</button></form>`
// Turned on: OpenVibe.Chat's "Your AI key" comes filled in with this computer and its first model (saved only there).
ddiff --git a/server/web/routes.js b/server/web/routes.js
iindex c46b9e4..3388cad 100644
---- a/server/web/routes.js
++++ b/server/web/routes.js
@@@ -581,6 +581,10 @@ function createWebRoutes(config, { domain = null, sim = null, onvif = null, limi
computer_screen_stream: sensors.computer_screen_stream === 'live' ? 'live' : 'off',
computer_desks: Number.isInteger(sensors.computer_desks) ? sensors.computer_desks : null,
ai_models: !!d.ai_models,
++ // How fast its last model answered there (Node v0.29.0's ai plugin), and whether one is answering now.
++ ai_speed: typeof sensors.ai_tokens_per_s === 'number' && sensors.ai_tokens_per_s > 0 && typeof sensors.ai_model === 'string'
++ ? { model: sensors.ai_model.slice(0, 120), tokens_per_s: sensors.ai_tokens_per_s } : null,
++ ai_busy: sensors.ai_busy === 1,
};
}));
// ── The live screen (plan T14): the owner watches their machine's screen on their own private stream ──
ddiff --git a/test/machine-computer.test.js b/test/machine-computer.test.js
iindex 39986e9..c54ea0e 100644
---- a/test/machine-computer.test.js
++++ b/test/machine-computer.test.js
@@@ -436,12 +436,19 @@ const { boot, check, done } = require('./helpers/app');
assert.strictEqual((await cmd(own.deviceId, { kind: 'ai', value: { model: 'qwen2.5-1.5b-instruct', messages: [{ role: 'user', content: 'hi' }] } }, { user: alex })).json.result, 'ack');
const noExec = await cmd(own.deviceId, { kind: 'exec', value: { argv: ['id'] } }, { user: alex });
assert.strictEqual(noExec.json.code, 'bot.computer_off', 'no terminal without the computer plugin');
++ // Node v0.29.0's ai plugin says how fast its last model answered there (after the 2 Hz telemetry window).
++ await t.wait(600);
++ own.c.send(frame('telemetry', { sensors: { ai_busy: 0, ai_model: 'qwen2.5-1.5b-instruct', ai_tokens_per_s: 14.6 } }));
++ await t.wait(600);
++ const fast = (await t.call('GET', '/api/v1/machines', { user: alex })).json.machines.find((m) => m.id === own.deviceId);
++ assert.deepStrictEqual(fast.computer.ai_speed, { model: 'qwen2.5-1.5b-instruct', tokens_per_s: 14.6 }, 'its speed, in the API');
let ownCard = '';
for (let i = 0; i < 20 && !ownCard.includes('qwen2.5-1.5b-instruct'); i++) {
if (i) await t.wait(100);
ownCard = await (await fetch(`${t.base}/machines`, { headers: cookie(alex) })).text();
}
assert.ok(ownCard.includes('qwen2.5-1.5b-instruct'), 'the card shows its models');
++ assert.ok(ownCard.includes('about 15 tokens a second'), 'and how fast they answer there');
own.c.close();
// The machines page: its models and the switch.
const html = await (await fetch(`${t.base}/machines`, { headers: cookie(alex) })).text();