// embedding.mts —— L1 向量库的状态 / 回填 / 重建(查看器写盘桥与人工运维共用)
//
// 包装 `memory/search/embedding/backfill.ts` 的三个导出(逻辑一行不复制,与 `task-state.mts` 同款)。
//
// 三条要知道的事:
// · **status 是只读且便宜**(读记录 + 向量库 id 集合,无网络);页面可以随便刷。
// · **backfill / rebuild 是长任务且吃网络**:每批 10 条一次 embedding 请求,耗时随未向量化条数线性增长。
// 桥是单线程 + `execFileSync` 同步,所以这两个动作期间**整座桥都不可用** —— 界面必须显示
// "运行中、别重复点",而不是若无其事地转圈。
// · **rebuild 会先清空向量库**(`clearAll` 后再全量重算):中途失败 = 库空。
// 所以它要求 `--apply` **和** `--confirm REBUILD` 两道,CLI 与桥各验一次。
//
// 用法:
// node --experimental-strip-types scripts/l1-admin/embedding.mts status [--profile
]
// node --experimental-strip-types scripts/l1-admin/embedding.mts backfill [--profile ] [--apply]
// node --experimental-strip-types scripts/l1-admin/embedding.mts rebuild [--profile ] [--apply] [--confirm REBUILD]
//
// 进度写 stderr(`# ` 前缀,无花括号):stdout 只留最后那一个 JSON —— 桥按"从第一个 `{` 起解析"
// 兜日志噪声,中途多打一个对象会让它解析到错的东西。
import { getVectorizationStatus, backfillMissingVectors, rebuildAllVectors } from "../../memory/search/embedding/backfill.ts";
import { APPLY, fail, finish, flagValue, positionals, profileArg, progress, run, usageError } from "./viewer-cli.mts";
const USAGE =
"node --experimental-strip-types scripts/l1-admin/embedding.mts status|backfill|rebuild " +
"[--profile ] [--apply] [--confirm REBUILD]";
/** 统一的收尾:把耗时一起报出去(长任务的时间也是使用者需要的信息)。 */
function report(op: string, extra: Record, startedAt: number): void {
finish({ success: true, op, elapsedMs: Date.now() - startedAt, ...extra });
}
run(async () => {
const profilePath = profileArg();
const op = positionals(["--profile", "--confirm"])[0] ?? "status";
if (!profilePath) usageError(USAGE);
const startedAt = Date.now();
if (op === "status") {
const st = await getVectorizationStatus(profilePath);
report(op, { status: st }, startedAt);
}
if (op !== "backfill" && op !== "rebuild") usageError(USAGE);
if (op === "rebuild" && flagValue("--confirm") !== "REBUILD") {
fail('rebuild 会先清空向量库再全量重算,需要显式确认:--confirm REBUILD(并与 --apply 同时给出)');
}
if (!APPLY) {
// 干跑也要有信息量:先报出"现在缺多少条",否则"加 --apply 才写盘"等于什么都没说。
const st = await getVectorizationStatus(profilePath);
finish({
success: true,
op,
dryRun: true,
status: st,
note:
op === "rebuild"
? "加 --apply --confirm REBUILD 才执行(会清空向量库后全量重算,耗时与条数成正比)"
: "加 --apply 才执行(只补缺失向量,不动已有向量)",
});
}
// 进度回调:只打无花括号的行,桥的 JSON 解析不会被它带偏。
const onProgress = (p: unknown): void => {
try {
progress(`${op} … ${JSON.stringify(p).replace(/[{}"]/g, "").slice(0, 160)}`);
} catch {
progress(`${op} …`);
}
};
if (op === "backfill") {
const res = await backfillMissingVectors(profilePath, onProgress);
report(op, { result: res }, startedAt);
} else {
const res = await rebuildAllVectors(profilePath, onProgress);
report(op, { result: res }, startedAt);
}
});