openvibers/openvibe.exchange Commits

Pricing: a page with its head, one row per way of calling (#153)

OP OpenVibers committed 4 days ago · 4ae87f0c62 · 3 files, +25 −6

/pricing had no h1 and said prices were 'set by the owner'. Now: Pricing, 'What a call costs,
before you make it.', and five rows (your own key at 0%, managed models, open models on our GPUs,
your own endpoint, search), the same rows on home.

Changes

ddiff --git a/public/css/platform.css b/public/css/platform.css
iindex c292b3a..9f0f696 100644
---- a/public/css/platform.css
++++ b/public/css/platform.css
@@@ -284,3 +284,11 @@
.xm-name > a { display: inline-flex; align-items: center; min-height: 44px; }
.xm-acts .ovk-btn { min-height: 44px; }
}
++
++/* ── Pricing: one row per way of calling, the name above what it costs ── */
++.xpr-list { list-style: none; margin: 0; padding: 0; border: 1px solid var(--ovk-line); border-radius: var(--ov-radius-2, 16px); background: var(--ovk-panel); overflow: hidden; }
++.xpr-list li { display: grid; gap: 2px; padding: 12px 16px; border-top: 1px solid var(--ovk-line); }
++.xpr-list li:first-child { border-top: 0; }
++.xpr-list span { color: var(--ovk-muted); font-size: .92rem; }
++.xpr-note { margin: 12px 2px 0; color: var(--ovk-muted); font-size: .88rem; }
++@media (min-width: 760px) { .xpr-list li { grid-template-columns: minmax(200px, 1fr) 2fr; align-items: baseline; gap: 16px; } }
ddiff --git a/server/http/pages.js b/server/http/pages.js
iindex 0d70f71..08edb62 100644
---- a/server/http/pages.js
++++ b/server/http/pages.js
@@@ -93,7 +93,7 @@ function createPageRoutes(ctx) {
.send(JSON.stringify(doc));
});
-- r.get('/pricing', (req,res) => page(req,res,{title:'Pricing',body:raw('<div class="ex-wrap xp">'+platform.pricing()+'</div>')}));
++ r.get('/pricing', (req, res) => page(req, res, { title: 'Pricing', description: 'What a call to OpenVibe.Exchange costs: your own key at 0%, managed models at their listed price, your own endpoint by the hour, search per 1,000.', body: raw(platform.pricingPage()) }));
r.get('/docs', (req,res) => res.redirect(301,'/api'));
// ── Home ─────────────────────────────────────────────────
ddiff --git a/server/views/platform.js b/server/views/platform.js
iindex f9ed531..44dda33 100644
---- a/server/views/platform.js
++++ b/server/views/platform.js
@@@ -59,11 +59,22 @@ function table(models) {
+ `<p class="xm-acts">${kit.button({ href: chat(m), label: 'Try in Chat', kind: 'quiet', small: true })} ${kit.button({ href: rank(m), label: 'How it ranks', kind: 'quiet', small: true })}</p></li>`;
}).join('')}</ul>`;
}
++// What each way of calling costs, in the reader's words (home's Pricing section and /pricing). Opus, 11-09.
++const PRICING = [
++ ['Your own provider key', "The provider's price. OpenVibe adds 0%, with no cap and no end date."],
++ ['Managed models', 'The input and output price per million tokens shown on each model, before you call.'],
++ ["Open models on OpenVibe's GPUs", 'A fixed price per million tokens, the same on every call. Coming next.'],
++ ['Your own endpoint', 'An hourly price, quoted before it starts; it stops when idle.'],
++ ['Search', "Each engine's price per 1,000 searches; the free engines first."],
++];
++const pricingRows = () => `<ul class="xpr-list">${PRICING.map(([name, what]) => `<li><strong>${esc(name)}</strong><span>${esc(what)}</span></li>`).join('')}</ul>`
++ + '<p class="xpr-note">Paid models open soon. Every price shows before you call, and nothing is charged until then.</p>';
function pricing() {
-- return section(
-- 'Pricing',
-- `<div class="xp-models"><table><thead><tr><th>Route</th><th>What you pay</th></tr></thead><tbody><tr><td>Your provider key</td><td>Provider price + 0% fee, forever, no cap</td></tr><tr><td>Managed models</td><td>The input and output rates listed on each model</td></tr><tr><td>Our GPUs</td><td>Fixed input and output prices set by the owner</td></tr><tr><td>Your deployment</td><td>The quoted hourly price, shown before starting</td></tr><tr><td>Search</td><td>Engine price per 1,000 searches; free engines first</td></tr></tbody></table></div><p>Paid models open soon. Every price shows before you call, and nothing is charged until then.</p>`,
-- );
++ return section('Pricing', pricingRows());
++}
++/** /pricing: the page, with its head (it had no h1). */
++function pricingPage() {
++ return `<div class="ex-wrap xp">${kit.head({ title: 'Pricing', sub: 'What a call costs, before you make it.' })}<section class="xpr">${pricingRows()}</section></div>`;
}
function home({ catalog, usage, viewer }) {
const models = listed(catalog);
@@@ -139,4 +150,4 @@ function modelsPage({ catalog, query = {} }) {
);
return `<div class="ex-wrap xp">${kit.head({ title: 'Models', sub: `${rows.length} models · live prices and measured performance` })}${unavailable(catalog)}${kit.find({ action: '/models', name: 'q', value: query.q || '', placeholder: 'Find a model', icon: 'search' })}<nav class="ex-chips" aria-label="Model filters">${chips.join('')}</nav>${table(rows)}</div>`;
}
--module.exports = { home, modelsPage, pricing, table };
++module.exports = { home, modelsPage, pricing, pricingPage, table };