openvibers/openvibe.design Commits

The create model list is the Exchange catalogue, not only AI's pinned route (#247)

OP OpenVibers committed 3 days ago · 7fc41ac213 · 3 files, +56 −55

Every chat model the Exchange catalogue shows is offered, with its price in/out and 18+ mark. A model AI's generate route lists is pinned to that offer; one it does not list is a soft preference, so the run may re-route and the take records the model that answered. The person's own keys and machines are still added from AI's choices.

Changes

ddiff --git a/server/create/options.js b/server/create/options.js
iindex fefded7..b64b676 100644
---- a/server/create/options.js
++++ b/server/create/options.js
@@@ -85,13 +85,14 @@ function createCreateOptions({ config = {}, ai = null, catalog, fetchImpl = glob
*/
function join(routes, docs) {
const models = Array.isArray(docs && docs.models) ? docs.models.filter((m) => !m.kind || m.kind === 'chat') : [];
-- const byOffer = new Map();
-- const byModel = new Map();
-- for (const doc of models) {
-- for (const offer of (doc.offers || [])) {
-- byOffer.set(`${offer.provider}|${offer.model || ''}`, { doc, offer });
-- if (offer.model) byModel.set(String(offer.model), { doc, offer });
-- }
++ const routeList = Array.isArray(routes) ? routes.filter((r) => r && r.provider) : [];
++ const routeByOffer = new Map();
++ const routeByModel = new Map();
++ for (const route of routeList) {
++ const provider = String(route.provider).trim();
++ const model = route.model != null ? String(route.model).trim() : '';
++ routeByOffer.set(`${provider}|${model}`, { provider, model: model || null });
++ if (model) routeByModel.set(model, { provider, model });
}
const out = [];
const seen = new Set();
@@@ -100,50 +101,50 @@ function createCreateOptions({ config = {}, ai = null, catalog, fetchImpl = glob
seen.add(entry.id);
out.push(entry);
};
-- const list = Array.isArray(routes) ? routes : [];
-- if (list.length) {
-- for (const route of list) {
-- const provider = String((route && route.provider) || '').trim();
-- const model = route && route.model != null ? String(route.model).trim() : null;
-- if (!provider) continue;
-- const match = byOffer.get(`${provider}|${model || ''}`) || (model ? byModel.get(model) : null) || null;
-- const doc = match && match.doc;
-- const offer = (match && match.offer) || (doc && bestOfferOf(doc)) || null;
-- const prices = priceOf(offer);
-- add({
-- id: model ? `${provider}:${model}` : provider,
-- name: (doc && doc.name) || model || provider,
-- provider,
-- model,
-- price_in: prices.price_in,
-- price_out: prices.price_out,
-- note: noteOf(offer, provider),
-- exchange_url: doc ? `${EXCHANGE_ORIGIN}/models/${String(doc.slug).split('/').map(encodeURIComponent).join('/')}` : null,
-- adult: Boolean(doc && doc.uncensored),
-- source: 'exchange',
-- pin: true,
-- });
++ const pageOf = (doc) => `${EXCHANGE_ORIGIN}/models/${String(doc.slug).split('/').map(encodeURIComponent).join('/')}`;
++
++ // The Exchange catalogue is the list. A doc whose offer AI's route lists can be pinned to that offer;
++ // one AI does not list is still offered, as a soft preference (the run may re-route, and the take
++ // records the model that actually answered).
++ for (const doc of models) {
++ const offers = Array.isArray(doc.offers) ? doc.offers.filter(Boolean) : [];
++ const best = bestOfferOf(doc);
++ let pin = null;
++ for (const offer of [best, ...offers]) {
++ if (!offer) continue;
++ pin = routeByOffer.get(`${offer.provider}|${offer.model || ''}`) || (offer.model ? routeByModel.get(String(offer.model)) : null) || null;
++ if (pin) break;
}
++ const used = pin || best;
++ if (!used) continue;
++ const prices = priceOf(best);
++ add({
++ id: doc.slug || doc.canonical || `${used.provider}:${used.model || ''}`,
++ name: doc.name || used.model || used.provider,
++ provider: pin ? pin.provider : (used.provider || null),
++ model: pin ? pin.model : (used.model || null),
++ price_in: prices.price_in,
++ price_out: prices.price_out,
++ note: noteOf(best, pin ? pin.provider : used.provider),
++ exchange_url: doc.slug ? pageOf(doc) : null,
++ adult: Boolean(doc.uncensored),
++ source: 'exchange',
++ pin: Boolean(pin),
++ prefer: pin ? { provider: pin.provider, model: pin.model } : (used.model ? { model: used.model } : null),
++ });
}
++ // No catalogue (AI did not answer): the runnable route offers still give the page something truthful.
if (!out.length) {
-- // AI's route list is the authority; when it cannot be read, the catalogue still gives the page
-- // something truthful to show (the run may re-route, and the take records what actually answered).
-- for (const doc of models) {
-- const offer = bestOfferOf(doc);
-- if (!offer) continue;
-- const prices = priceOf(offer);
++ for (const route of routeList) {
add({
-- id: `${offer.provider}:${offer.model || ''}`,
-- name: doc.name || offer.model,
-- provider: offer.provider,
-- model: offer.model || null,
-- price_in: prices.price_in,
-- price_out: prices.price_out,
-- note: noteOf(offer, offer.provider),
-- exchange_url: `${EXCHANGE_ORIGIN}/models/${String(doc.slug).split('/').map(encodeURIComponent).join('/')}`,
-- adult: Boolean(doc.uncensored),
-- source: 'exchange',
-- pin: true,
++ id: `${route.provider}:${route.model || ''}`,
++ name: route.model || route.provider,
++ provider: route.provider,
++ model: route.model || null,
++ price_in: null, price_out: null,
++ note: 'Managed by OpenVibe.AI', exchange_url: null, adult: false,
++ source: 'exchange', pin: true,
++ prefer: route.model ? { provider: route.provider, model: route.model } : { provider: route.provider },
});
}
}
ddiff --git a/server/create/plan.js b/server/create/plan.js
iindex a67ff8c..7c3d000 100644
---- a/server/create/plan.js
++++ b/server/create/plan.js
@@@ -97,7 +97,7 @@ async function resolvePlan(body = {}, ctx = {}) {
let legacy = false;
const slots = [];
-- const preferOf = (model) => (model.provider && model.model ? { provider: model.provider, model: model.model } : model.model ? { model: model.model } : null);
++ const preferOf = (model) => model.prefer || (model.provider && model.model ? { provider: model.provider, model: model.model } : model.model ? { model: model.model } : null);
if (ids.length) {
if (ids.length > options.limits.models) throw fail(422, 'create.models', `Choose at most ${options.limits.models} models.`);
ddiff --git a/test/create-options.test.js b/test/create-options.test.js
iindex 989bb36..d55b205 100644
---- a/test/create-options.test.js
++++ b/test/create-options.test.js
@@@ -60,7 +60,7 @@ const balance = { read: async () => ({ currency: 'USD', balance_usd: 2, held_usd
assert.ok(signedOutOptions.kinds.some((k) => k.id === 'component' && k.frames.includes('desktop')));
assert.ok(signedOutOptions.frames.some((f) => f.id === 'email' && f.width === 600));
assert.ok(signedOutOptions.looks.some((l) => l.source === 'theme' && l.id === 'paper'));
-- const alpha = signedOutOptions.models.find((m) => m.id === 'alpha:one');
++ const alpha = signedOutOptions.models.find((m) => m.id === 'alpha/one');
assert.ok(alpha, 'the runnable catalogue model is listed');
assert.equal(alpha.price_in, 1);
assert.equal(alpha.price_out, 2);
@@@ -74,14 +74,14 @@ const balance = { read: async () => ({ currency: 'USD', balance_usd: 2, held_usd
const r = await t.get('/api/create/options', { as: user });
assert.equal(r.status, 200, r.text);
const body = r.json();
-- assert.ok(body.models.some((m) => m.id === 'secret:abliterated' && m.adult === true), 'AI says this person may use 18+');
++ assert.ok(body.models.some((m) => m.id === 'secret/abliterated' && m.adult === true), 'AI says this person may use 18+');
assert.ok(body.models.some((m) => m.id === 'keys:my/model' && m.price_in === 0));
assert.ok(body.models.some((m) => m.id === 'machines:dev_1:local/model' && m.price_in === 0));
});
await check('the estimate prices the chosen models and says whether Balance covers it', async () => {
const r = await t.get('/api/create/estimate', { as: user, origin: 'https://openvibe.design',
-- json: { prompt: 'A pricing card', kind: 'component', frame: 'desktop', models: ['alpha:one', 'beta:two'] } });
++ json: { prompt: 'A pricing card', kind: 'component', frame: 'desktop', models: ['alpha/one', 'beta/two'] } });
assert.equal(r.status, 200, r.text);
const body = r.json();
assert.equal(body.currency, 'USD');
@@@ -95,7 +95,7 @@ const balance = { read: async () => ({ currency: 'USD', balance_usd: 2, held_usd
});
await check('two takes per model doubles the estimate through the same function', async () => {
-- const body = { prompt: 'A pricing card', kind: 'component', frame: 'desktop', models: ['alpha:one', 'beta:two'] };
++ const body = { prompt: 'A pricing card', kind: 'component', frame: 'desktop', models: ['alpha/one', 'beta/two'] };
const one = (await t.get('/api/create/estimate', { as: user, origin: 'https://openvibe.design', json: body })).json();
const two = (await t.get('/api/create/estimate', { as: user, origin: 'https://openvibe.design',
json: { ...body, takes: 2 } })).json();
@@@ -105,7 +105,7 @@ const balance = { read: async () => ({ currency: 'USD', balance_usd: 2, held_usd
await check('signed out estimate answers without a Balance', async () => {
const r = await t.get('/api/create/estimate', { origin: 'https://openvibe.design',
-- json: { prompt: 'A pricing card', kind: 'component', models: ['alpha:one'] } });
++ json: { prompt: 'A pricing card', kind: 'component', models: ['alpha/one'] } });
assert.equal(r.status, 200, r.text);
const body = r.json();
assert.ok(body.total_usd > 0);
@@@ -117,11 +117,11 @@ const balance = { read: async () => ({ currency: 'USD', balance_usd: 2, held_usd
await check('a component with two models returns two takes that record their model', async () => {
const r = await t.get('/api/create', { as: user, origin: 'https://openvibe.design',
json: { prompt: 'A pricing card', kind: 'component', frame: 'desktop', look: 'paper',
-- models: ['alpha:one', 'beta:two'] } });
++ models: ['alpha/one', 'beta/two'] } });
assert.equal(r.status, 201, r.text);
created = r.json();
assert.equal(created.takes.length, 2);
-- assert.deepEqual(created.takes.map((x) => x.modelId).sort(), ['alpha:one', 'beta:two']);
++ assert.deepEqual(created.takes.map((x) => x.modelId).sort(), ['alpha/one', 'beta/two']);
assert.deepEqual(created.takes.map((x) => x.model).sort(), ['one', 'two']);
const row = await t.store.db.maybe('SELECT * FROM designs WHERE id=$1', [created.design.id]);
assert.equal(row.kind, 'component');