merge: 拉取 origin/main(KB snapshot + 在线 FAQ 同步 + 环境文档)
This commit is contained in:
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
# 从已有环境导出 KB(kb_docs)快照,供没有旧 bridge 的测试/生产环境导入。
|
||||
# 用法: bash scripts/export-kb-snapshot.sh [输出文件]
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
OUT="${1:-$ROOT/deploy/seeds/kb_docs.sql.gz}"
|
||||
SRC_CONTAINER="${SOURCE_PG_CONTAINER:-crm-postgres}"
|
||||
SRC_DB="${SOURCE_PG_DB:-products}"
|
||||
SRC_USER="${SOURCE_PG_USER:-postgres}"
|
||||
mkdir -p "$(dirname "$OUT")"
|
||||
docker exec "$SRC_CONTAINER" pg_dump -U "$SRC_USER" -d "$SRC_DB" \
|
||||
-t kb_docs --no-owner --no-privileges | gzip -9 > "$OUT"
|
||||
echo "[export-kb] $SRC_CONTAINER/$SRC_DB.kb_docs -> $OUT ($(du -h "$OUT" | cut -f1))"
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
# 在没有旧 bridge 的环境导入 KB 快照(默认导入 crmnew-postgres/bridge)。
|
||||
# 用法:
|
||||
# bash scripts/import-kb-snapshot.sh [seed.sql.gz]
|
||||
# FORCE=1 bash scripts/import-kb-snapshot.sh # 覆盖已有 kb_docs(会 DROP)
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
SEED="${1:-$ROOT/deploy/seeds/kb_docs.sql.gz}"
|
||||
TARGET_CONTAINER="${TARGET_PG_CONTAINER:-crmnew-postgres}"
|
||||
TARGET_DB="${TARGET_PG_DB:-bridge}"
|
||||
TARGET_USER="${TARGET_PG_USER:-crmnew}"
|
||||
|
||||
if [ ! -f "$SEED" ]; then echo "seed not found: $SEED" >&2; exit 1; fi
|
||||
|
||||
# 确保数据库存在
|
||||
if ! docker exec "$TARGET_CONTAINER" psql -U "$TARGET_USER" -d postgres -tAc \
|
||||
"SELECT 1 FROM pg_database WHERE datname='$TARGET_DB'" | grep -q 1; then
|
||||
docker exec "$TARGET_CONTAINER" psql -U "$TARGET_USER" -d postgres -c "CREATE DATABASE \"$TARGET_DB\""
|
||||
fi
|
||||
|
||||
docker exec "$TARGET_CONTAINER" psql -U "$TARGET_USER" -d "$TARGET_DB" -c \
|
||||
"CREATE EXTENSION IF NOT EXISTS vector;" >/dev/null
|
||||
|
||||
# 旧 dump 带 tsv trigger,先确保函数存在
|
||||
docker exec -i "$TARGET_CONTAINER" psql -U "$TARGET_USER" -d "$TARGET_DB" >/dev/null <<'SQL'
|
||||
CREATE OR REPLACE FUNCTION public.kb_docs_tsv_update() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
NEW.text_tsv := to_tsvector('english',
|
||||
coalesce(NEW.question,'') || ' ' || coalesce(NEW.answer,'') || ' ' ||
|
||||
coalesce(array_to_string(NEW.keywords,' '),'') || ' ' ||
|
||||
coalesce(NEW.title,'') || ' ' || coalesce(NEW.section,''));
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
SQL
|
||||
|
||||
EXISTS=$(docker exec "$TARGET_CONTAINER" psql -U "$TARGET_USER" -d "$TARGET_DB" -tAc \
|
||||
"SELECT to_regclass('public.kb_docs') IS NOT NULL")
|
||||
if [ "$EXISTS" = "t" ]; then
|
||||
if [ "${FORCE:-0}" != "1" ]; then
|
||||
echo "[import-kb] kb_docs already exists in $TARGET_DB; refusing to overwrite. Set FORCE=1 to drop it." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[import-kb] FORCE=1: dropping existing kb_docs in $TARGET_DB"
|
||||
docker exec "$TARGET_CONTAINER" psql -U "$TARGET_USER" -d "$TARGET_DB" -c "DROP TABLE IF EXISTS kb_docs CASCADE" >/dev/null
|
||||
fi
|
||||
|
||||
gunzip -c "$SEED" | docker exec -i "$TARGET_CONTAINER" psql -U "$TARGET_USER" -d "$TARGET_DB" -v ON_ERROR_STOP=1 >/dev/null
|
||||
|
||||
COUNT=$(docker exec "$TARGET_CONTAINER" psql -U "$TARGET_USER" -d "$TARGET_DB" -tAc "SELECT count(*) FROM kb_docs")
|
||||
echo "[import-kb] imported kb_docs rows=$COUNT into $TARGET_CONTAINER/$TARGET_DB"
|
||||
echo "[import-kb] 提示: bridge 的 RAG_PG_URL 应指向该库(容器内通常是 postgres://...@postgres:5432/$TARGET_DB)"
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# 启动/重启公网 HTTPS(Cloudflare Quick Tunnel),并把 LibreDesk root_url 指过去。
|
||||
# 仅本地联调:启动/重启公网 HTTPS(Cloudflare Quick Tunnel),并把 LibreDesk root_url 指过去。
|
||||
# 测试/生产不要用 Quick Tunnel:使用正式域名 + Ingress/Nginx/ALB + TLS。
|
||||
# 注意:quick tunnel 每次重启 URL 会变,需要重新跑本脚本(Meta 里的 webhook 也要同步改)。
|
||||
set -euo pipefail
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/.."
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# 仅当存在旧 bridge 容器/旧 products 库时使用。
|
||||
# 没有旧 bridge 的测试/生产环境请用:
|
||||
# bash scripts/import-kb-snapshot.sh # 初始快照
|
||||
# node scripts/sync-kb-online.js # 在线更新
|
||||
# 重建旧 KB 的 parent-child 索引(调用旧 bridge 容器里的 indexer),再回填 text_tsv。
|
||||
# 旧表会由 indexer 归档为 kb_docs_vN;额外先做一份 kb_docs_backup_<date> 备份。
|
||||
set -euo pipefail
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# 测试服 JSON-RPC 端口外网被封:在本机建 SSH 隧道,供 crm-new 的 bridge 访问。
|
||||
# 仅本地开发:测试服 JSON-RPC 端口外网被封时,在本机建 SSH 隧道。
|
||||
# 测试/生产不需要此隧道:把 RPC URL 直接配成内网可达地址。
|
||||
#
|
||||
# 默认只启动隧道并打印 .env 需要的 4 行配置;
|
||||
# 加 --apply 会自动更新 .env 里的 RPC URL 并重建 bridge。
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# 本地静态测试页(widget JWT 演示)
|
||||
# 仅本地预览:静态测试页(widget JWT 演示);测试/生产不需要
|
||||
set -euo pipefail
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/../web"
|
||||
echo "http://localhost:${WEB_PORT:-8088}/demo.html"
|
||||
|
||||
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