openvibers/openvibe.live Commits
The looks repair scans card lines on their own too: 15 clip card lines with no full text still described people after #298 (#299)
Changes
@@@ -735,8 +735,10 @@ function _extractDescFromMaybeJson(text) {// The looks repair below scans ~20k rows with a regex (~0.8 s of database time on every boot). New rows are scrubbed when// written, so the scan only needs to run again when the scrub rules change: this hash of their source says when.let _looksRulesVersion = null;++// What the repair covers is part of its version: a change in scope runs the scan again even when the rules did not change.++const _LOOKS_REPAIR_SCOPE = 'full and short columns, each on its own (2026-10-22)';function looksRulesVersion() {-- if (!_looksRulesVersion) _looksRulesVersion = require('crypto').createHash('sha1').update([_noLooks, _noLooksWords, _noGender, _noGrooming, _noClothing, _LOOKS, _PEOPLE, _WORN, _WORN_ANY, _FACE, _GENDER_ADJ, _GROOMING].map(String).join('|')).digest('hex').slice(0, 12);++ if (!_looksRulesVersion) _looksRulesVersion = require('crypto').createHash('sha1').update([_LOOKS_REPAIR_SCOPE, _noLooks, _noLooksWords, _noGender, _noGrooming, _noClothing, _LOOKS, _PEOPLE, _WORN, _WORN_ANY, _FACE, _GENDER_ADJ, _GROOMING].map(String).join('|')).digest('hex').slice(0, 12);return _looksRulesVersion;}async function cleanupMalformedAiText({ force = false } = {}) {@@@ -796,6 +798,17 @@ async function cleanupMalformedAiText({ force = false } = {}) {else await run(`UPDATE ${table} SET ${col} = ? WHERE ${key} = ?`, [clean, r.k]);fixed++;}++ // A card line can be stored without its full text (or derived from an older one): it is repaired on its own.++ // ' ' is the "nothing to say" sentinel and stays when the whole line goes.++ if (short) {++ const shorts = await all(`SELECT ${key} AS k, ${short} AS v FROM ${table} WHERE ${short} ${looks}`);++ for (const r of shorts) {++ const clean = _noLooks(r.v);++ if (clean === r.v) continue;++ await run(`UPDATE ${table} SET ${short} = ? WHERE ${key} = ?`, [clean && clean.trim() ? clean : ' ', r.k]);++ fixed++;++ }++ }} catch (e) { console.warn(`[AI] looks repair (${table}):`, e.message); looksDone = null; }}// Recorded only after a clean pass, so a failed table is tried again at the next boot.@@@ -90,6 +90,13 @@ const db = require('../server/db/database');await db.setState('ai_looks_repair_rules', 'an older rule set');await db.cleanupMalformedAiText();assert.strictEqual((await db.get('SELECT description FROM stream_memories WHERE offset_seconds = 9240')).description, 'A person cooks pasta.');++ // A card line stored without its full text (or derived from an older one) is repaired on its own.++ await db.run('INSERT INTO clip_ai_state (clip_id, ai_overview_short) VALUES (?, ?)', [990001, 'The video shows a man wearing a headset in a kitchen.']);++ await db.run('INSERT INTO clip_ai_state (clip_id, ai_overview_short) VALUES (?, ?)', [990002, 'A person adjusts their hair or clothing near the counter.']);++ await db.setState('ai_looks_repair_rules', 'an older rule set');++ await db.cleanupMalformedAiText();++ assert.strictEqual((await db.get('SELECT ai_overview_short FROM clip_ai_state WHERE clip_id = 990001')).ai_overview_short, 'The video shows a person in a kitchen.');++ assert.strictEqual((await db.get('SELECT ai_overview_short FROM clip_ai_state WHERE clip_id = 990002')).ai_overview_short, ' ', 'a line that is all looks becomes the nothing-to-say sentinel');assert.strictEqual((await db.get('SELECT description FROM stream_memories WHERE offset_seconds = 9000')).description, 'The streamer rests his chin on his hand.');assert.deepStrictEqual(Object.values(await db.get('SELECT ai_overview, ai_overview_short FROM streams WHERE id = ?', [sid])), ['The streamer plays Fortnite.', 'The streamer plays Fortnite.']);assert.strictEqual((await db.get('SELECT description FROM stream_memories WHERE offset_seconds = 9060')).description, 'An older version of the game loads.', 'looks words about things stay');
@@ -735,8 +735,10 @@ function _extractDescFromMaybeJson(text) {
@@ -735,8 +735,10 @@ function _extractDescFromMaybeJson(text) {
// The looks repair below scans ~20k rows with a regex (~0.8 s of database time on every boot). New rows are scrubbed when
// The looks repair below scans ~20k rows with a regex (~0.8 s of database time on every boot). New rows are scrubbed when
// written, so the scan only needs to run again when the scrub rules change: this hash of their source says when.
// written, so the scan only needs to run again when the scrub rules change: this hash of their source says when.
let _looksRulesVersion = null;
let _looksRulesVersion = null;
+// What the repair covers is part of its version: a change in scope runs the scan again even when the rules did not change.
+const _LOOKS_REPAIR_SCOPE = 'full and short columns, each on its own (2026-10-22)';
function looksRulesVersion() {
function looksRulesVersion() {
- if (!_looksRulesVersion) _looksRulesVersion = require('crypto').createHash('sha1').update([_noLooks, _noLooksWords, _noGender, _noGrooming, _noClothing, _LOOKS, _PEOPLE, _WORN, _WORN_ANY, _FACE, _GENDER_ADJ, _GROOMING].map(String).join('|')).digest('hex').slice(0, 12);
+ if (!_looksRulesVersion) _looksRulesVersion = require('crypto').createHash('sha1').update([_LOOKS_REPAIR_SCOPE, _noLooks, _noLooksWords, _noGender, _noGrooming, _noClothing, _LOOKS, _PEOPLE, _WORN, _WORN_ANY, _FACE, _GENDER_ADJ, _GROOMING].map(String).join('|')).digest('hex').slice(0, 12);
return _looksRulesVersion;
return _looksRulesVersion;
}
}
async function cleanupMalformedAiText({ force = false } = {}) {
async function cleanupMalformedAiText({ force = false } = {}) {
@@ -796,6 +798,17 @@ async function cleanupMalformedAiText({ force = false } = {}) {
@@ -796,6 +798,17 @@ async function cleanupMalformedAiText({ force = false } = {}) {
else await run(`UPDATE ${table} SET ${col} = ? WHERE ${key} = ?`, [clean, r.k]);
else await run(`UPDATE ${table} SET ${col} = ? WHERE ${key} = ?`, [clean, r.k]);
fixed++;
fixed++;
}
}
+ // A card line can be stored without its full text (or derived from an older one): it is repaired on its own.
+ // ' ' is the "nothing to say" sentinel and stays when the whole line goes.
+ if (short) {
+ const shorts = await all(`SELECT ${key} AS k, ${short} AS v FROM ${table} WHERE ${short} ${looks}`);
+ for (const r of shorts) {
+ const clean = _noLooks(r.v);
+ if (clean === r.v) continue;
+ await run(`UPDATE ${table} SET ${short} = ? WHERE ${key} = ?`, [clean && clean.trim() ? clean : ' ', r.k]);
+ fixed++;
+ }
+ }
} catch (e) { console.warn(`[AI] looks repair (${table}):`, e.message); looksDone = null; }
} catch (e) { console.warn(`[AI] looks repair (${table}):`, e.message); looksDone = null; }
}
}
// Recorded only after a clean pass, so a failed table is tried again at the next boot.
// Recorded only after a clean pass, so a failed table is tried again at the next boot.
@@ -90,6 +90,13 @@ const db = require('../server/db/database');
@@ -90,6 +90,13 @@ const db = require('../server/db/database');
await db.setState('ai_looks_repair_rules', 'an older rule set');
await db.setState('ai_looks_repair_rules', 'an older rule set');
await db.cleanupMalformedAiText();
await db.cleanupMalformedAiText();
assert.strictEqual((await db.get('SELECT description FROM stream_memories WHERE offset_seconds = 9240')).description, 'A person cooks pasta.');
assert.strictEqual((await db.get('SELECT description FROM stream_memories WHERE offset_seconds = 9240')).description, 'A person cooks pasta.');
+ // A card line stored without its full text (or derived from an older one) is repaired on its own.
+ await db.run('INSERT INTO clip_ai_state (clip_id, ai_overview_short) VALUES (?, ?)', [990001, 'The video shows a man wearing a headset in a kitchen.']);
+ await db.run('INSERT INTO clip_ai_state (clip_id, ai_overview_short) VALUES (?, ?)', [990002, 'A person adjusts their hair or clothing near the counter.']);
+ await db.setState('ai_looks_repair_rules', 'an older rule set');
+ await db.cleanupMalformedAiText();
+ assert.strictEqual((await db.get('SELECT ai_overview_short FROM clip_ai_state WHERE clip_id = 990001')).ai_overview_short, 'The video shows a person in a kitchen.');
+ assert.strictEqual((await db.get('SELECT ai_overview_short FROM clip_ai_state WHERE clip_id = 990002')).ai_overview_short, ' ', 'a line that is all looks becomes the nothing-to-say sentinel');
assert.strictEqual((await db.get('SELECT description FROM stream_memories WHERE offset_seconds = 9000')).description, 'The streamer rests his chin on his hand.');
assert.strictEqual((await db.get('SELECT description FROM stream_memories WHERE offset_seconds = 9000')).description, 'The streamer rests his chin on his hand.');
assert.deepStrictEqual(Object.values(await db.get('SELECT ai_overview, ai_overview_short FROM streams WHERE id = ?', [sid])), ['The streamer plays Fortnite.', 'The streamer plays Fortnite.']);
assert.deepStrictEqual(Object.values(await db.get('SELECT ai_overview, ai_overview_short FROM streams WHERE id = ?', [sid])), ['The streamer plays Fortnite.', 'The streamer plays Fortnite.']);
assert.strictEqual((await db.get('SELECT description FROM stream_memories WHERE offset_seconds = 9060')).description, 'An older version of the game loads.', 'looks words about things stay');
assert.strictEqual((await db.get('SELECT description FROM stream_memories WHERE offset_seconds = 9060')).description, 'An older version of the game loads.', 'looks words about things stay');