openvibers/openvibe.chat Commits

Vibe's page says what Vibe thinks with when it is your own model ("Vibe thinks with llama3.1:8b on your computer (change)"), and the rail says there is no daily limit then (#212)

OP OpenVibers committed 18 days ago · 84d7b4c352 · 4 files, +23 −7

Changes

ddiff --git a/public/web/vibe.js b/public/web/vibe.js
iindex bfd9b35..13da9bc 100644
---- a/public/web/vibe.js
++++ b/public/web/vibe.js
@@@ -296,7 +296,7 @@
fetch('/api/chat/vibe?limit=100', { credentials: 'same-origin' }).then(function (r) { return r.ok ? r.json() : null; }).then(function (d) {
if (!d) return;
var allow = document.getElementById('vb-allow');
-- if (allow && d.allowance) allow.textContent = Math.max(0, d.allowance.per_day - d.allowance.used_today) + ' of ' + d.allowance.per_day + ' answers left today';
++ if (allow && d.allowance && !allow.hasAttribute('data-own')) allow.textContent = Math.max(0, d.allowance.per_day - d.allowance.used_today) + ' of ' + d.allowance.per_day + ' answers left today';
var list = document.querySelector('.vb-rail-list'); if (!list || !d.chats) return;
// Rebuilt from data with text nodes (the server's grouping, kept simple here: Today and Earlier).
var start = new Date(); start.setUTCHours(0, 0, 0, 0);
ddiff --git a/server/rooms/vibe.js b/server/rooms/vibe.js
iindex d91c5de..4a051ef 100644
---- a/server/rooms/vibe.js
++++ b/server/rooms/vibe.js
@@@ -63,6 +63,11 @@ function titleOf(text) {
const newSlug = () => `v-${crypto.randomBytes(8).toString('hex').slice(0, 12)}`;
++/** What Vibe thinks with, said to its person: "llama3.1:8b on your computer", "your openai key (gpt-4o-mini)". */
++const youOf = (cred) => (cred.provider === 'node'
++ ? `${cred.model || 'a model'} on your computer${cred.machine === 'any' ? 's' : ''}`
++ : `your ${cred.provider} key (${cred.model || 'its default model'})`);
++
/** The person's choice of model for Vibe: their own credential when they chose it and it is still stored. */
async function engineOf(user) {
if (!user || !user.subject_id || !(await db.get('SELECT 1 AS own FROM vibe_own_engine WHERE user_id = ?', [user.id]))) return { own: false, label: null };
@@@ -253,4 +258,4 @@ async function countReply(user) {
ON CONFLICT (user_id, day) DO UPDATE SET replies = vibe_usage.replies + 1`, [user.id, today()]);
}
--module.exports = { VIBE, MAX_CHATS, TITLE_MAX, SEARCH_MAX, DEFAULT_TITLE, MODES, VibeError, enabled, isVibeRoom, titleOf, start, startWork, work, createRoom, erase: (roomId) => db.tx(() => erase(roomId)), list, search, ownChat, rename, remove, retry, lastExchange, repliesToday, countReply, engineOf: async (user) => { const e = await engineOf(user); return { own: e.own, label: e.label }; }, setEngine };
++module.exports = { VIBE, MAX_CHATS, TITLE_MAX, SEARCH_MAX, DEFAULT_TITLE, MODES, VibeError, enabled, isVibeRoom, titleOf, start, startWork, work, createRoom, erase: (roomId) => db.tx(() => erase(roomId)), list, search, ownChat, rename, remove, retry, lastExchange, repliesToday, countReply, engineOf: async (user) => { const e = await engineOf(user); return { own: e.own, label: e.label, ...(e.own ? { you: youOf(e.cred) } : {}) }; }, setEngine };
ddiff --git a/server/web/vibe-pages.js b/server/web/vibe-pages.js
iindex 559e2f0..d85676e 100644
---- a/server/web/vibe-pages.js
++++ b/server/web/vibe-pages.js
@@@ -93,6 +93,7 @@ function rail({ chats, current, signedIn, allowance, query = '', here = null })
? groups.map((g) => `<p class="vb-group">${esc(g.label)}</p><ul class="vb-chats">${g.chats.map((c) => `<li><a class="vb-chat" href="/vibe/${esc(c.slug)}"${c.slug === current ? ' aria-current="page"' : ''}>${esc(c.name)}</a></li>`).join('')}</ul>`).join('')
: '<p class="vb-rail-note">Your chats with Vibe show up here.</p>');
const left = allowance ? Math.max(0, allowance.per_day - allowance.used_today) : null;
++ const own = allowance && allowance.engine && allowance.engine.own ? allowance.engine : null; // Vibe on their own model
return `<aside class="vb-rail" id="vb-rail" aria-label="Your chats with Vibe">
<div class="vb-rail-top">
<a class="vb-new" href="/vibe">${ICON.new}<span>New chat</span></a>
@@@ -110,7 +111,7 @@ ${signedIn ? `<a href="/vibe/memory" title="What Vibe remembers about you across
<a href="/rooms">${ICON.rooms}<span>Rooms and servers</span></a>
<a href="/">${ICON.globe}<span>Global chat</span></a>
</nav>
--${left != null ? `<p class="vb-allow" id="vb-allow">${left} of ${allowance.per_day} answers left today</p>` : ''}
++${own ? `<p class="vb-allow" id="vb-allow" data-own="1">No daily limit: Vibe thinks with ${esc(own.you)}</p>` : left != null ? `<p class="vb-allow" id="vb-allow">${left} of ${allowance.per_day} answers left today</p>` : ''}
</aside>`;
}
@@@ -140,7 +141,7 @@ const MODES = [
// The text files the attach button takes (read in the browser, vibe.js readText).
const TEXT_FILES = 'text/plain,text/markdown,text/csv,application/json,.txt,.md,.markdown,.csv,.tsv,.json,.log,.xml,.yaml,.yml,.toml,.ini,.html,.css,.js,.mjs,.ts,.py,.go,.rs,.java,.c,.h,.cpp,.rb,.php,.sh,.sql';
--function composer({ slug, signedIn, enabled, prefill = '' }) {
++function composer({ slug, signedIn, enabled, prefill = '', engine = null }) {
const action = !signedIn ? `/auth/login` : slug ? `/vibe/${esc(slug)}` : '/vibe';
// A picture (public/web/vibe.js): it goes to OpenVibe.Pics as the person, and Vibe sees it with the question. A text
// file (notes, a CSV, code) is read in the browser and goes into the message itself.
@@@ -158,11 +159,16 @@ ${attach}
<button class="vb-send" type="submit" aria-label="Send"${enabled ? '' : ' disabled'}>${ICON.send}</button>
<fieldset class="vb-modes" aria-label="What Vibe does with it">${MODES.map(([value, label, hint, placeholder], i) => `<label title="${esc(hint)}"><input type="radio" name="mode" value="${value}" data-placeholder="${esc(placeholder)}"${i ? '' : ' checked'}><span>${label}</span></label>`).join('')}</fieldset>
</form>
--<p class="vb-fine">${enabled ? 'Vibe can get things wrong. Check what matters.' : 'Vibe is resting right now. Try again later.'}</p>`;
++<p class="vb-fine">${!enabled ? 'Vibe is resting right now. Try again later.' : engine && engine.own ? `Vibe thinks with ${esc(engine.you)} (<a href="/settings#oc-ai-key">change</a>). It can get things wrong: check what matters.` : 'Vibe can get things wrong. Check what matters.'}</p>`;
}
function mountVibePages(router, { page, viewerOf, needUser, sameSite, formLimit, notice }) {
-- const allowanceOf = async (user) => ({ per_day: require('../config').ai.vibeRepliesPerDay, used_today: await vibe.repliesToday(user) });
++ const allowanceOf = async (user) => {
++ // Which model Vibe thinks with: the person's own key or computer when they chose it (no daily limit then).
++ let engine = null;
++ try { engine = await vibe.engineOf(user); } catch { engine = null; }
++ return { per_day: require('../config').ai.vibeRepliesPerDay, used_today: await vibe.repliesToday(user), engine };
++ };
/**
* "Ask Vibe" from any OpenVibe site (the shared navbar): ?ask=<the text they selected> and ?from=<the page's https
@@@ -220,7 +226,7 @@ ${head}
${error ? notice('error', error) : ''}
${room ? thread : hello}
</div>
--<div class="vb-dock">${composer({ slug: room && room.slug, signedIn: !!user, enabled, prefill: room ? '' : prefillOf(req.query || {}) })}</div>
++<div class="vb-dock">${composer({ slug: room && room.slug, signedIn: !!user, enabled, prefill: room ? '' : prefillOf(req.query || {}), engine: allowance && allowance.engine })}</div>
</section>
</div>`;
await page(req, res, status, {
ddiff --git a/test/room-ai-models.test.js b/test/room-ai-models.test.js
iindex b1f17d8..e29f3b5 100644
---- a/test/room-ai-models.test.js
++++ b/test/room-ai-models.test.js
@@@ -216,12 +216,17 @@ t('Vibe thinks with your own key or computer when you choose it: every chat of y
assert.ok(mine.length >= 1, `Vibe answered with their computer (${runs.length - before} runs)`);
const answer = await h.db.get('SELECT m.how FROM room_messages m JOIN rooms r ON r.id = m.room_id WHERE r.slug = ? AND m.user_id = 0 ORDER BY m.id DESC LIMIT 1', [next.body.room.slug]);
assert.ok(answer && String(answer.how).includes('Answered with llama3.1:8b on Di\'s computer'), `"How Vibe did this" names it (${answer && answer.how})`);
++ // Vibe's page says so, with a way to change it, and there is no daily limit then.
++ const vibePage = (await web('GET', `/vibe/${next.body.room.slug}`, di)).text;
++ assert.ok(vibePage.includes('Vibe thinks with llama3.1:8b on your computer (<a href="/settings#oc-ai-key">change</a>)'), 'the composer says what Vibe thinks with');
++ assert.ok(vibePage.includes('data-own="1">No daily limit: Vibe thinks with llama3.1:8b on your computer'), 'the rail, instead of the free answers left');
// The settings form says so and keeps it; unchecked, Vibe goes back to OpenVibe's model.
const page = await web('GET', '/settings', di);
assert.ok(page.text.includes('name="vibe" value="1" checked') && page.text.includes('· Vibe thinks with it'), 'the box, checked');
const saved = await web('POST', '/settings/ai-key', di, { provider: 'node', machine: MACHINE, model: 'qwen2.5:7b' });
assert.strictEqual(saved.status, 303);
assert.deepStrictEqual((await vibeApi('GET', '/engine')).body, { own: false, label: null }, 'unchecked: OpenVibe\'s model');
++ assert.ok((await web('GET', '/vibe', di)).text.includes('answers left today'), 'the free answers left again');
assert.strictEqual((await memberOf(first.body.room.slug)).engine, 'openvibe');
await web('POST', '/settings/ai-key', di, { provider: 'node', machine: MACHINE, model: 'qwen2.5:7b', vibe: '1' });
assert.strictEqual((await memberOf(next.body.room.slug)).engine_label, 'qwen2.5:7b on Di\'s computer', 'checked: their computer, the new model');