feat: add online FAQ/KB sync without old bridge
This commit is contained in:
Executable
+73
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
// 在线 FAQ/KB 同步(不依赖旧 bridge):
|
||||
// 抓官网 FAQ JSON-LD → 更新 bridge/kb/*.md → parent/child + embedding → 原子切换 kb_docs
|
||||
// 用法:
|
||||
// node scripts/sync-kb-online.js [--force]
|
||||
// 需要环境变量:
|
||||
// RAG_PG_URL / EMB_BASE_URL / EMB_API_KEY / EMB_MODEL / EMB_DIM
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { loadConfig } = require('../bridge/src/config');
|
||||
const { createEmbeddingClient } = require('../bridge/src/embedding');
|
||||
const { createKbIndexer } = require('../bridge/src/kb-indexer');
|
||||
const { createFaqFetcher, loadFaqConfig } = require('../bridge/src/faq-fetcher');
|
||||
const { updateMarkdownFile } = require('../bridge/src/faq-update');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
const env = {};
|
||||
for (const line of fs.readFileSync(path.join(ROOT, '.env'), 'utf8').split('\n')) {
|
||||
if (!line.includes('=') || line.trim().startsWith('#')) continue;
|
||||
const i = line.indexOf('=');
|
||||
env[line.slice(0, i).trim()] = line.slice(i + 1).trim();
|
||||
}
|
||||
const cfg = loadConfig({ ...env, ...process.env });
|
||||
const force = process.argv.includes('--force');
|
||||
const localOnly = process.argv.includes('--local-only');
|
||||
const KB_DIR = path.join(ROOT, 'bridge', 'kb');
|
||||
const FAQ_CONFIG = path.join(ROOT, 'bridge', 'config', 'faq.json');
|
||||
|
||||
(async () => {
|
||||
if (!cfg.ragPgUrl) throw new Error('RAG_PG_URL is not configured');
|
||||
if (!cfg.embApiKey || !cfg.embBaseUrl) throw new Error('EMB_* is not configured');
|
||||
const faqCfg = loadFaqConfig(FAQ_CONFIG);
|
||||
if (!faqCfg.enabled) throw new Error('faq.json disabled');
|
||||
|
||||
let changedFiles = 0;
|
||||
let added = 0;
|
||||
let updated = 0;
|
||||
if (!localOnly) {
|
||||
const fetcher = createFaqFetcher(faqCfg);
|
||||
const fetched = await fetcher.fetchAll();
|
||||
for (const page of fetched) {
|
||||
if (page.error || !page.questions.length) {
|
||||
console.log(JSON.stringify({ ts: new Date().toISOString(), faq_fetch: 'skip', file: page.file, error: page.error || 'no_questions' }));
|
||||
continue;
|
||||
}
|
||||
const filePath = path.join(KB_DIR, page.file);
|
||||
const res = updateMarkdownFile(filePath, page.questions);
|
||||
if (res.changed) changedFiles += 1;
|
||||
added += res.added || 0;
|
||||
updated += res.updated || 0;
|
||||
console.log(JSON.stringify({ ts: new Date().toISOString(), faq_fetch: 'ok', file: page.file, questions: page.questions.length, added: res.added || 0, updated: res.updated || 0 }));
|
||||
}
|
||||
}
|
||||
|
||||
if (!localOnly && !force && changedFiles === 0) {
|
||||
console.log(JSON.stringify({ ts: new Date().toISOString(), kb_sync: 'skipped', reason: 'no_faq_changes' }));
|
||||
return;
|
||||
}
|
||||
|
||||
const emb = createEmbeddingClient(cfg);
|
||||
const indexer = createKbIndexer({ pool: emb.pool, embeddingClient: emb, kbDir: KB_DIR, dim: cfg.embDim });
|
||||
try {
|
||||
const result = await indexer.reindex({ note: `sync-kb-online ${new Date().toISOString().slice(0, 10)}` });
|
||||
console.log(JSON.stringify({ ts: new Date().toISOString(), kb_sync: 'done', changed_files: changedFiles, added, updated, ...result }));
|
||||
} finally {
|
||||
await emb.pool.end().catch(() => {});
|
||||
}
|
||||
})().catch((err) => {
|
||||
console.error('[sync-kb-online]', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user