openvibers/openvibe.design Commits
The create model list is the Exchange catalogue, not only AI's pinned route (#247)
Changes
@@@ -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 = globseen.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 },});}}@@@ -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.`);@@@ -60,7 +60,7 @@ const balance = { read: async () => ({ currency: 'USD', balance_usd: 2, held_usdassert.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_usdconst 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_usdawait 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_usdawait 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');
@@ -85,13 +85,14 @@ function createCreateOptions({ config = {}, ai = null, catalog, fetchImpl = glob
@@ -85,13 +85,14 @@ function createCreateOptions({ config = {}, ai = null, catalog, fetchImpl = glob
*/
*/
function join(routes, docs) {
function join(routes, docs) {
const models = Array.isArray(docs && docs.models) ? docs.models.filter((m) => !m.kind || m.kind === 'chat') : [];
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 out = [];
const seen = new Set();
const seen = new Set();
@@ -100,50 +101,50 @@ function createCreateOptions({ config = {}, ai = null, catalog, fetchImpl = glob
@@ -100,50 +101,50 @@ function createCreateOptions({ config = {}, ai = null, catalog, fetchImpl = glob
seen.add(entry.id);
seen.add(entry.id);
out.push(entry);
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) {
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({
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 },
});
});
}
}
}
}
@@ -97,7 +97,7 @@ async function resolvePlan(body = {}, ctx = {}) {
@@ -97,7 +97,7 @@ async function resolvePlan(body = {}, ctx = {}) {
let legacy = false;
let legacy = false;
const slots = [];
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) {
if (ids.length > options.limits.models) throw fail(422, 'create.models', `Choose at most ${options.limits.models} models.`);
if (ids.length > options.limits.models) throw fail(422, 'create.models', `Choose at most ${options.limits.models} models.`);
@@ -60,7 +60,7 @@ const balance = { read: async () => ({ currency: 'USD', balance_usd: 2, held_usd
@@ -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.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.frames.some((f) => f.id === 'email' && f.width === 600));
assert.ok(signedOutOptions.looks.some((l) => l.source === 'theme' && l.id === 'paper'));
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.ok(alpha, 'the runnable catalogue model is listed');
assert.equal(alpha.price_in, 1);
assert.equal(alpha.price_in, 1);
assert.equal(alpha.price_out, 2);
assert.equal(alpha.price_out, 2);
@@ -74,14 +74,14 @@ const balance = { read: async () => ({ currency: 'USD', balance_usd: 2, held_usd
@@ -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 });
const r = await t.get('/api/create/options', { as: user });
assert.equal(r.status, 200, r.text);
assert.equal(r.status, 200, r.text);
const body = r.json();
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 === 'keys:my/model' && m.price_in === 0));
assert.ok(body.models.some((m) => m.id === 'machines:dev_1:local/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 () => {
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',
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);
assert.equal(r.status, 200, r.text);
const body = r.json();
const body = r.json();
assert.equal(body.currency, 'USD');
assert.equal(body.currency, 'USD');
@@ -95,7 +95,7 @@ const balance = { read: async () => ({ currency: 'USD', balance_usd: 2, held_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 () => {
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 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',
const two = (await t.get('/api/create/estimate', { as: user, origin: 'https://openvibe.design',
json: { ...body, takes: 2 } })).json();
json: { ...body, takes: 2 } })).json();
@@ -105,7 +105,7 @@ const balance = { read: async () => ({ currency: 'USD', balance_usd: 2, held_usd
@@ -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 () => {
await check('signed out estimate answers without a Balance', async () => {
const r = await t.get('/api/create/estimate', { origin: 'https://openvibe.design',
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);
assert.equal(r.status, 200, r.text);
const body = r.json();
const body = r.json();
assert.ok(body.total_usd > 0);
assert.ok(body.total_usd > 0);
@@ -117,11 +117,11 @@ const balance = { read: async () => ({ currency: 'USD', balance_usd: 2, held_usd
@@ -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 () => {
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',
const r = await t.get('/api/create', { as: user, origin: 'https://openvibe.design',
json: { prompt: 'A pricing card', kind: 'component', frame: 'desktop', look: 'paper',
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);
assert.equal(r.status, 201, r.text);
created = r.json();
created = r.json();
assert.equal(created.takes.length, 2);
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']);
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]);
const row = await t.store.db.maybe('SELECT * FROM designs WHERE id=$1', [created.design.id]);
assert.equal(row.kind, 'component');
assert.equal(row.kind, 'component');