openvibers/openvibe.network Commits
Home: the box hands anything that is not an app to Vibe with the text filled in, and an Ask Vibe intent leads the list (filler words and ties never pick an app) (#336)
Changes
@@@ -205,7 +205,7 @@ footer{border-top:1px solid var(--border);background:var(--bg-secondary)}<form class="intent-bar" action="https://openvibe.tools/search" method="get" role="search"><label for="intent-q" class="sr-only">What do you want to do?</label><i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>-- <input id="intent-q" name="q" type="search" placeholder="What do you want to do?" title="Try “go live”, “merge pdf” or “chat”" autocomplete="off" enterkeyhint="go">++ <input id="intent-q" name="q" type="search" placeholder="What do you want to do?" title="Try “go live”, “merge pdf”, or ask anything" autocomplete="off" enterkeyhint="go"><button type="submit">Start</button></form><!--OV:INTENTS-->@@@ -274,26 +274,41 @@ footer{border-top:1px solid var(--border);background:var(--bg-secondary)}})();</script><script>--// The hero's search: typing lights the intents it names; Enter opens the one it names clearly, and anything else is a--// tool search on openvibe.tools (the form's own action, so it works without this script too).++// The hero's search: typing lights the intents it names; Enter opens the one it names clearly, a tool query searches++// openvibe.tools, and anything else (a question, a task in plain words) opens Vibe with the text filled in, never sent++// (plan track K, the owner 10-24). Without this script the form's own action is the tool search.(function () {var form = document.querySelector('.intent-bar'), q = document.getElementById('intent-q');if (!form || !q) return;var items = [].slice.call(document.querySelectorAll('.intent'));-- function words(v) { return v.toLowerCase().split(/[^a-z0-9]+/).filter(function (w) { return w.length > 1; }); }++ // Filler words never pick an app ("find me a flat" is about the flat, not about "me").++ var FILLER = ' a an and are can could do does for from have how i in is it me my of on or our please should so the their to want what when where which who why will with would you your ';++ function words(v) { return v.toLowerCase().split(/[^a-z0-9]+/).filter(function (w) { return w.length > 1 && FILLER.indexOf(' ' + w + ' ') < 0; }); }function score(el, ws) {var keys = (el.getAttribute('data-k') || '').split(' '), s = 0;ws.forEach(function (w) { if (keys.indexOf(w) >= 0) s += 2; else if (w.length > 2 && keys.some(function (k) { return k.indexOf(w) === 0; })) s += 1; });return s;}-- function best(ws) { var top = null, ts = 0; items.forEach(function (el) { var s = score(el, ws); if (s > ts) { ts = s; top = el; } }); return { el: top, s: ts }; }++ // The intent the words name most; two naming it equally is a tie (nobody wins, and the sentence goes to Vibe).++ function best(ws) { var top = null, ts = 0, tie = false; items.forEach(function (el) { var s = score(el, ws); if (s > ts) { ts = s; top = el; tie = false; } else if (s && s === ts) tie = true; }); return { el: top, s: ts, tie: tie }; }q.addEventListener('input', function () {var ws = words(q.value), b = best(ws);items.forEach(function (el) { var s = ws.length ? score(el, ws) : 1; el.classList.toggle('dim', ws.length > 0 && s === 0 && !!b.el); el.classList.toggle('match', !!b.el && el === b.el && b.s > 0); });});++ var VIBE = 'https://openvibe.chat/vibe';form.addEventListener('submit', function (e) {-- var b = best(words(q.value));-- if (b.el && b.s >= 2 && b.el.getAttribute('data-id') !== 'tools') { e.preventDefault(); location.href = b.el.href; }++ var text = q.value.trim(), ws = words(text), b = best(ws), id = b.el && b.el.getAttribute('data-id');++ if (!text) return;++ var site = b.el && !b.tie && id !== 'tools' && id !== 'vibe';++ var go = function (href) { e.preventDefault(); location.href = href; };++ var vibe = function () { go(VIBE + '?ask=' + encodeURIComponent(text.slice(0, 2000)) + '&from=' + encodeURIComponent('https://openvibe.network/')); };++ // An app named clearly wins, even in a question ("how do I go live?"); then a question or a request in a++ // sentence goes to Vibe; then an app named once; a tool query stays a tool search; nothing else: Vibe.++ if (site && b.s >= 4) return go(b.el.href);++ if (id === 'vibe' || /\?\s*$/.test(text) || ws.length >= 5) return vibe();++ if (site && b.s >= 2) return go(b.el.href);++ if (id === 'tools' && b.s > 0) return undefined;++ return vibe();});})();</script>@@@ -41,6 +41,7 @@ const POPULAR = ['yt', 'convert', 'mergepdf', 'jsonfmt', 'mp3', 'dns', 'whois',// The front door (plan T11, D93): what people come to do, each a real link to where it starts. `k` holds the words// the hero's search box matches (public/index.html); a query that names no intent strongly goes to the tool search.const INTENTS = [++ ['vibe', 'fa-brain', 'Ask Vibe', 'Questions, research and tasks in plain words: OpenVibe\'s assistant, with your chats kept.', 'https://openvibe.chat/vibe', 'ask vibe question questions answer explain research chatgpt claude gemini chat-gpt'],['live', 'fa-tower-broadcast', 'Go live', 'From a browser tab. No OBS, no follower minimum.', 'https://openvibe.live/broadcast', 'go live stream streaming broadcast camera webcam screen obs whip rtmp twitch'],['tools', 'fa-screwdriver-wrench', 'Get something done', '', 'https://openvibe.tools/', 'tool tools convert pdf mp3 mp4 video audio image compress resize dns whois json regex download youtube'],['chat', 'fa-comments', 'Chat', 'Group chats with AI members, servers with text and voice channels, pictures and DMs.', 'https://openvibe.chat/', 'chat talk message messages dm room rooms friends group server servers channel channels voice discord slack ai bot members'],@@@ -53,12 +54,12 @@ const INTENTS = [['wiki', 'fa-book-open', 'Look it up', 'Wiki pages with sources and history.', 'https://openvibe.wiki/', 'wiki learn read knowledge research look'],['services', 'fa-server', 'Build on OpenVibe', 'One console for projects, keys, grants and the API docs of every service.', 'https://openvibe.services/', 'build api sdk developer developers oauth webhook webhooks app integrate console platform'],['codes', 'fa-code', 'Code with any agent', 'Claude Code, Codex, OpenCode or your own model on one task, with hand-offs.', 'https://openvibe.codes/', 'code coding agent agents ai claude codex opencode deepseek harness contribute'],-- ['actor', 'fa-user-astronaut', 'Give a task to an agent', 'Actor picks the best agent for it, checks the answer and shows the cost and why.', 'https://openvibe.actor/', 'agent agents ai assistant task tasks automate research router openrouter do it for me'],++ ['actor', 'fa-user-astronaut', 'Give a task to an agent', 'Actor picks the best agent for it, checks the answer and shows the cost and why.', 'https://openvibe.actor/', 'agent agents ai assistant task tasks automate automation router openrouter delegate'],['work', 'fa-briefcase', 'Find work', 'Jobs from open boards in one search, each linked to where you apply.', 'https://openvibe.work/', 'job jobs work career hiring remote employment apply listings'],-- ['food', 'fa-utensils', 'Eat well for less', 'Food near you, meal plans and recipes for your budget, and delivery apps compared.', 'https://openvibe.food/', 'food eat meal meals plan grocery groceries cheap budget recipes food bank pantry delivery doordash ubereats grubhub instacart meal kit hellofresh'],++ ['food', 'fa-utensils', 'Eat well for less', 'Food near you, meal plans and recipes for your budget, and delivery apps compared.', 'https://openvibe.food/', 'food eat meal meals grocery groceries recipe recipes foodbank pantry delivery doordash ubereats grubhub instacart hellofresh'],['quest', 'fa-medal', 'Earn badges', 'Quests across the network for the things you already do there.', 'https://openvibe.quest/', 'quest quests badge badges achievement achievements rewards opencoins earn'],['rent', 'fa-house', 'Rent a place', 'Every rental site in one search, and one board for what you find there.', 'https://openvibe.rent/', 'rent rental rentals apartment apartments room house flat lease parking storage equipment craigslist zillow zumper'],-- ['homes', 'fa-house-chimney', 'Buy a home', 'Every for-sale site in one search, with county records and a mortgage calculator.', 'https://openvibe.homes/', 'buy home homes house houses for sale real estate realtor redfin mortgage property records land'],++ ['homes', 'fa-house-chimney', 'Buy a home', 'Every for-sale site in one search, with county records and a mortgage calculator.', 'https://openvibe.homes/', 'home homes house houses sale realestate realtor redfin zillow mortgage property records land'],['video', 'fa-film', 'Watch and upload videos', 'Upload or record a video and get AI captions, chapters and cuts, plus OpenVibe.Live\'s past streams.', 'https://openvibe.video/', 'video videos upload record vod vods recording recordings replay replays clip clips past stream streams highlights vlog captions'],['pics', 'fa-image', 'Share pictures', 'Upload a picture and get links to share or embed it anywhere, in any size.', 'https://openvibe.pics/', 'picture pictures image images photo photos imgur upload host embed gif album albums alt text'],['media-hub', 'fa-cloud-arrow-up', 'Keep and share files', 'A 25 GB drive with share links and folder backups.', 'https://openvibe.download/', 'download files drive upload share link storage backup sync cloud send mediafire mega'],nnew file mode 100644@@@ -0,0 +1,43 @@++'use strict';++// The home's "What do you want to do?" box (public/index.html's last script), run against the real intents++// (server/home/render.js INTENTS) in a small stand-in DOM: an app named clearly opens it, even in a question; a tool++// query stays a tool search; a question, a request in a sentence or a tie opens Vibe with the text filled in (plan++// track K); filler words ("find me a…") never pick an app.++const assert = require('assert');++const fs = require('fs');++const path = require('path');++const vm = require('vm');++const { INTENTS } = require('../server/home/render');++++const html = fs.readFileSync(path.join(__dirname, '..', 'public', 'index.html'), 'utf8');++const script = html.slice(html.lastIndexOf('<script>') + 8, html.lastIndexOf('</script>'));++++function route(text) {++ const intentEls = INTENTS.map(([id, , , , href, k]) => ({ href, classList: { toggle() {} }, getAttribute: (a) => ({ 'data-id': id, 'data-k': k })[a] }));++ let onSubmit = null;++ const input = { value: text, addEventListener() {} };++ const form = { addEventListener: (ev, fn) => { if (ev === 'submit') onSubmit = fn; } };++ const location = { href: null };++ const document = { querySelector: (s) => (s === '.intent-bar' ? form : null), getElementById: (id) => (id === 'intent-q' ? input : null), querySelectorAll: () => intentEls };++ vm.runInNewContext(script, { document, location, encodeURIComponent });++ let prevented = false;++ onSubmit({ preventDefault() { prevented = true; } });++ return prevented ? location.href : 'tool search';++}++const vibe = (q) => `https://openvibe.chat/vibe?ask=${encodeURIComponent(q)}&from=${encodeURIComponent('https://openvibe.network/')}`;++++const cases = [++ ['go live', 'https://openvibe.live/broadcast'],++ ['how do I go live?', 'https://openvibe.live/broadcast'],++ ['convert mp4 to mp3', 'tool search'],++ ['merge pdf', 'tool search'],++ ['find me a cheap apartment in austin', 'https://openvibe.rent/'],++ ['get help with my account', 'https://openvibe.help/'],++ ['what is the weather in paris?', vibe('what is the weather in paris?')],++ ['help me plan a birthday party for ten kids', vibe('help me plan a birthday party for ten kids')],++ ['ask vibe', vibe('ask vibe')],++ ['sourdough starter', vibe('sourdough starter')],++];++cases.forEach(([q, want]) => assert.strictEqual(route(q), want, `"${q}"`));++assert.strictEqual(route(' '), 'tool search', 'an empty box does nothing new');++console.log(`home intent routing: ${cases.length} queries routed`);
@@ -205,7 +205,7 @@ footer{border-top:1px solid var(--border);background:var(--bg-secondary)}
@@ -205,7 +205,7 @@ footer{border-top:1px solid var(--border);background:var(--bg-secondary)}
<form class="intent-bar" action="https://openvibe.tools/search" method="get" role="search">
<form class="intent-bar" action="https://openvibe.tools/search" method="get" role="search">
<label for="intent-q" class="sr-only">What do you want to do?</label>
<label for="intent-q" class="sr-only">What do you want to do?</label>
<i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
<i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
- <input id="intent-q" name="q" type="search" placeholder="What do you want to do?" title="Try “go live”, “merge pdf” or “chat”" autocomplete="off" enterkeyhint="go">
+ <input id="intent-q" name="q" type="search" placeholder="What do you want to do?" title="Try “go live”, “merge pdf”, or ask anything" autocomplete="off" enterkeyhint="go">
<button type="submit">Start</button>
<button type="submit">Start</button>
</form>
</form>
<!--OV:INTENTS-->
<!--OV:INTENTS-->
@@ -274,26 +274,41 @@ footer{border-top:1px solid var(--border);background:var(--bg-secondary)}
@@ -274,26 +274,41 @@ footer{border-top:1px solid var(--border);background:var(--bg-secondary)}
})();
})();
</script>
</script>
<script>
<script>
-// The hero's search: typing lights the intents it names; Enter opens the one it names clearly, and anything else is a
-// tool search on openvibe.tools (the form's own action, so it works without this script too).
+// The hero's search: typing lights the intents it names; Enter opens the one it names clearly, a tool query searches
+// openvibe.tools, and anything else (a question, a task in plain words) opens Vibe with the text filled in, never sent
+// (plan track K, the owner 10-24). Without this script the form's own action is the tool search.
(function () {
(function () {
var form = document.querySelector('.intent-bar'), q = document.getElementById('intent-q');
var form = document.querySelector('.intent-bar'), q = document.getElementById('intent-q');
if (!form || !q) return;
if (!form || !q) return;
var items = [].slice.call(document.querySelectorAll('.intent'));
var items = [].slice.call(document.querySelectorAll('.intent'));
- function words(v) { return v.toLowerCase().split(/[^a-z0-9]+/).filter(function (w) { return w.length > 1; }); }
+ // Filler words never pick an app ("find me a flat" is about the flat, not about "me").
+ var FILLER = ' a an and are can could do does for from have how i in is it me my of on or our please should so the their to want what when where which who why will with would you your ';
+ function words(v) { return v.toLowerCase().split(/[^a-z0-9]+/).filter(function (w) { return w.length > 1 && FILLER.indexOf(' ' + w + ' ') < 0; }); }
function score(el, ws) {
function score(el, ws) {
var keys = (el.getAttribute('data-k') || '').split(' '), s = 0;
var keys = (el.getAttribute('data-k') || '').split(' '), s = 0;
ws.forEach(function (w) { if (keys.indexOf(w) >= 0) s += 2; else if (w.length > 2 && keys.some(function (k) { return k.indexOf(w) === 0; })) s += 1; });
ws.forEach(function (w) { if (keys.indexOf(w) >= 0) s += 2; else if (w.length > 2 && keys.some(function (k) { return k.indexOf(w) === 0; })) s += 1; });
return s;
return s;
}
}
- function best(ws) { var top = null, ts = 0; items.forEach(function (el) { var s = score(el, ws); if (s > ts) { ts = s; top = el; } }); return { el: top, s: ts }; }
+ // The intent the words name most; two naming it equally is a tie (nobody wins, and the sentence goes to Vibe).
+ function best(ws) { var top = null, ts = 0, tie = false; items.forEach(function (el) { var s = score(el, ws); if (s > ts) { ts = s; top = el; tie = false; } else if (s && s === ts) tie = true; }); return { el: top, s: ts, tie: tie }; }
q.addEventListener('input', function () {
q.addEventListener('input', function () {
var ws = words(q.value), b = best(ws);
var ws = words(q.value), b = best(ws);
items.forEach(function (el) { var s = ws.length ? score(el, ws) : 1; el.classList.toggle('dim', ws.length > 0 && s === 0 && !!b.el); el.classList.toggle('match', !!b.el && el === b.el && b.s > 0); });
items.forEach(function (el) { var s = ws.length ? score(el, ws) : 1; el.classList.toggle('dim', ws.length > 0 && s === 0 && !!b.el); el.classList.toggle('match', !!b.el && el === b.el && b.s > 0); });
});
});
+ var VIBE = 'https://openvibe.chat/vibe';
form.addEventListener('submit', function (e) {
form.addEventListener('submit', function (e) {
- var b = best(words(q.value));
- if (b.el && b.s >= 2 && b.el.getAttribute('data-id') !== 'tools') { e.preventDefault(); location.href = b.el.href; }
+ var text = q.value.trim(), ws = words(text), b = best(ws), id = b.el && b.el.getAttribute('data-id');
+ if (!text) return;
+ var site = b.el && !b.tie && id !== 'tools' && id !== 'vibe';
+ var go = function (href) { e.preventDefault(); location.href = href; };
+ var vibe = function () { go(VIBE + '?ask=' + encodeURIComponent(text.slice(0, 2000)) + '&from=' + encodeURIComponent('https://openvibe.network/')); };
+ // An app named clearly wins, even in a question ("how do I go live?"); then a question or a request in a
+ // sentence goes to Vibe; then an app named once; a tool query stays a tool search; nothing else: Vibe.
+ if (site && b.s >= 4) return go(b.el.href);
+ if (id === 'vibe' || /\?\s*$/.test(text) || ws.length >= 5) return vibe();
+ if (site && b.s >= 2) return go(b.el.href);
+ if (id === 'tools' && b.s > 0) return undefined;
+ return vibe();
});
});
})();
})();
</script>
</script>
@@ -41,6 +41,7 @@ const POPULAR = ['yt', 'convert', 'mergepdf', 'jsonfmt', 'mp3', 'dns', 'whois',
@@ -41,6 +41,7 @@ const POPULAR = ['yt', 'convert', 'mergepdf', 'jsonfmt', 'mp3', 'dns', 'whois',
// The front door (plan T11, D93): what people come to do, each a real link to where it starts. `k` holds the words
// The front door (plan T11, D93): what people come to do, each a real link to where it starts. `k` holds the words
// the hero's search box matches (public/index.html); a query that names no intent strongly goes to the tool search.
// the hero's search box matches (public/index.html); a query that names no intent strongly goes to the tool search.
const INTENTS = [
const INTENTS = [
+ ['vibe', 'fa-brain', 'Ask Vibe', 'Questions, research and tasks in plain words: OpenVibe\'s assistant, with your chats kept.', 'https://openvibe.chat/vibe', 'ask vibe question questions answer explain research chatgpt claude gemini chat-gpt'],
['live', 'fa-tower-broadcast', 'Go live', 'From a browser tab. No OBS, no follower minimum.', 'https://openvibe.live/broadcast', 'go live stream streaming broadcast camera webcam screen obs whip rtmp twitch'],
['live', 'fa-tower-broadcast', 'Go live', 'From a browser tab. No OBS, no follower minimum.', 'https://openvibe.live/broadcast', 'go live stream streaming broadcast camera webcam screen obs whip rtmp twitch'],
['tools', 'fa-screwdriver-wrench', 'Get something done', '', 'https://openvibe.tools/', 'tool tools convert pdf mp3 mp4 video audio image compress resize dns whois json regex download youtube'],
['tools', 'fa-screwdriver-wrench', 'Get something done', '', 'https://openvibe.tools/', 'tool tools convert pdf mp3 mp4 video audio image compress resize dns whois json regex download youtube'],
['chat', 'fa-comments', 'Chat', 'Group chats with AI members, servers with text and voice channels, pictures and DMs.', 'https://openvibe.chat/', 'chat talk message messages dm room rooms friends group server servers channel channels voice discord slack ai bot members'],
['chat', 'fa-comments', 'Chat', 'Group chats with AI members, servers with text and voice channels, pictures and DMs.', 'https://openvibe.chat/', 'chat talk message messages dm room rooms friends group server servers channel channels voice discord slack ai bot members'],
@@ -53,12 +54,12 @@ const INTENTS = [
@@ -53,12 +54,12 @@ const INTENTS = [
['wiki', 'fa-book-open', 'Look it up', 'Wiki pages with sources and history.', 'https://openvibe.wiki/', 'wiki learn read knowledge research look'],
['wiki', 'fa-book-open', 'Look it up', 'Wiki pages with sources and history.', 'https://openvibe.wiki/', 'wiki learn read knowledge research look'],
['services', 'fa-server', 'Build on OpenVibe', 'One console for projects, keys, grants and the API docs of every service.', 'https://openvibe.services/', 'build api sdk developer developers oauth webhook webhooks app integrate console platform'],
['services', 'fa-server', 'Build on OpenVibe', 'One console for projects, keys, grants and the API docs of every service.', 'https://openvibe.services/', 'build api sdk developer developers oauth webhook webhooks app integrate console platform'],
['codes', 'fa-code', 'Code with any agent', 'Claude Code, Codex, OpenCode or your own model on one task, with hand-offs.', 'https://openvibe.codes/', 'code coding agent agents ai claude codex opencode deepseek harness contribute'],
['codes', 'fa-code', 'Code with any agent', 'Claude Code, Codex, OpenCode or your own model on one task, with hand-offs.', 'https://openvibe.codes/', 'code coding agent agents ai claude codex opencode deepseek harness contribute'],
- ['actor', 'fa-user-astronaut', 'Give a task to an agent', 'Actor picks the best agent for it, checks the answer and shows the cost and why.', 'https://openvibe.actor/', 'agent agents ai assistant task tasks automate research router openrouter do it for me'],
+ ['actor', 'fa-user-astronaut', 'Give a task to an agent', 'Actor picks the best agent for it, checks the answer and shows the cost and why.', 'https://openvibe.actor/', 'agent agents ai assistant task tasks automate automation router openrouter delegate'],
['work', 'fa-briefcase', 'Find work', 'Jobs from open boards in one search, each linked to where you apply.', 'https://openvibe.work/', 'job jobs work career hiring remote employment apply listings'],
['work', 'fa-briefcase', 'Find work', 'Jobs from open boards in one search, each linked to where you apply.', 'https://openvibe.work/', 'job jobs work career hiring remote employment apply listings'],
- ['food', 'fa-utensils', 'Eat well for less', 'Food near you, meal plans and recipes for your budget, and delivery apps compared.', 'https://openvibe.food/', 'food eat meal meals plan grocery groceries cheap budget recipes food bank pantry delivery doordash ubereats grubhub instacart meal kit hellofresh'],
+ ['food', 'fa-utensils', 'Eat well for less', 'Food near you, meal plans and recipes for your budget, and delivery apps compared.', 'https://openvibe.food/', 'food eat meal meals grocery groceries recipe recipes foodbank pantry delivery doordash ubereats grubhub instacart hellofresh'],
['quest', 'fa-medal', 'Earn badges', 'Quests across the network for the things you already do there.', 'https://openvibe.quest/', 'quest quests badge badges achievement achievements rewards opencoins earn'],
['quest', 'fa-medal', 'Earn badges', 'Quests across the network for the things you already do there.', 'https://openvibe.quest/', 'quest quests badge badges achievement achievements rewards opencoins earn'],
['rent', 'fa-house', 'Rent a place', 'Every rental site in one search, and one board for what you find there.', 'https://openvibe.rent/', 'rent rental rentals apartment apartments room house flat lease parking storage equipment craigslist zillow zumper'],
['rent', 'fa-house', 'Rent a place', 'Every rental site in one search, and one board for what you find there.', 'https://openvibe.rent/', 'rent rental rentals apartment apartments room house flat lease parking storage equipment craigslist zillow zumper'],
- ['homes', 'fa-house-chimney', 'Buy a home', 'Every for-sale site in one search, with county records and a mortgage calculator.', 'https://openvibe.homes/', 'buy home homes house houses for sale real estate realtor redfin mortgage property records land'],
+ ['homes', 'fa-house-chimney', 'Buy a home', 'Every for-sale site in one search, with county records and a mortgage calculator.', 'https://openvibe.homes/', 'home homes house houses sale realestate realtor redfin zillow mortgage property records land'],
['video', 'fa-film', 'Watch and upload videos', 'Upload or record a video and get AI captions, chapters and cuts, plus OpenVibe.Live\'s past streams.', 'https://openvibe.video/', 'video videos upload record vod vods recording recordings replay replays clip clips past stream streams highlights vlog captions'],
['video', 'fa-film', 'Watch and upload videos', 'Upload or record a video and get AI captions, chapters and cuts, plus OpenVibe.Live\'s past streams.', 'https://openvibe.video/', 'video videos upload record vod vods recording recordings replay replays clip clips past stream streams highlights vlog captions'],
['pics', 'fa-image', 'Share pictures', 'Upload a picture and get links to share or embed it anywhere, in any size.', 'https://openvibe.pics/', 'picture pictures image images photo photos imgur upload host embed gif album albums alt text'],
['pics', 'fa-image', 'Share pictures', 'Upload a picture and get links to share or embed it anywhere, in any size.', 'https://openvibe.pics/', 'picture pictures image images photo photos imgur upload host embed gif album albums alt text'],
['media-hub', 'fa-cloud-arrow-up', 'Keep and share files', 'A 25 GB drive with share links and folder backups.', 'https://openvibe.download/', 'download files drive upload share link storage backup sync cloud send mediafire mega'],
['media-hub', 'fa-cloud-arrow-up', 'Keep and share files', 'A 25 GB drive with share links and folder backups.', 'https://openvibe.download/', 'download files drive upload share link storage backup sync cloud send mediafire mega'],
new file mode 100644
new file mode 100644
@@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
+'use strict';
+// The home's "What do you want to do?" box (public/index.html's last script), run against the real intents
+// (server/home/render.js INTENTS) in a small stand-in DOM: an app named clearly opens it, even in a question; a tool
+// query stays a tool search; a question, a request in a sentence or a tie opens Vibe with the text filled in (plan
+// track K); filler words ("find me a…") never pick an app.
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+const vm = require('vm');
+const { INTENTS } = require('../server/home/render');
+
+const html = fs.readFileSync(path.join(__dirname, '..', 'public', 'index.html'), 'utf8');
+const script = html.slice(html.lastIndexOf('<script>') + 8, html.lastIndexOf('</script>'));
+
+function route(text) {
+ const intentEls = INTENTS.map(([id, , , , href, k]) => ({ href, classList: { toggle() {} }, getAttribute: (a) => ({ 'data-id': id, 'data-k': k })[a] }));
+ let onSubmit = null;
+ const input = { value: text, addEventListener() {} };
+ const form = { addEventListener: (ev, fn) => { if (ev === 'submit') onSubmit = fn; } };
+ const location = { href: null };
+ const document = { querySelector: (s) => (s === '.intent-bar' ? form : null), getElementById: (id) => (id === 'intent-q' ? input : null), querySelectorAll: () => intentEls };
+ vm.runInNewContext(script, { document, location, encodeURIComponent });
+ let prevented = false;
+ onSubmit({ preventDefault() { prevented = true; } });
+ return prevented ? location.href : 'tool search';
+}
+const vibe = (q) => `https://openvibe.chat/vibe?ask=${encodeURIComponent(q)}&from=${encodeURIComponent('https://openvibe.network/')}`;
+
+const cases = [
+ ['go live', 'https://openvibe.live/broadcast'],
+ ['how do I go live?', 'https://openvibe.live/broadcast'],
+ ['convert mp4 to mp3', 'tool search'],
+ ['merge pdf', 'tool search'],
+ ['find me a cheap apartment in austin', 'https://openvibe.rent/'],
+ ['get help with my account', 'https://openvibe.help/'],
+ ['what is the weather in paris?', vibe('what is the weather in paris?')],
+ ['help me plan a birthday party for ten kids', vibe('help me plan a birthday party for ten kids')],
+ ['ask vibe', vibe('ask vibe')],
+ ['sourdough starter', vibe('sourdough starter')],
+];
+cases.forEach(([q, want]) => assert.strictEqual(route(q), want, `"${q}"`));
+assert.strictEqual(route(' '), 'tool search', 'an empty box does nothing new');
+console.log(`home intent routing: ${cases.length} queries routed`);