openvibers/openvibe.live Commits
The Rundown never files a segment under a vague topic (a kind like chat, games or life, or 'the stream'), and vague names are left out of the known topics the AI reuses (35 segments had piled into chat, games and life) (#259)
Changes
@@@ -96,12 +96,13 @@ async function mentionsIn(text, speakerId) {// ── Topics ───────────────────────────────────────────────────async function knownTopics() {-- return (await db.all("SELECT title FROM rundown_topics WHERE status = 'live' AND last_seen_at >= ? ORDER BY last_seen_at DESC LIMIT ?", [daysAgo(30), KNOWN_TOPICS])).map((r) => r.title);++ return (await db.all("SELECT title FROM rundown_topics WHERE status = 'live' AND last_seen_at >= ? ORDER BY last_seen_at DESC LIMIT ?", [daysAgo(30), KNOWN_TOPICS * 2]))++ .map((r) => r.title).filter((t) => !filters.vagueTopic(t)).slice(0, KNOWN_TOPICS);}/** The topic for a name: the same slug, else a recent topic with most of the same key words, else a new one. */async function topicFor(title, kind, at) {const slug = filters.slugify(title);-- if (!slug) return null;++ if (!slug || filters.vagueTopic(title)) return null; // a segment with no real subject keeps no topiclet row = await db.get('SELECT id, title FROM rundown_topics WHERE slug = ?', [slug]);if (!row) {const recent = await db.all('SELECT id, title FROM rundown_topics WHERE last_seen_at >= ? ORDER BY last_seen_at DESC LIMIT 300', [daysAgo(60)]);@@@ -98,6 +98,17 @@ function privateDetail(text) {return PRIVATE.some((re) => re.test(t));}++// A topic name that names no subject: a kind ("chat", "games", "life"…) or a word for streaming itself. The model filed 35++// segments under "chat", "games" and "life" once the first of them was in the known topics it is asked to reuse.++const VAGUE = new Set(['news', 'games', 'game', 'tech', 'life', 'music', 'sports', 'money', 'internet', 'chat', 'other', 'gaming', 'gameplay',++ 'chatting', 'just chatting', 'stream', 'streaming', 'the stream', 'stream talk', 'misc', 'general', 'random', 'stuff', 'irl', 'talk', 'conversation',++ 'banter', 'reactions', 'updates', 'life update', 'nothing']);++/** Is this topic name too vague to be a topic? */++function vagueTopic(title) {++ const t = norm(title);++ return !t || VAGUE.has(t) || t.length < 4;++}++function slugify(title) {return norm(title).replace(/ /g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '').slice(0, 80).replace(/-$/, '');}@@@ -113,4 +124,4 @@ function sameTopic(a, b) {return both / (x.size + y.size - both) >= 0.6;}--module.exports = { gameLine, verifiedQuote, addsAccusation, privateDetail, slugify, sameTopic, norm, lineLang, GAME_LINES };++module.exports = { gameLine, verifiedQuote, addsAccusation, privateDetail, vagueTopic, slugify, sameTopic, norm, lineLang, GAME_LINES };@@@ -37,6 +37,8 @@ for (const t of ['back around eight mountain time', 'famous because of the schoofor (const t of ['First blood was mine', 'This is my favourite ride', 'Supermarket fridges hacked in France']) assert.ok(!filters.privateDetail(t), `not private: ${t}`);assert.ok(filters.sameTopic('League ranked matchmaking', 'Ranked matchmaking in League'));assert.ok(!filters.sameTopic('League ranked matchmaking', 'Moving apartments'));++['chat', 'Games', 'life', 'Just chatting', 'the stream', 'x'].forEach((t) => assert.ok(filters.vagueTopic(t), `vague: ${t}`));++assert.ok(!filters.vagueTopic('League ranked matchmaking') && !filters.vagueTopic('Halloween'));console.log('✅ filters: game lines, verbatim quotes, accusations, topics');// ── People and streams ──@@@ -257,12 +259,13 @@ console.log('✅ API: front page, topic, reactions as a signed-out reader; old /const prev = answers['live.rundown.segment'];answers['live.rundown.segment'] = (input) => input.streamer !== 'teenstreamer' ? prev(input) : { others: [], segments: [{ start: 0, end: 5, topic: 'Streaming later tonight', kind: 'chat', title: 'Back at eight mountain time', what: 'teenstreamer will stream again around eight mountain time.', take: null, quip: 'x', score: 3, quotes: [] },-- { start: 5, end: 300, topic: 'Theme park rides', kind: 'life', title: 'The best ride in the park', what: 'teenstreamer loved a ride.', take: 'This ride is the best.', quip: 'See you after school.', score: 3,++ { start: 5, end: 300, topic: 'chat', kind: 'life', title: 'The best ride in the park', what: 'teenstreamer loved a ride.', take: 'This ride is the best.', quip: 'See you after school.', score: 3,quotes: [{ t: 0, text: 'I might live again later around eight mountain time', en: null }, { t: 10, text: 'this ride is honestly the best ride in the whole park', en: null }] },] };desk._test.reset();await desk.tick();const kept = await db.all('SELECT title, quip, quotes_json FROM rundown_segments WHERE stream_id = ?', [st]);++ assert.ok(!await db.get("SELECT 1 FROM rundown_topics WHERE slug = 'chat'"), 'a vague topic is never made');assert.deepStrictEqual(kept.map((k) => k.title), ['The best ride in the park'], 'a segment naming a time zone is dropped');assert.strictEqual(kept[0].quip, null, 'a quip about school is dropped');assert.deepStrictEqual(JSON.parse(kept[0].quotes_json).map((q) => q.text), ['this ride is honestly the best ride in the whole park'], 'a quote with a time zone is dropped');
@@ -96,12 +96,13 @@ async function mentionsIn(text, speakerId) {
@@ -96,12 +96,13 @@ async function mentionsIn(text, speakerId) {
// ── Topics ───────────────────────────────────────────────────
// ── Topics ───────────────────────────────────────────────────
async function knownTopics() {
async function knownTopics() {
- return (await db.all("SELECT title FROM rundown_topics WHERE status = 'live' AND last_seen_at >= ? ORDER BY last_seen_at DESC LIMIT ?", [daysAgo(30), KNOWN_TOPICS])).map((r) => r.title);
+ return (await db.all("SELECT title FROM rundown_topics WHERE status = 'live' AND last_seen_at >= ? ORDER BY last_seen_at DESC LIMIT ?", [daysAgo(30), KNOWN_TOPICS * 2]))
+ .map((r) => r.title).filter((t) => !filters.vagueTopic(t)).slice(0, KNOWN_TOPICS);
}
}
/** The topic for a name: the same slug, else a recent topic with most of the same key words, else a new one. */
/** The topic for a name: the same slug, else a recent topic with most of the same key words, else a new one. */
async function topicFor(title, kind, at) {
async function topicFor(title, kind, at) {
const slug = filters.slugify(title);
const slug = filters.slugify(title);
- if (!slug) return null;
+ if (!slug || filters.vagueTopic(title)) return null; // a segment with no real subject keeps no topic
let row = await db.get('SELECT id, title FROM rundown_topics WHERE slug = ?', [slug]);
let row = await db.get('SELECT id, title FROM rundown_topics WHERE slug = ?', [slug]);
if (!row) {
if (!row) {
const recent = await db.all('SELECT id, title FROM rundown_topics WHERE last_seen_at >= ? ORDER BY last_seen_at DESC LIMIT 300', [daysAgo(60)]);
const recent = await db.all('SELECT id, title FROM rundown_topics WHERE last_seen_at >= ? ORDER BY last_seen_at DESC LIMIT 300', [daysAgo(60)]);
@@ -98,6 +98,17 @@ function privateDetail(text) {
@@ -98,6 +98,17 @@ function privateDetail(text) {
return PRIVATE.some((re) => re.test(t));
return PRIVATE.some((re) => re.test(t));
}
}
+// A topic name that names no subject: a kind ("chat", "games", "life"…) or a word for streaming itself. The model filed 35
+// segments under "chat", "games" and "life" once the first of them was in the known topics it is asked to reuse.
+const VAGUE = new Set(['news', 'games', 'game', 'tech', 'life', 'music', 'sports', 'money', 'internet', 'chat', 'other', 'gaming', 'gameplay',
+ 'chatting', 'just chatting', 'stream', 'streaming', 'the stream', 'stream talk', 'misc', 'general', 'random', 'stuff', 'irl', 'talk', 'conversation',
+ 'banter', 'reactions', 'updates', 'life update', 'nothing']);
+/** Is this topic name too vague to be a topic? */
+function vagueTopic(title) {
+ const t = norm(title);
+ return !t || VAGUE.has(t) || t.length < 4;
+}
+
function slugify(title) {
function slugify(title) {
return norm(title).replace(/ /g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '').slice(0, 80).replace(/-$/, '');
return norm(title).replace(/ /g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '').slice(0, 80).replace(/-$/, '');
}
}
@@ -113,4 +124,4 @@ function sameTopic(a, b) {
@@ -113,4 +124,4 @@ function sameTopic(a, b) {
return both / (x.size + y.size - both) >= 0.6;
return both / (x.size + y.size - both) >= 0.6;
}
}
-module.exports = { gameLine, verifiedQuote, addsAccusation, privateDetail, slugify, sameTopic, norm, lineLang, GAME_LINES };
+module.exports = { gameLine, verifiedQuote, addsAccusation, privateDetail, vagueTopic, slugify, sameTopic, norm, lineLang, GAME_LINES };
@@ -37,6 +37,8 @@ for (const t of ['back around eight mountain time', 'famous because of the schoo
@@ -37,6 +37,8 @@ for (const t of ['back around eight mountain time', 'famous because of the schoo
for (const t of ['First blood was mine', 'This is my favourite ride', 'Supermarket fridges hacked in France']) assert.ok(!filters.privateDetail(t), `not private: ${t}`);
for (const t of ['First blood was mine', 'This is my favourite ride', 'Supermarket fridges hacked in France']) assert.ok(!filters.privateDetail(t), `not private: ${t}`);
assert.ok(filters.sameTopic('League ranked matchmaking', 'Ranked matchmaking in League'));
assert.ok(filters.sameTopic('League ranked matchmaking', 'Ranked matchmaking in League'));
assert.ok(!filters.sameTopic('League ranked matchmaking', 'Moving apartments'));
assert.ok(!filters.sameTopic('League ranked matchmaking', 'Moving apartments'));
+['chat', 'Games', 'life', 'Just chatting', 'the stream', 'x'].forEach((t) => assert.ok(filters.vagueTopic(t), `vague: ${t}`));
+assert.ok(!filters.vagueTopic('League ranked matchmaking') && !filters.vagueTopic('Halloween'));
console.log('✅ filters: game lines, verbatim quotes, accusations, topics');
console.log('✅ filters: game lines, verbatim quotes, accusations, topics');
// ── People and streams ──
// ── People and streams ──
@@ -257,12 +259,13 @@ console.log('✅ API: front page, topic, reactions as a signed-out reader; old /
@@ -257,12 +259,13 @@ console.log('✅ API: front page, topic, reactions as a signed-out reader; old /
const prev = answers['live.rundown.segment'];
const prev = answers['live.rundown.segment'];
answers['live.rundown.segment'] = (input) => input.streamer !== 'teenstreamer' ? prev(input) : { others: [], segments: [
answers['live.rundown.segment'] = (input) => input.streamer !== 'teenstreamer' ? prev(input) : { others: [], segments: [
{ start: 0, end: 5, topic: 'Streaming later tonight', kind: 'chat', title: 'Back at eight mountain time', what: 'teenstreamer will stream again around eight mountain time.', take: null, quip: 'x', score: 3, quotes: [] },
{ start: 0, end: 5, topic: 'Streaming later tonight', kind: 'chat', title: 'Back at eight mountain time', what: 'teenstreamer will stream again around eight mountain time.', take: null, quip: 'x', score: 3, quotes: [] },
- { start: 5, end: 300, topic: 'Theme park rides', kind: 'life', title: 'The best ride in the park', what: 'teenstreamer loved a ride.', take: 'This ride is the best.', quip: 'See you after school.', score: 3,
+ { start: 5, end: 300, topic: 'chat', kind: 'life', title: 'The best ride in the park', what: 'teenstreamer loved a ride.', take: 'This ride is the best.', quip: 'See you after school.', score: 3,
quotes: [{ t: 0, text: 'I might live again later around eight mountain time', en: null }, { t: 10, text: 'this ride is honestly the best ride in the whole park', en: null }] },
quotes: [{ t: 0, text: 'I might live again later around eight mountain time', en: null }, { t: 10, text: 'this ride is honestly the best ride in the whole park', en: null }] },
] };
] };
desk._test.reset();
desk._test.reset();
await desk.tick();
await desk.tick();
const kept = await db.all('SELECT title, quip, quotes_json FROM rundown_segments WHERE stream_id = ?', [st]);
const kept = await db.all('SELECT title, quip, quotes_json FROM rundown_segments WHERE stream_id = ?', [st]);
+ assert.ok(!await db.get("SELECT 1 FROM rundown_topics WHERE slug = 'chat'"), 'a vague topic is never made');
assert.deepStrictEqual(kept.map((k) => k.title), ['The best ride in the park'], 'a segment naming a time zone is dropped');
assert.deepStrictEqual(kept.map((k) => k.title), ['The best ride in the park'], 'a segment naming a time zone is dropped');
assert.strictEqual(kept[0].quip, null, 'a quip about school is dropped');
assert.strictEqual(kept[0].quip, null, 'a quip about school is dropped');
assert.deepStrictEqual(JSON.parse(kept[0].quotes_json).map((q) => q.text), ['this ride is honestly the best ride in the whole park'], 'a quote with a time zone is dropped');
assert.deepStrictEqual(JSON.parse(kept[0].quotes_json).map((q) => q.text), ['this ride is honestly the best ride in the whole park'], 'a quote with a time zone is dropped');