feat: port quality, escalation, KB version, site-map KB, experiments and metrics

This commit is contained in:
wangliang
2026-10-23 11:41:09 +08:00
parent 81b49c9350
commit b778570045
33 changed files with 2446 additions and 344 deletions
+51
View File
@@ -0,0 +1,51 @@
'use strict';
// KB 版本查看/回滚:备份表命名 kb_docs_v<timestamp>,当前表为 kb_docs。
function quoteIdent(name) {
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) throw new Error(`invalid identifier: ${name}`);
return `"${name}"`;
}
async function listVersions(pool) {
const backups = await pool.query(
`SELECT tablename FROM pg_tables WHERE schemaname='public' AND tablename LIKE 'kb_docs_v%' ORDER BY tablename DESC`
);
const runs = await pool.query('SELECT * FROM kb_sync_runs ORDER BY id DESC LIMIT 20').catch(() => ({ rows: [] }));
return { backups: backups.rows.map((r) => r.tablename), runs: runs.rows };
}
async function rollback(pool, target) {
if (!/^kb_docs_v[a-zA-Z0-9_]+$/.test(String(target))) throw new Error(`invalid backup table: ${target}`);
const exists = await pool.query('SELECT to_regclass($1) IS NOT NULL AS ok', [`public.${target}`]);
if (!exists.rows[0].ok) throw new Error(`backup not found: ${target}`);
const hasCurrent = await pool.query("SELECT to_regclass('public.kb_docs') IS NOT NULL AS ok");
const stamp = new Date().toISOString().replace(/[-:TZ.]/g, '').slice(0, 14);
const currentBackup = `kb_docs_before_rollback_${stamp}`;
const client = await pool.connect();
try {
await client.query('BEGIN');
if (hasCurrent.rows[0].ok) {
const idx = await client.query("SELECT indexname FROM pg_indexes WHERE schemaname='public' AND tablename='kb_docs'");
await client.query(`ALTER TABLE kb_docs RENAME TO ${quoteIdent(currentBackup)}`);
for (const row of idx.rows) {
const newName = `${currentBackup}_${row.indexname.replace(/^kb_docs_/, '').replace(/_idx$/, '')}_idx`;
await client.query(`ALTER INDEX ${quoteIdent(row.indexname)} RENAME TO ${quoteIdent(newName)}`).catch(() => {});
}
}
await client.query(`ALTER TABLE ${quoteIdent(target)} RENAME TO kb_docs`);
const targetIdx = await client.query(`SELECT indexname FROM pg_indexes WHERE schemaname='public' AND tablename='kb_docs'`);
for (const row of targetIdx.rows) {
const suffix = row.indexname.includes('vec') ? 'vec_idx' : row.indexname.includes('tsv') ? 'tsv_idx' : row.indexname.includes('parent') ? 'parent_idx' : null;
if (suffix) await client.query(`ALTER INDEX ${quoteIdent(row.indexname)} RENAME TO ${quoteIdent('kb_docs_' + suffix)}`).catch(() => {});
}
await client.query('COMMIT');
return { rolled_back_to: target, previous: hasCurrent.rows[0].ok ? currentBackup : null };
} catch (err) {
await client.query('ROLLBACK');
throw err;
} finally {
client.release();
}
}
module.exports = { listVersions, rollback };