feat: add online FAQ/KB sync without old bridge

This commit is contained in:
wangliang
2026-10-23 11:29:43 +08:00
parent f526696f93
commit 81b49c9350
24 changed files with 1793 additions and 5 deletions
+40
View File
@@ -0,0 +1,40 @@
'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { extractFaqFromLdJson } = require('../src/faq-fetcher');
const { updateMarkdownFile } = require('../src/faq-update');
test('faq: extracts FAQPage JSON-LD questions', () => {
const html = `<script type="application/ld+json">{"@type":"FAQPage","mainEntity":[{"name":"Q1?","acceptedAnswer":{"text":"A1"}},{"name":"Q2?","acceptedAnswer":{"text":"A2"}}]}</script>`;
const qs = extractFaqFromLdJson(html);
assert.deepEqual(qs, [{ q: 'Q1?', a: 'A1' }, { q: 'Q2?', a: 'A2' }]);
});
test('faq: updates existing answer and appends new question', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'kb-faq-'));
const file = path.join(dir, '01.md');
fs.writeFileSync(file, '---\ntitle: T\n---\n### Q: Old?\nold answer\n');
const r = updateMarkdownFile(file, [{ q: 'Old?', a: 'new answer' }, { q: 'New?', a: 'new q answer' }]);
assert.equal(r.changed, true);
assert.equal(r.updated, 1);
assert.equal(r.added, 1);
const out = fs.readFileSync(file, 'utf8');
assert.match(out, /Old\?\nnew answer/);
assert.match(out, /New\?\nnew q answer/);
});
test('faq: normalized question match avoids duplicate append', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'kb-faq-norm-'));
const file = path.join(dir, '01.md');
fs.writeFileSync(file, '---\ntitle: T\n---\n### Q: How does Brexit affect shipping\nold\n');
const r = updateMarkdownFile(file, [{ q: 'How does Brexit affect shipping?', a: 'new' }]);
assert.equal(r.added, 0);
assert.equal(r.updated, 1);
const out = fs.readFileSync(file, 'utf8');
assert.equal((out.match(/### Q:/g) || []).length, 1);
assert.match(out, /new/);
});
+18
View File
@@ -0,0 +1,18 @@
'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const { parseFrontmatter, splitChunks, createParentChildChunks } = require('../src/kb-chunker');
test('kb chunker: parse frontmatter and split Q/A', () => {
const md = `---\ntitle: Test\ncategory: test\nkeywords: [ring, gold]\nsource_url: https://www.yehwang.com/x.html\n---\n## Section\n### Q: What is it?\nLong answer ${'x'.repeat(600)}\n`;
const { meta, body } = parseFrontmatter(md);
assert.equal(meta.title, 'Test');
assert.deepEqual(meta.keywords, ['ring', 'gold']);
const chunks = splitChunks(body);
assert.equal(chunks.length, 1);
const { parent, children } = createParentChildChunks(chunks[0], meta, '01-test.md');
assert.equal(parent.question, 'What is it?');
assert.equal(parent.source_urls[0], 'https://www.yehwang.com/x.html');
assert.equal(children.length, 2);
});