74 lines
3.1 KiB
JavaScript
Executable File
74 lines
3.1 KiB
JavaScript
Executable File
#!/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);
|
|
});
|