openvibers/openvibe.ai Commits

Markdown twins of the home, /stats and /updates (SEO-1) (#283)

OP OpenVibers committed 10 days ago · d006614ce1 · 3 files, +80 −9

The site had none. Each public page has one render, used by the page and by its twin (/index.md,
/stats.md, /updates.md, ?format=markdown, Accept: text/markdown); nothing on them is per person.
The page names its twin in the head (shell.page agentLayer) and in the Link header. Nothing else
is answered as a twin, and curl still gets the API index at /.

Changes

ddiff --git a/server/app.js b/server/app.js
iindex f011909..a565115 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -170,16 +170,38 @@ function createApp({ config, db, registry, pool, quotas, cache, runs, auth, keys
try { homeStats = { at: clock.now(), rows: await statsRows() }; } catch (e) { log.warn(`[home] price and latency rows unavailable: ${e.message}`); }
return homeStats.rows || [];
}
++ // The public pages, one render each: the page and its Markdown twin (SEO-1) are the same render, and the same for
++ // everyone (nothing on them is per person).
++ const publicPages = {
++ '/': async () => home.renderHome({ siteUrl: config.baseUrl, stats: { days: STATS_DAYS, rows: await homeRows() } }),
++ '/stats': async () => {
++ const now = clock.now();
++ return home.renderStats({ siteUrl: config.baseUrl, days: STATS_DAYS, rows: await statsRows(),
++ from: new Date(now - STATS_DAYS * 86400000).toISOString().slice(0, 10), to: new Date(now).toISOString().slice(0, 10) });
++ },
++ '/updates': async () => home.renderUpdates({ siteUrl: config.baseUrl }),
++ };
++ const agentLayer = require('openvibe-shared/agent-layer');
++ // The Link: header that names a public page's twin, as its head does.
++ const twinLink = (p) => agentLayer.linkHeaderTags(`${config.baseUrl}${p}`, '/llms.txt').header;
++ {
++ // A twin request (`/stats.md`, `/index.md`, ?format=markdown, Accept: text/markdown) for a public page.
++ const twins = express.Router();
++ agentLayer.mount(twins, { descriptor: require('./agent-descriptor'), render: async (p) => (publicPages[p] ? { html: await publicPages[p](), title: null, updated: null } : null) });
++ app.use((req, res, next) => {
++ const p = String(req.path || '');
++ const page = p === '/index.md' ? '/' : p.replace(/\.md$/, '');
++ const asked = p.endsWith('.md') || req.query.format === 'markdown' || req.accepts(['html', 'text/markdown']) === 'text/markdown';
++ return asked && publicPages[page] && (req.method === 'GET' || req.method === 'HEAD') ? twins(req, res, next) : next();
++ });
++ }
app.get('/stats', async (req, res, next) => {
try {
-- const now = clock.now();
-- const rows = await statsRows();
++ const html = await publicPages['/stats']();
res.setHeader('Cache-Control', cachePolicy.htmlHeaders());
res.setHeader('Content-Security-Policy', home.HOME_CSP);
-- res.type('html').send(home.renderStats({ siteUrl: config.baseUrl,
-- days: STATS_DAYS, rows,
-- from: new Date(now - STATS_DAYS * 86400000).toISOString().slice(0, 10), to: new Date(now).toISOString().slice(0, 10),
-- }));
++ res.setHeader('Link', twinLink('/stats'));
++ res.type('html').send(html);
} catch (e) { next(e); }
});
@@@ -215,17 +237,18 @@ function createApp({ config, db, registry, pool, quotas, cache, runs, auth, keys
// GET /updates: what shipped on this site (the shared update log).
app.get('/updates', (req, res) => {
res.type('html').set('Content-Security-Policy', home.HOME_CSP).set('Cache-Control', 'public, max-age=300')
-- .send(home.renderUpdates({ siteUrl: config.baseUrl }));
++ .set('Link', twinLink('/updates')).send(home.renderUpdates({ siteUrl: config.baseUrl }));
});
app.get('/', async (req, res) => {
res.vary('Accept');
if (req.accepts(['text/plain', 'text/html']) === 'text/html') {
-- const rows = await homeRows();
++ const html = await publicPages['/']();
return res.type('html')
.set('Content-Security-Policy', home.HOME_CSP)
.set('Cache-Control', 'private, max-age=300')
-- .send(home.renderHome({ siteUrl: config.baseUrl, stats: { days: STATS_DAYS, rows } }));
++ .set('Link', twinLink('/'))
++ .send(html);
}
res.type('text/plain').send([
'OpenVibe.AI: providers, models, routing, versioned prompt templates and workflows, runs, citations, scoped cache, quotas and audit.',
ddiff --git a/server/home.js b/server/home.js
iindex 3649612..5462cef 100644
---- a/server/home.js
++++ b/server/home.js
@@@ -308,6 +308,7 @@ ${list.map((r) => `<tr><td data-label="Provider"><code>${esc(r.provider)}</code>
siteName: SITE_NAME,
description: `Price and latency of every AI model OpenVibe.AI called in the last ${days} days: p50 and p95 latency, success rate, price per million tokens and the free allowance.`,
canonical: `${site}/stats`,
++ agentLayer: true, llms: '/llms.txt', // the head names the Markdown twin (SEO-1)
robots: 'index,follow',
navbar: { service: 'ai', apiBase: NETWORK_URL, links: NAV_LINKS, history: { type: 'page', title: SITE_NAME } },
footer: FOOTER, home: '/', navLinks: NAV_LINKS,
@@@ -332,6 +333,7 @@ function renderUpdates({ siteUrl }) {
siteName: SITE_NAME,
description: `Every change deployed to ${SITE_NAME}, newest first.`,
canonical: `${siteUrl}/updates`,
++ agentLayer: true, llms: '/llms.txt',
robots: 'index, follow',
navbar: { service: 'ai', apiBase: NETWORK_URL, links: NAV_LINKS, history: { type: 'page', title: SITE_NAME } },
footer: FOOTER, home: '/', navLinks: NAV_LINKS,
@@@ -356,6 +358,7 @@ function renderHome({ siteUrl, stats = null }) {
description: DESCRIPTION,
summary: SUMMARY,
canonical: `${siteUrl}/`,
++ agentLayer: true, llms: '/llms.txt',
robots: 'index, follow',
jsonLd: [
seo.jsonLd.website({ name: SITE_NAME, url: site, description: DESCRIPTION }),
ddiff --git a/test/agent-twins.test.js b/test/agent-twins.test.js
nnew file mode 100644
iindex 0000000..f98dbf3
---- /dev/null
++++ b/test/agent-twins.test.js
@@@ -0,0 +1,45 @@
++'use strict';
++// SEO-1: the public pages' Markdown twins. A twin is the page's own render (the same for everyone: nothing on these
++// pages is per person), and the page names it in its head and in the Link: header.
++const assert = require('assert');
++const { boot, request, suite } = require('./helpers');
++
++const t = suite('agent-twins');
++let h;
++const HTML = { accept: 'text/html' };
++
++t.test('boot', async () => {
++ h = await boot({ env: { AI_STUB_FALLBACK: 'false' } });
++});
++
++t.test('each public page names its twin in the head and the Link header, and the twin is the page', async () => {
++ for (const [p, twinPath, words] of [['/', '/index.md', 'OpenVibe.AI'], ['/stats', '/stats.md', 'no provider calls in the last 7 days'], ['/updates', '/updates.md', 'OpenVibe.AI']]) {
++ const page = await request(h.base, 'GET', p, { headers: HTML });
++ assert.strictEqual(page.status, 200, p);
++ const head = page.text.match(/<link rel="alternate" type="text\/markdown" href="([^"]+)">/);
++ assert.ok(head && head[1].endsWith(twinPath), `${p} names ${twinPath} in the head`);
++ assert.ok(page.text.includes('<link rel="describedby" href="/llms.txt">'), `${p} names llms.txt`);
++ const link = String(page.headers.get('link') || '');
++ assert.ok(link.includes(`<${head[1]}>; rel="alternate"; type="text/markdown"`) && link.includes('</llms.txt>; rel="describedby"'), `${p}: the Link header says what the head says (${link})`);
++ const twin = await request(h.base, 'GET', twinPath);
++ assert.strictEqual(twin.status, 200, twinPath);
++ assert.match(String(twin.headers.get('content-type')), /^text\/markdown/, `${twinPath} is Markdown`);
++ assert.ok(twin.text.startsWith('---\ntitle:') && twin.text.includes(words), `${twinPath} is the page's words`);
++ assert.ok(!/<(?:script|style)\b/i.test(twin.text.replace(/~~~~[\s\S]*?~~~~/g, '')), `${twinPath} carries no markup`);
++ const asked = await request(h.base, 'GET', p, { headers: { accept: 'text/markdown' } });
++ assert.match(String(asked.headers.get('content-type')), /^text\/markdown/, `${p} with Accept: text/markdown is the twin`);
++ }
++});
++
++t.test('nothing else is a twin: the console, the API, and the API index for curl are as they were', async () => {
++ const console_ = await request(h.base, 'GET', '/console.md');
++ assert.ok(!/^text\/markdown/.test(String(console_.headers.get('content-type'))), 'the console has no twin');
++ const api = await request(h.base, 'GET', '/api/v1/workflows', { headers: { accept: 'text/markdown' } });
++ assert.ok(!/^text\/markdown/.test(String(api.headers.get('content-type'))), 'the API is not answered as a twin');
++ const index = await request(h.base, 'GET', '/');
++ assert.match(String(index.headers.get('content-type')), /^text\/plain/, 'curl still gets the API index');
++});
++
++t.test('stop', async () => { await h.stop(); });
++
++t.run();