openvibers/openvibe.directory Commits
Keep Directory model entries fresh and link every status (#7)
Changes
@@@ -25,9 +25,11 @@ function models(data, checked) {const out=[], makers=new Map();for (const m of data.models||data.things||[]) {const id=m.slug||m.id, maker=m.maker||String(id).split('/')[0];-- const source=safeUrl(m.url)||URLS.models;-- const website=m.slug ? `https://openvibe.exchange/m/${m.slug.split('/').map(encodeURIComponent).join('/')}` : `https://openvibe.compare/things/${encodeURIComponent(id)}`;-- const e=entry('models',id,m.name,`AI model from ${maker}. See current provider offers, prices and measured results.`,website,source,iso(data.generated_at||data.updated_at)||checked,{provider:maker,context:m.context||null,modalities:m.modalities||[]});++ const source=safeUrl(m.source)||safeUrl(m.url)||URLS.models;++ const website=m.routing_enabled && m.slug++ ? `https://openvibe.exchange/m/${m.slug.split('/').map(encodeURIComponent).join('/')}`++ : `https://openvibe.exchange/models/${encodeURIComponent(id)}`;++ const e=entry('models',id,m.name,`AI model from ${maker}. See current provider offers, prices and measured results.`,website,source,iso(data.generated_at||data.updated_at)||checked,{provider:maker,context:m.context_window??m.context??null,modalities:m.modalities||[],status:m.status||null});if(e) out.push(e);if(maker && !makers.has(maker)) makers.set(maker,entry('providers',maker,maker,'AI model provider.',source,URLS.models,iso(data.updated_at)||checked));}@@@ -54,7 +56,7 @@ function businesses(xml, checked) {}function toArray(value){return value==null?[]:Array.isArray(value)?value:[value];}const parsers={sites,tools,models,projects,businesses};--function createStore({fetchImpl=fetch,now=()=>new Date(),intervalMs=6*60*60*1000}={}){++function createStore({fetchImpl=fetch,now=()=>new Date(),intervalMs=10*60*1000}={}){const state={entries:new Map(Object.keys(kinds).map(k=>[k,[]])),status:{},refreshed_at:null};let pending=null, timer=null;async function refresh(){@@@ -9,11 +9,17 @@ test('unverified OSM phone tags are not published and business source is attribuassert.equal(rows.length,1);assert.equal(rows[0].phone,undefined);assert.match(rows[0].sources[0].url,/openstreetmap/);assert.match(rows[0].osm_attribution,/ODbL/);});test('AI directory reads Exchange model ids and links to their model pages',()=>{-- const rows=models({generated_at:'2026-11-11T00:00:00Z',models:[{slug:'google/gemini-3',name:'Gemini 3',context:1000000,modalities:['text','image']}]},'2026-11-11T01:00:00Z');++ const rows=models({generated_at:'2026-11-11T00:00:00Z',models:[++ {slug:'google/gemini-3',name:'Gemini 3',routing_enabled:true,context_window:1000000,modalities:['text','image']},++ {id:'google/gemini-4',name:'Gemini 4',status:'spotted',routing_enabled:false,source:'https://aistudio.google.com/docs/changelog'}++ ]},'2026-11-11T01:00:00Z');assert.equal(URLS.models,'https://openvibe.exchange/api/v1/models');assert.equal(rows[0].website,'https://openvibe.exchange/m/google/gemini-3');assert.equal(rows[0].context,1000000);-- assert.equal(rows[1].kind,'providers');++ assert.equal(rows[1].website,'https://openvibe.exchange/models/google%2Fgemini-4');++ assert.equal(rows[1].status,'spotted');++ assert.equal(rows[1].sources[0].url,'https://aistudio.google.com/docs/changelog');++ assert.equal(rows[2].kind,'providers');});test('failed refresh retains the last successful source and reports stale status',async()=>{let fail=false;const fetchImpl=async(url)=>{if(fail)throw Error('source down');return {ok:true,json:async()=>url.includes('frame?')?{nav:[{id:'live',name:'Live',url:'https://openvibe.live/'}]}:{tools:[{id:'yaml',name:'YAML',docs:'https://openvibe.tools/tool/yaml'}],things:[{id:'a/b',name:'B',maker:'A',url:'https://example.com'}],items:[{full_name:'OpenVibers/Test',name:'Test',html_url:'https://github.com/OpenVibers/Test'}]},text:async()=>'<osm><node id="1"><tag k="shop" v="books"/><tag k="name" v="Books"/></node></osm>'};};
@@ -25,9 +25,11 @@ function models(data, checked) {
@@ -25,9 +25,11 @@ function models(data, checked) {
const out=[], makers=new Map();
const out=[], makers=new Map();
for (const m of data.models||data.things||[]) {
for (const m of data.models||data.things||[]) {
const id=m.slug||m.id, maker=m.maker||String(id).split('/')[0];
const id=m.slug||m.id, maker=m.maker||String(id).split('/')[0];
- const source=safeUrl(m.url)||URLS.models;
- const website=m.slug ? `https://openvibe.exchange/m/${m.slug.split('/').map(encodeURIComponent).join('/')}` : `https://openvibe.compare/things/${encodeURIComponent(id)}`;
- const e=entry('models',id,m.name,`AI model from ${maker}. See current provider offers, prices and measured results.`,website,source,iso(data.generated_at||data.updated_at)||checked,{provider:maker,context:m.context||null,modalities:m.modalities||[]});
+ const source=safeUrl(m.source)||safeUrl(m.url)||URLS.models;
+ const website=m.routing_enabled && m.slug
+ ? `https://openvibe.exchange/m/${m.slug.split('/').map(encodeURIComponent).join('/')}`
+ : `https://openvibe.exchange/models/${encodeURIComponent(id)}`;
+ const e=entry('models',id,m.name,`AI model from ${maker}. See current provider offers, prices and measured results.`,website,source,iso(data.generated_at||data.updated_at)||checked,{provider:maker,context:m.context_window??m.context??null,modalities:m.modalities||[],status:m.status||null});
if(e) out.push(e);
if(e) out.push(e);
if(maker && !makers.has(maker)) makers.set(maker,entry('providers',maker,maker,'AI model provider.',source,URLS.models,iso(data.updated_at)||checked));
if(maker && !makers.has(maker)) makers.set(maker,entry('providers',maker,maker,'AI model provider.',source,URLS.models,iso(data.updated_at)||checked));
}
}
@@ -54,7 +56,7 @@ function businesses(xml, checked) {
@@ -54,7 +56,7 @@ function businesses(xml, checked) {
}
}
function toArray(value){return value==null?[]:Array.isArray(value)?value:[value];}
function toArray(value){return value==null?[]:Array.isArray(value)?value:[value];}
const parsers={sites,tools,models,projects,businesses};
const parsers={sites,tools,models,projects,businesses};
-function createStore({fetchImpl=fetch,now=()=>new Date(),intervalMs=6*60*60*1000}={}){
+function createStore({fetchImpl=fetch,now=()=>new Date(),intervalMs=10*60*1000}={}){
const state={entries:new Map(Object.keys(kinds).map(k=>[k,[]])),status:{},refreshed_at:null};
const state={entries:new Map(Object.keys(kinds).map(k=>[k,[]])),status:{},refreshed_at:null};
let pending=null, timer=null;
let pending=null, timer=null;
async function refresh(){
async function refresh(){
@@ -9,11 +9,17 @@ test('unverified OSM phone tags are not published and business source is attribu
@@ -9,11 +9,17 @@ test('unverified OSM phone tags are not published and business source is attribu
assert.equal(rows.length,1);assert.equal(rows[0].phone,undefined);assert.match(rows[0].sources[0].url,/openstreetmap/);assert.match(rows[0].osm_attribution,/ODbL/);
assert.equal(rows.length,1);assert.equal(rows[0].phone,undefined);assert.match(rows[0].sources[0].url,/openstreetmap/);assert.match(rows[0].osm_attribution,/ODbL/);
});
});
test('AI directory reads Exchange model ids and links to their model pages',()=>{
test('AI directory reads Exchange model ids and links to their model pages',()=>{
- const rows=models({generated_at:'2026-11-11T00:00:00Z',models:[{slug:'google/gemini-3',name:'Gemini 3',context:1000000,modalities:['text','image']}]},'2026-11-11T01:00:00Z');
+ const rows=models({generated_at:'2026-11-11T00:00:00Z',models:[
+ {slug:'google/gemini-3',name:'Gemini 3',routing_enabled:true,context_window:1000000,modalities:['text','image']},
+ {id:'google/gemini-4',name:'Gemini 4',status:'spotted',routing_enabled:false,source:'https://aistudio.google.com/docs/changelog'}
+ ]},'2026-11-11T01:00:00Z');
assert.equal(URLS.models,'https://openvibe.exchange/api/v1/models');
assert.equal(URLS.models,'https://openvibe.exchange/api/v1/models');
assert.equal(rows[0].website,'https://openvibe.exchange/m/google/gemini-3');
assert.equal(rows[0].website,'https://openvibe.exchange/m/google/gemini-3');
assert.equal(rows[0].context,1000000);
assert.equal(rows[0].context,1000000);
- assert.equal(rows[1].kind,'providers');
+ assert.equal(rows[1].website,'https://openvibe.exchange/models/google%2Fgemini-4');
+ assert.equal(rows[1].status,'spotted');
+ assert.equal(rows[1].sources[0].url,'https://aistudio.google.com/docs/changelog');
+ assert.equal(rows[2].kind,'providers');
});
});
test('failed refresh retains the last successful source and reports stale status',async()=>{
test('failed refresh retains the last successful source and reports stale status',async()=>{
let fail=false;const fetchImpl=async(url)=>{if(fail)throw Error('source down');return {ok:true,json:async()=>url.includes('frame?')?{nav:[{id:'live',name:'Live',url:'https://openvibe.live/'}]}:{tools:[{id:'yaml',name:'YAML',docs:'https://openvibe.tools/tool/yaml'}],things:[{id:'a/b',name:'B',maker:'A',url:'https://example.com'}],items:[{full_name:'OpenVibers/Test',name:'Test',html_url:'https://github.com/OpenVibers/Test'}]},text:async()=>'<osm><node id="1"><tag k="shop" v="books"/><tag k="name" v="Books"/></node></osm>'};};
let fail=false;const fetchImpl=async(url)=>{if(fail)throw Error('source down');return {ok:true,json:async()=>url.includes('frame?')?{nav:[{id:'live',name:'Live',url:'https://openvibe.live/'}]}:{tools:[{id:'yaml',name:'YAML',docs:'https://openvibe.tools/tool/yaml'}],things:[{id:'a/b',name:'B',maker:'A',url:'https://example.com'}],items:[{full_name:'OpenVibers/Test',name:'Test',html_url:'https://github.com/OpenVibers/Test'}]},text:async()=>'<osm><node id="1"><tag k="shop" v="books"/><tag k="name" v="Books"/></node></osm>'};};