openvibers/openvibe.ai Commits

AI: /updates — what shipped on ai.openvibe.services (the shared update log) (#79)

OP OpenVibers committed 29 days ago · 8b19e3e740 · 4 files, +51 −9

ai.openvibe.services gets `/updates`: what shipped on the site, from the network's shared update log (`openvibe-shared/frame` `updatesBody` + `shipped.js`), like the other OpenVibe sites. A site-wide crawl found openvibe.help linking it (404).

- **Route:** `GET /updates`, with one `<h1>`, a canonical URL, the home's CSP (`connect-src` already allows openvibe.network's changelog) and a public cache of 5 minutes.
- **Vhost:** `location = /updates` (the vhost proxies only the paths it lists). Install it with `ov-vhost-install`.
- **Links:** the footer links it (`updates: '/updates'`). robots.txt allows it and the sitemap lists it.

`test/home.test.js` (11/11) covers the page, the vhost location, robots and the sitemap. `vhost-catchall` passes.

Changes

ddiff --git a/deploy/nginx/ai.openvibe.services.conf b/deploy/nginx/ai.openvibe.services.conf
iindex 53a8c1c..b8b9b8f 100644
---- a/deploy/nginx/ai.openvibe.services.conf
++++ b/deploy/nginx/ai.openvibe.services.conf
@@@ -149,6 +149,7 @@ server {
}
location = /robots.txt { proxy_pass http://127.0.0.1:4700; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header CF-Connecting-IP $remote_addr; }
++ location = /updates { proxy_pass http://127.0.0.1:4700; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header CF-Connecting-IP $remote_addr; }
location = /stats { proxy_pass http://127.0.0.1:4700; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header CF-Connecting-IP $remote_addr; }
location = /sitemap.xml { proxy_pass http://127.0.0.1:4700; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header CF-Connecting-IP $remote_addr; }
location = /llms.txt { proxy_pass http://127.0.0.1:4700; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header CF-Connecting-IP $remote_addr; }
ddiff --git a/server/app.js b/server/app.js
iindex 84bc169..7c8a47c 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -124,14 +124,14 @@ function createApp({ config, db, registry, pool, quotas, cache, runs, auth, keys
// Discovery for the public home (ai.openvibe.services): the home and the price page are for search engines
// and assistants; the console, its sign-in and the API are not.
app.get('/robots.txt', (_req, res) => {
-- res.type('text/plain').send(`User-agent: *\nAllow: /$\nAllow: /stats\nDisallow: /console\nDisallow: /auth/\nDisallow: /api/\n\nSitemap: ${config.baseUrl}/sitemap.xml\n`);
++ res.type('text/plain').send(`User-agent: *\nAllow: /$\nAllow: /stats\nAllow: /updates\nDisallow: /console\nDisallow: /auth/\nDisallow: /api/\n\nSitemap: ${config.baseUrl}/sitemap.xml\n`);
});
app.get('/sitemap.xml', async (_req, res, next) => {
try {
const from = new Date(clock.now() - STATS_DAYS * 86400000).toISOString().slice(0, 10);
const stats = await db.prepare('SELECT MAX(day) AS lastmod FROM provider_stats_daily WHERE day >= ?').get(from);
const statsLastmod = [stats && stats.lastmod ? new Date(stats.lastmod).toISOString() : null, releaseLastmod].filter(Boolean).sort().pop();
-- const entries = [['/', releaseLastmod], ['/stats', statsLastmod]];
++ const entries = [['/', releaseLastmod], ['/stats', statsLastmod], ['/updates', releaseLastmod]];
res.type('application/xml').send(`<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${entries.map(([p, lastmod]) => ` <url><loc>${config.baseUrl}${p}</loc>${lastmod ? `<lastmod>${lastmod.slice(0, 10)}</lastmod>` : ''}</url>`).join('\n')}\n</urlset>\n`);
} catch (e) { next(e); }
});
@@@ -143,6 +143,12 @@ function createApp({ config, db, registry, pool, quotas, cache, runs, auth, keys
// GET /: the home page for a browser (server/home.js), the API index for curl and API clients (`*/*`, no
// Accept, or any non-HTML Accept). Vary: Accept so a shared cache never serves one to the other; the home is
// private because Cloudflare caches by URL and ignores Vary: Accept. The home carries its own CSP.
++ // 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 }));
++ });
++
app.get('/', (req, res) => {
res.vary('Accept');
if (req.accepts(['text/plain', 'text/html']) === 'text/html') {
ddiff --git a/server/home.js b/server/home.js
iindex 4204444..9dac594 100644
---- a/server/home.js
++++ b/server/home.js
@@@ -11,6 +11,7 @@
* llmsTxt({ siteUrl }) /llms.txt, the same facts as plain text
*/
const shell = require('openvibe-shared/shell');
++const frame = require('openvibe-shared/frame');
const showcase = require('openvibe-shared/showcase');
const ovServe = require('openvibe-shared/serve');
const appIcon = require('openvibe-shared/app-icon');
@@@ -115,10 +116,33 @@ function sections({ siteUrl }) {
});
}
++const NAV_LINKS = [{ label: 'Prices', href: '/stats' }, { label: 'Guide', href: GUIDE }];
++const FOOTER = { service: 'ai', variant: 'full', mount: '#ov-footer', brandName: SITE_NAME, updates: '/updates' };
++
++/** /updates: what shipped on this site (the network's shared update log, openvibe-shared/frame updatesBody). */
++function renderUpdates({ siteUrl }) {
++ return shell.page({
++ name: SITE_NAME, service: 'ai', lang: 'en',
++ title: `What shipped on ${SITE_NAME}`,
++ siteName: SITE_NAME,
++ description: `Every change deployed to ${SITE_NAME}, newest first.`,
++ canonical: `${siteUrl}/updates`,
++ robots: 'index, follow',
++ navbar: { service: 'ai', apiBase: NETWORK_URL, links: NAV_LINKS, history: { type: 'page', title: SITE_NAME } },
++ footer: FOOTER, home: '/', navLinks: NAV_LINKS,
++ head: appIcon.headTags({ site: 'ai' }),
++ body: `<div id="navbar-mount"></div>
++<main id="main" class="page">
++${frame.updatesBody({ service: 'ai', siteName: SITE_NAME })}
++</main>
++<script src="${ovServe.url('shipped.js')}" defer></script>`,
++ });
++}
++
function renderHome({ siteUrl }) {
-- const navLinks = [{ label: 'Prices', href: '/stats' }, { label: 'Guide', href: GUIDE }];
++ const navLinks = NAV_LINKS;
const nav = { service: 'ai', apiBase: NETWORK_URL, links: navLinks, history: { type: 'page', title: SITE_NAME } };
-- const footer = { service: 'ai', variant: 'full', mount: '#ov-footer', brandName: SITE_NAME };
++ const footer = FOOTER;
return shell.page({
name: SITE_NAME, service: 'ai', lang: 'en',
title: `${SITE_NAME}: many models, one API, every answer explained`,
@@@ -172,4 +196,4 @@ function renderNotFound() {
'<h1>Not found</h1>', `<p>Nothing is here. <a href="/">${SITE_NAME}</a> · <a href="/stats">Prices and latency</a> · <a href="${NETWORK_URL}">OpenVibe</a></p>`, '</body>', '</html>'].join('\n');
}
--module.exports = { renderHome, llmsTxt, HOME_CSP, SITE_NAME, renderNotFound };
++module.exports = { renderHome, renderUpdates, llmsTxt, HOME_CSP, SITE_NAME, renderNotFound };
ddiff --git a/test/home.test.js b/test/home.test.js
iindex 40dd052..00326cc 100644
---- a/test/home.test.js
++++ b/test/home.test.js
@@@ -67,15 +67,26 @@ t.test('the home links the pinned Frame stylesheet, and /shared serves it', asyn
assert.match(css.headers.get('cache-control'), /immutable/);
});
++t.test('/updates: what shipped on this site, the shared update log, with one heading and the footer link', async () => {
++ const r = await request(h.base, 'GET', '/updates', { headers: { Accept: 'text/html' } });
++ assert.strictEqual(r.status, 200);
++ assert.match(r.headers.get('content-type'), /^text\/html/);
++ assert.ok(r.text.includes('<h1>What shipped on OpenVibe.AI</h1>') && r.text.includes('data-ov-shipped="log" data-service="ai"'));
++ assert.strictEqual((r.text.match(/<h1[\s>]/g) || []).length, 1);
++ assert.match(r.text, /<script src="\/shared\/shipped\.js\?v=[0-9a-f]+" defer><\/script>/);
++ assert.match(r.text, new RegExp(`<link rel="canonical" href="${SITE.replace(/[.]/g, '\\.')}/updates">`));
++ assert.ok(r.headers.get('content-security-policy').includes("connect-src 'self' https://openvibe.network"), 'the log reads the network changelog');
++});
++
t.test('robots.txt, sitemap.xml and llms.txt name the public origin', async () => {
const robots = await request(h.base, 'GET', '/robots.txt');
assert.strictEqual(robots.status, 200);
-- for (const line of ['Allow: /$', 'Allow: /stats', 'Disallow: /console', 'Disallow: /auth/', 'Disallow: /api/', `Sitemap: ${SITE}/sitemap.xml`]) assert.ok(robots.text.includes(line), line);
++ for (const line of ['Allow: /$', 'Allow: /stats', 'Allow: /updates', 'Disallow: /console', 'Disallow: /auth/', 'Disallow: /api/', `Sitemap: ${SITE}/sitemap.xml`]) assert.ok(robots.text.includes(line), line);
const map = await request(h.base, 'GET', '/sitemap.xml');
assert.strictEqual(map.status, 200);
assert.match(map.headers.get('content-type'), /xml/);
-- assert.deepStrictEqual([...map.text.matchAll(/<loc>([^<]+)<\/loc>/g)].map((m) => m[1]), [`${SITE}/`, `${SITE}/stats`]);
-- assert.strictEqual(assertSitemapLastmods(map.text).length, 2);
++ assert.deepStrictEqual([...map.text.matchAll(/<loc>([^<]+)<\/loc>/g)].map((m) => m[1]), [`${SITE}/`, `${SITE}/stats`, `${SITE}/updates`]);
++ assert.strictEqual(assertSitemapLastmods(map.text).length, 3);
const llms = await request(h.base, 'GET', '/llms.txt');
assert.strictEqual(llms.status, 200);
assert.match(llms.headers.get('content-type'), /^text\/plain/);
@@@ -87,7 +98,7 @@ const active = (file) => fs.readFileSync(path.join(__dirname, '..', 'deploy', 'n
t.test('the public vhost proxies the home, its files and exactly the developer-app API routes', () => {
const conf = active('ai.openvibe.services.conf');
-- for (const loc of ['location = / {', 'location = /stats', 'location = /sitemap.xml', 'location = /llms.txt', 'location /shared/']) assert.ok(conf.includes(loc), loc);
++ for (const loc of ['location = / {', 'location = /stats', 'location = /updates', 'location = /sitemap.xml', 'location = /llms.txt', 'location /shared/']) assert.ok(conf.includes(loc), loc);
const ops = conf.indexOf('location ~ ^/api/v1/(chat|generate|summarize|classify|extract|embed)$ {');
const run = conf.indexOf('location ~ ^/api/v1/runs/[A-Za-z0-9_-]+$ {');
const completions = conf.indexOf('location = /v1/chat/completions {');