scream-code 0.8.4 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -5,7 +5,7 @@ const __filename = __cjsShimFileURLToPath(import.meta.url);
|
|
|
5
5
|
const __dirname = __cjsShimDirname(__filename);
|
|
6
6
|
import { a as __toESM, i as __require, r as __exportAll, t as __commonJSMin } from "./chunk-apG1qJts.mjs";
|
|
7
7
|
import "./suppress-sqlite-warning-C2VB0doZ.mjs";
|
|
8
|
-
import { C as join$1, D as resolve$1, E as relative$1, S as isAbsolute$1, T as parse$7, a as isSupportedFile, b as basename$1, i as ingestFile, r as ingestDirectory, t as multiSearch, w as normalize, x as dirname$2, y as KnowledgeStore } from "./src-
|
|
8
|
+
import { C as join$1, D as resolve$1, E as relative$1, S as isAbsolute$1, T as parse$7, a as isSupportedFile, b as basename$1, i as ingestFile, r as ingestDirectory, t as multiSearch, w as normalize, x as dirname$2, y as KnowledgeStore } from "./src-BHYcdFfg.mjs";
|
|
9
9
|
import { createRequire } from "node:module";
|
|
10
10
|
import { createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
|
|
11
11
|
import Jn, { access, appendFile, chmod, copyFile, cp, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, rmdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
@@ -56624,10 +56624,31 @@ function buildEmbeddingText(memo) {
|
|
|
56624
56624
|
return `${memo.userNeed} ${memo.approach} ${memo.whatWorked}`;
|
|
56625
56625
|
}
|
|
56626
56626
|
/**
|
|
56627
|
+
* Cache of created engines keyed by cacheDir.
|
|
56628
|
+
* Guarantees that all callers sharing the same cacheDir reuse the same engine
|
|
56629
|
+
* instance and the same in-flight model download/load — avoiding duplicate
|
|
56630
|
+
* downloads and file-corruption races when /memory and /knowledge both start
|
|
56631
|
+
* before the model is cached.
|
|
56632
|
+
*/
|
|
56633
|
+
const engineCache = /* @__PURE__ */ new Map();
|
|
56634
|
+
/**
|
|
56627
56635
|
* Create an embedding engine backed by fastembed.
|
|
56628
56636
|
* Lazily loads the model on first use so startup is not blocked.
|
|
56629
|
-
|
|
56630
|
-
|
|
56637
|
+
* Engines are cached by cacheDir so repeated calls with the same cacheDir
|
|
56638
|
+
* return the same instance, sharing model state and download progress.
|
|
56639
|
+
* @param cacheDir Absolute path for model cache (e.g. ~/.scream-code/cache/fastembed).
|
|
56640
|
+
* Defaults to "local_cache" (CWD-relative) if not provided — prefer
|
|
56641
|
+
* passing an explicit path so the cache doesn't duplicate across CWDs.
|
|
56642
|
+
*/
|
|
56643
|
+
function createFastEmbedEngine(cacheDir) {
|
|
56644
|
+
const key = cacheDir ?? "";
|
|
56645
|
+
const cached = engineCache.get(key);
|
|
56646
|
+
if (cached !== void 0) return cached;
|
|
56647
|
+
const engine = createFastEmbedEngineImpl(cacheDir);
|
|
56648
|
+
engineCache.set(key, engine);
|
|
56649
|
+
return engine;
|
|
56650
|
+
}
|
|
56651
|
+
function createFastEmbedEngineImpl(cacheDir) {
|
|
56631
56652
|
let embedder = null;
|
|
56632
56653
|
let initPromise = null;
|
|
56633
56654
|
let loadFailed = false;
|
|
@@ -56640,7 +56661,7 @@ function createFastEmbedEngine() {
|
|
|
56640
56661
|
if (texts.length === 0) return [];
|
|
56641
56662
|
try {
|
|
56642
56663
|
if (embedder === null) {
|
|
56643
|
-
initPromise ??= loadEmbedder();
|
|
56664
|
+
initPromise ??= loadEmbedder(cacheDir);
|
|
56644
56665
|
embedder = await initPromise;
|
|
56645
56666
|
if (embedder === null) {
|
|
56646
56667
|
loadFailed = true;
|
|
@@ -56670,9 +56691,12 @@ function createFastEmbedEngine() {
|
|
|
56670
56691
|
return denom === 0 ? 0 : dot / denom;
|
|
56671
56692
|
},
|
|
56672
56693
|
async ensureReady() {
|
|
56673
|
-
if (embedder !== null)
|
|
56694
|
+
if (embedder !== null) {
|
|
56695
|
+
loadFailed = false;
|
|
56696
|
+
return true;
|
|
56697
|
+
}
|
|
56674
56698
|
try {
|
|
56675
|
-
initPromise
|
|
56699
|
+
if (loadFailed || initPromise === null) initPromise = loadEmbedder(cacheDir);
|
|
56676
56700
|
embedder = await initPromise;
|
|
56677
56701
|
if (embedder === null) {
|
|
56678
56702
|
initPromise = null;
|
|
@@ -56687,19 +56711,71 @@ function createFastEmbedEngine() {
|
|
|
56687
56711
|
}
|
|
56688
56712
|
};
|
|
56689
56713
|
}
|
|
56690
|
-
async function loadEmbedder() {
|
|
56714
|
+
async function loadEmbedder(cacheDir) {
|
|
56691
56715
|
try {
|
|
56692
56716
|
const { FlagEmbedding, EmbeddingModel } = await import("./esm-Du2MwXei.mjs");
|
|
56693
|
-
|
|
56717
|
+
if (cacheDir !== void 0) mkdirSync(cacheDir, { recursive: true });
|
|
56718
|
+
const model = EmbeddingModel.BGESmallZH;
|
|
56719
|
+
const initOpts = cacheDir !== void 0 ? {
|
|
56720
|
+
model,
|
|
56721
|
+
cacheDir
|
|
56722
|
+
} : { model };
|
|
56723
|
+
try {
|
|
56724
|
+
return await FlagEmbedding.init(initOpts);
|
|
56725
|
+
} catch (initError) {
|
|
56726
|
+
const msg = initError instanceof Error ? initError.message : "";
|
|
56727
|
+
if (!/Config file not found|Tokenizer file not found|Tokens map file not found/iu.test(msg)) return null;
|
|
56728
|
+
try {
|
|
56729
|
+
await ensureFastembedModelSidecars(String(model), cacheDir);
|
|
56730
|
+
return await FlagEmbedding.init(initOpts);
|
|
56731
|
+
} catch {
|
|
56732
|
+
return null;
|
|
56733
|
+
}
|
|
56734
|
+
}
|
|
56694
56735
|
} catch {
|
|
56695
56736
|
return null;
|
|
56696
56737
|
}
|
|
56697
56738
|
}
|
|
56739
|
+
/**
|
|
56740
|
+
* Small config/tokenizer files that fastembed expects alongside model.onnx.
|
|
56741
|
+
* If these are missing (e.g. GCS download partially failed), fastembed throws.
|
|
56742
|
+
* We download them from HuggingFace so the model can load — this covers the
|
|
56743
|
+
* case where the GCS tarball was incomplete but the HF repo is reachable.
|
|
56744
|
+
*/
|
|
56745
|
+
const FASTEMBED_SIDECARS = [
|
|
56746
|
+
"config.json",
|
|
56747
|
+
"tokenizer.json",
|
|
56748
|
+
"tokenizer_config.json",
|
|
56749
|
+
"special_tokens_map.json"
|
|
56750
|
+
];
|
|
56751
|
+
const FASTEMBED_HF_REPOS = { "fast-bge-small-zh-v1.5": "BAAI/bge-small-zh-v1.5" };
|
|
56752
|
+
async function ensureFastembedModelSidecars(model, cacheDir) {
|
|
56753
|
+
const repo = FASTEMBED_HF_REPOS[model];
|
|
56754
|
+
if (repo === void 0) return;
|
|
56755
|
+
const modelDir = join(cacheDir ?? "local_cache", model);
|
|
56756
|
+
mkdirSync(modelDir, { recursive: true });
|
|
56757
|
+
for (const fileName of FASTEMBED_SIDECARS) {
|
|
56758
|
+
const target = join(modelDir, fileName);
|
|
56759
|
+
try {
|
|
56760
|
+
const { access } = await import("node:fs/promises");
|
|
56761
|
+
await access(target);
|
|
56762
|
+
continue;
|
|
56763
|
+
} catch {}
|
|
56764
|
+
const hfUrl = `https://huggingface.co/${repo}/resolve/main/${fileName}`;
|
|
56765
|
+
try {
|
|
56766
|
+
const response = await fetch(hfUrl);
|
|
56767
|
+
if (!response.ok) continue;
|
|
56768
|
+
const { writeFile } = await import("node:fs/promises");
|
|
56769
|
+
await writeFile(target, Buffer.from(await response.arrayBuffer()));
|
|
56770
|
+
} catch {}
|
|
56771
|
+
}
|
|
56772
|
+
}
|
|
56698
56773
|
//#endregion
|
|
56699
56774
|
//#region ../../packages/memory/src/store.ts
|
|
56700
56775
|
const FILE_NAME = "entries.jsonl";
|
|
56701
56776
|
const MIGRATION_MARKER = ".migrated";
|
|
56702
56777
|
const SQLITE_MIGRATION_MARKER = ".migrated-to-sqlite";
|
|
56778
|
+
const EMBEDDING_LOAD_RETRY_MS = 300 * 1e3;
|
|
56703
56779
|
var MemoryMemoStore = class MemoryMemoStore {
|
|
56704
56780
|
projectDir;
|
|
56705
56781
|
jsonlPath;
|
|
@@ -56714,6 +56790,7 @@ var MemoryMemoStore = class MemoryMemoStore {
|
|
|
56714
56790
|
embeddingDegraded = false;
|
|
56715
56791
|
embeddingConsecutiveFailures = 0;
|
|
56716
56792
|
lastEmbeddingError;
|
|
56793
|
+
embeddingLoadTimer;
|
|
56717
56794
|
log;
|
|
56718
56795
|
constructor(projectDir, log) {
|
|
56719
56796
|
this.projectDir = projectDir;
|
|
@@ -56751,6 +56828,10 @@ var MemoryMemoStore = class MemoryMemoStore {
|
|
|
56751
56828
|
}
|
|
56752
56829
|
/** Close the database handle and checkpoint WAL. */
|
|
56753
56830
|
close() {
|
|
56831
|
+
if (this.embeddingLoadTimer !== void 0) {
|
|
56832
|
+
clearInterval(this.embeddingLoadTimer);
|
|
56833
|
+
this.embeddingLoadTimer = void 0;
|
|
56834
|
+
}
|
|
56754
56835
|
if (this.db !== void 0) {
|
|
56755
56836
|
this.db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
56756
56837
|
this.db.close();
|
|
@@ -57128,6 +57209,34 @@ var MemoryMemoStore = class MemoryMemoStore {
|
|
|
57128
57209
|
/** Set the embedding engine. Call once after construction, before any writes. */
|
|
57129
57210
|
setEmbeddingEngine(engine) {
|
|
57130
57211
|
this.embeddingEngine = engine;
|
|
57212
|
+
this.startEmbeddingLoadLoop();
|
|
57213
|
+
}
|
|
57214
|
+
/**
|
|
57215
|
+
* Background loop that retries model loading every 5 minutes until success.
|
|
57216
|
+
* Covers the case where the model download fails on first use (network issue,
|
|
57217
|
+
* GCS blocked, etc.) — without this, the engine stays loadFailed forever and
|
|
57218
|
+
* all future memos fall back to keyword search silently.
|
|
57219
|
+
* Stops itself once the model loads successfully.
|
|
57220
|
+
*/
|
|
57221
|
+
startEmbeddingLoadLoop() {
|
|
57222
|
+
if (this.embeddingEngine === void 0) return;
|
|
57223
|
+
if (this.embeddingLoadTimer !== void 0) return;
|
|
57224
|
+
const attempt = async () => {
|
|
57225
|
+
if (this.embeddingEngine === void 0) return;
|
|
57226
|
+
if (await this.embeddingEngine.ensureReady()) {
|
|
57227
|
+
if (this.embeddingLoadTimer !== void 0) {
|
|
57228
|
+
clearInterval(this.embeddingLoadTimer);
|
|
57229
|
+
this.embeddingLoadTimer = void 0;
|
|
57230
|
+
}
|
|
57231
|
+
this.embeddingDegraded = false;
|
|
57232
|
+
return;
|
|
57233
|
+
}
|
|
57234
|
+
};
|
|
57235
|
+
this.embeddingLoadTimer = setInterval(() => {
|
|
57236
|
+
attempt();
|
|
57237
|
+
}, EMBEDDING_LOAD_RETRY_MS);
|
|
57238
|
+
this.embeddingLoadTimer.unref?.();
|
|
57239
|
+
attempt();
|
|
57131
57240
|
}
|
|
57132
57241
|
/** Check whether the store has any vector embeddings. */
|
|
57133
57242
|
hasEmbeddings() {
|
|
@@ -58232,7 +58341,7 @@ var KnowledgeLookupTool = class {
|
|
|
58232
58341
|
const llm = { generate: async (systemPrompt, userPrompt) => {
|
|
58233
58342
|
return this.agent.generateText(systemPrompt, userPrompt);
|
|
58234
58343
|
} };
|
|
58235
|
-
const { multiSearchWithTrace } = await import("./src-
|
|
58344
|
+
const { multiSearchWithTrace } = await import("./src-CC8SSs-H.mjs");
|
|
58236
58345
|
const { results, trace } = await multiSearchWithTrace(store, llm, query, { topK });
|
|
58237
58346
|
if (results.length === 0) return {
|
|
58238
58347
|
isError: false,
|
|
@@ -98593,6 +98702,7 @@ var Agent = class {
|
|
|
98593
98702
|
dreamTracker;
|
|
98594
98703
|
replayBuilder;
|
|
98595
98704
|
lastLlmConfigLogSignature;
|
|
98705
|
+
sharedEmbeddingEngine;
|
|
98596
98706
|
constructor(options) {
|
|
98597
98707
|
this.type = options.type ?? "main";
|
|
98598
98708
|
this.jian = options.jian;
|
|
@@ -98607,6 +98717,8 @@ var Agent = class {
|
|
|
98607
98717
|
this.subagentHost = options.subagentHost;
|
|
98608
98718
|
this.mcp = options.mcp;
|
|
98609
98719
|
this.hooks = options.hookEngine;
|
|
98720
|
+
const embedCacheDir = options.screamHomeDir !== void 0 ? join$1(options.screamHomeDir, "cache", "fastembed") : void 0;
|
|
98721
|
+
this.sharedEmbeddingEngine = createFastEmbedEngine(embedCacheDir);
|
|
98610
98722
|
this.log = options.log ?? log;
|
|
98611
98723
|
this.blobStore = options.homedir ? new BlobStore({ blobsDir: join$1(options.homedir, "blobs") }) : void 0;
|
|
98612
98724
|
this.records = new AgentRecords(this, options.persistence ?? (options.homedir ? new FileSystemAgentRecordPersistence(join$1(options.homedir, "wire.jsonl"), {
|
|
@@ -98665,7 +98777,7 @@ var Agent = class {
|
|
|
98665
98777
|
this.log.error("memory legacy migration failed", error);
|
|
98666
98778
|
}
|
|
98667
98779
|
try {
|
|
98668
|
-
this.memoStore.setEmbeddingEngine(
|
|
98780
|
+
this.memoStore.setEmbeddingEngine(this.sharedEmbeddingEngine);
|
|
98669
98781
|
} catch (error) {
|
|
98670
98782
|
this.log.warn("embedding engine init failed; falling back to keyword search", error);
|
|
98671
98783
|
}
|
|
@@ -98680,7 +98792,7 @@ var Agent = class {
|
|
|
98680
98792
|
this.log.error("knowledge store init failed", error);
|
|
98681
98793
|
}
|
|
98682
98794
|
try {
|
|
98683
|
-
store.setEmbeddingEngine(
|
|
98795
|
+
store.setEmbeddingEngine(this.sharedEmbeddingEngine);
|
|
98684
98796
|
} catch (error) {
|
|
98685
98797
|
this.log.warn("knowledge embedding engine init failed", error);
|
|
98686
98798
|
}
|
|
@@ -122269,7 +122381,7 @@ function optionalBuildString(value) {
|
|
|
122269
122381
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
122270
122382
|
}
|
|
122271
122383
|
const SCREAM_BUILD_INFO = {
|
|
122272
|
-
version: optionalBuildString("0.8.
|
|
122384
|
+
version: optionalBuildString("0.8.5"),
|
|
122273
122385
|
channel: optionalBuildString(""),
|
|
122274
122386
|
commit: optionalBuildString(""),
|
|
122275
122387
|
buildTarget: optionalBuildString("darwin-arm64")
|
|
@@ -133109,11 +133221,16 @@ function openUrl(url) {
|
|
|
133109
133221
|
//#endregion
|
|
133110
133222
|
//#region src/tui/commands/knowledge-store.ts
|
|
133111
133223
|
let knowledgeStoreInstance;
|
|
133224
|
+
function getEmbeddingCacheDir() {
|
|
133225
|
+
const dir = join(getDataDir(), "cache", "fastembed");
|
|
133226
|
+
mkdirSync(dir, { recursive: true });
|
|
133227
|
+
return dir;
|
|
133228
|
+
}
|
|
133112
133229
|
async function getKnowledgeStore() {
|
|
133113
133230
|
if (knowledgeStoreInstance === void 0) {
|
|
133114
133231
|
knowledgeStoreInstance = new KnowledgeStore(getDataDir());
|
|
133115
133232
|
await knowledgeStoreInstance.init();
|
|
133116
|
-
knowledgeStoreInstance.setEmbeddingEngine(createFastEmbedEngine());
|
|
133233
|
+
knowledgeStoreInstance.setEmbeddingEngine(createFastEmbedEngine(getEmbeddingCacheDir()));
|
|
133117
133234
|
}
|
|
133118
133235
|
return knowledgeStoreInstance;
|
|
133119
133236
|
}
|
|
@@ -133583,7 +133700,7 @@ function serveHTML(res) {
|
|
|
133583
133700
|
});
|
|
133584
133701
|
res.end(HTML);
|
|
133585
133702
|
}
|
|
133586
|
-
async function handleWeb() {
|
|
133703
|
+
async function handleWeb(host) {
|
|
133587
133704
|
const store = await getKnowledgeStore();
|
|
133588
133705
|
const s = await store.stats();
|
|
133589
133706
|
if (s.entities === 0 && s.events === 0) throw new Error("知识库为空,请先用 /knowledge 摄入文档");
|
|
@@ -133604,7 +133721,7 @@ async function handleWeb() {
|
|
|
133604
133721
|
});
|
|
133605
133722
|
const url = `http://127.0.0.1:${server.address().port}`;
|
|
133606
133723
|
openUrl(url);
|
|
133607
|
-
|
|
133724
|
+
host.showStatus(`知识图谱已打开: ${url}`);
|
|
133608
133725
|
}
|
|
133609
133726
|
//#endregion
|
|
133610
133727
|
//#region src/tui/components/dialogs/knowledge-result-viewer.ts
|
|
@@ -134000,6 +134117,7 @@ function makeLlmCaller(host) {
|
|
|
134000
134117
|
}
|
|
134001
134118
|
function formatProgress(progress) {
|
|
134002
134119
|
switch (progress.stage) {
|
|
134120
|
+
case "embedding-check": return progress.message;
|
|
134003
134121
|
case "chunking": return `切分文件中...`;
|
|
134004
134122
|
case "embedding-chunks": return `嵌入 chunks: ${progress.chunkIndex}/${progress.totalChunks}`;
|
|
134005
134123
|
case "extracting": return `抽取事件: ${progress.chunkIndex}/${progress.totalChunks}`;
|
|
@@ -134030,19 +134148,6 @@ async function handleIngest(host) {
|
|
|
134030
134148
|
}
|
|
134031
134149
|
const store = await getKnowledgeStore();
|
|
134032
134150
|
const llm = makeLlmCaller(host);
|
|
134033
|
-
const checkSpinner = host.showProgressSpinner("检查向量模型...");
|
|
134034
|
-
if (!(await store.ensureEmbeddingReady()).ok) {
|
|
134035
|
-
checkSpinner.stop({
|
|
134036
|
-
ok: false,
|
|
134037
|
-
label: "向量模型下载失败"
|
|
134038
|
-
});
|
|
134039
|
-
host.showNotice("向量模型下载失败", "知识库需要中文向量模型 bge-small-zh-v1.5(约 95MB)才能进行语义检索。\n\n可能原因:\n• 网络不通,无法下载模型文件\n• onnxruntime native binding 加载失败\n\n请检查网络后重试。");
|
|
134040
|
-
return;
|
|
134041
|
-
}
|
|
134042
|
-
checkSpinner.stop({
|
|
134043
|
-
ok: true,
|
|
134044
|
-
label: "向量模型就绪"
|
|
134045
|
-
});
|
|
134046
134151
|
const spinner = host.showProgressSpinner("开始摄入...");
|
|
134047
134152
|
try {
|
|
134048
134153
|
if (stats.isDirectory()) {
|
|
@@ -134145,11 +134250,17 @@ async function handleSearch(host) {
|
|
|
134145
134250
|
ok: true,
|
|
134146
134251
|
label: "搜索完成"
|
|
134147
134252
|
});
|
|
134253
|
+
const engine = store.getEmbeddingEngine();
|
|
134254
|
+
const degraded = engine === void 0 || !engine.available;
|
|
134148
134255
|
if (results.length === 0) {
|
|
134149
|
-
await showResultViewer(host, "搜索结果", `查询 "${query}" 未命中任何 chunk`);
|
|
134256
|
+
await showResultViewer(host, "搜索结果", `查询 "${query}" 未命中任何 chunk${degraded ? "\n\n⚠️ 向量模型未就绪,当前为关键词检索模式。如下载失败,建议开启科学上网后重启。" : ""}`);
|
|
134150
134257
|
return;
|
|
134151
134258
|
}
|
|
134152
134259
|
const lines = [`查询: ${query}`, ""];
|
|
134260
|
+
if (degraded) {
|
|
134261
|
+
lines.push("⚠️ 向量模型未就绪,当前为关键词检索模式。如下载失败,建议开启科学上网后重启。");
|
|
134262
|
+
lines.push("");
|
|
134263
|
+
}
|
|
134153
134264
|
for (const [i, r] of results.entries()) {
|
|
134154
134265
|
lines.push(`#${i + 1} [score=${r.score.toFixed(3)}] ${r.heading ?? "(无标题)"}`);
|
|
134155
134266
|
lines.push(` 来源: ${r.sourceName}`);
|
|
@@ -134300,7 +134411,7 @@ async function handleKnowledgeCommand(host, _args) {
|
|
|
134300
134411
|
else if (value === "search") await handleSearch(host);
|
|
134301
134412
|
else if (value === "delete") await handleDelete(host);
|
|
134302
134413
|
else if (value === "stats") await handleStats(host);
|
|
134303
|
-
else if (value === "web") await handleWeb();
|
|
134414
|
+
else if (value === "web") await handleWeb(host);
|
|
134304
134415
|
} catch (error) {
|
|
134305
134416
|
const msg = error instanceof Error ? error.message : String(error);
|
|
134306
134417
|
host.showError(`操作失败: ${msg}`);
|
package/dist/main.mjs
CHANGED
|
@@ -6,7 +6,7 @@ const __dirname = __cjsShimDirname(__filename);
|
|
|
6
6
|
import "./suppress-sqlite-warning-C2VB0doZ.mjs";
|
|
7
7
|
//#region src/main.ts
|
|
8
8
|
try {
|
|
9
|
-
(await import("./app-
|
|
9
|
+
(await import("./app-DFMT0YIn.mjs")).main();
|
|
10
10
|
} catch (error) {
|
|
11
11
|
process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
|
|
12
12
|
process.exit(1);
|
|
@@ -1392,6 +1392,19 @@ Only include entities that explicitly appear in the query. If no clear entities,
|
|
|
1392
1392
|
}
|
|
1393
1393
|
//#endregion
|
|
1394
1394
|
//#region ../../packages/knowledge/src/ingest.ts
|
|
1395
|
+
/**
|
|
1396
|
+
* Embed texts, returning null-array if embeddings are skipped or on failure.
|
|
1397
|
+
* Caller should set skipEmbedding=true when the model didn't load in time,
|
|
1398
|
+
* so the entire ingestion is consistent (all or nothing for embeddings).
|
|
1399
|
+
* Search falls back to FTS5 keyword match for chunks without embeddings.
|
|
1400
|
+
*/
|
|
1401
|
+
async function embedOrNulls(engine, texts, skipEmbedding) {
|
|
1402
|
+
if (engine === void 0 || texts.length === 0 || skipEmbedding) return texts.map(() => null);
|
|
1403
|
+
if (!engine.available) return texts.map(() => null);
|
|
1404
|
+
const vectors = await engine.embedBatch(texts);
|
|
1405
|
+
if (vectors === null || vectors.length !== texts.length) return texts.map(() => null);
|
|
1406
|
+
return vectors;
|
|
1407
|
+
}
|
|
1395
1408
|
/** LLM concurrency for extraction — kept low to avoid rate-limit bursts. */
|
|
1396
1409
|
const LLM_CONCURRENCY = 3;
|
|
1397
1410
|
const SUPPORTED_EXTENSIONS = new Set([
|
|
@@ -1454,12 +1467,25 @@ async function mapWithConcurrency(items, limit, fn, onProgress) {
|
|
|
1454
1467
|
*/
|
|
1455
1468
|
async function ingestFile(store, llm, filePath, onProgress) {
|
|
1456
1469
|
const engine = store.getEmbeddingEngine();
|
|
1457
|
-
|
|
1470
|
+
let skipEmbed = engine === void 0;
|
|
1471
|
+
if (engine !== void 0) {
|
|
1458
1472
|
onProgress?.({
|
|
1459
|
-
stage: "
|
|
1460
|
-
message: "
|
|
1473
|
+
stage: "embedding-check",
|
|
1474
|
+
message: "检查向量模型..."
|
|
1461
1475
|
});
|
|
1462
|
-
|
|
1476
|
+
try {
|
|
1477
|
+
skipEmbed = !await Promise.race([engine.ensureReady(), new Promise((resolve) => setTimeout(() => resolve(false), 15e3))]);
|
|
1478
|
+
if (skipEmbed) onProgress?.({
|
|
1479
|
+
stage: "embedding-check",
|
|
1480
|
+
message: "向量模型未就绪,本次摄入跳过向量嵌入"
|
|
1481
|
+
});
|
|
1482
|
+
} catch {
|
|
1483
|
+
skipEmbed = true;
|
|
1484
|
+
onProgress?.({
|
|
1485
|
+
stage: "embedding-check",
|
|
1486
|
+
message: "向量模型未就绪,本次摄入跳过向量嵌入"
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1463
1489
|
}
|
|
1464
1490
|
const existing = await store.findSourceByFilePath(filePath);
|
|
1465
1491
|
if (existing !== void 0) {
|
|
@@ -1501,14 +1527,7 @@ async function ingestFile(store, llm, filePath, onProgress) {
|
|
|
1501
1527
|
totalChunks: sections.length,
|
|
1502
1528
|
message: `嵌入 chunks: 0/${sections.length}`
|
|
1503
1529
|
});
|
|
1504
|
-
const chunkEmbeddings = await engine
|
|
1505
|
-
if (chunkEmbeddings === null) {
|
|
1506
|
-
onProgress?.({
|
|
1507
|
-
stage: "error",
|
|
1508
|
-
message: "chunk 向量嵌入失败"
|
|
1509
|
-
});
|
|
1510
|
-
throw new Error("chunk 向量嵌入失败");
|
|
1511
|
-
}
|
|
1530
|
+
const chunkEmbeddings = await embedOrNulls(engine, sections.map((s) => s.heading !== null ? `${s.heading}\n${s.content}` : s.content), skipEmbed);
|
|
1512
1531
|
const chunks = [];
|
|
1513
1532
|
for (let i = 0; i < sections.length; i++) {
|
|
1514
1533
|
const section = sections[i];
|
|
@@ -1552,14 +1571,7 @@ async function ingestFile(store, llm, filePath, onProgress) {
|
|
|
1552
1571
|
});
|
|
1553
1572
|
const titleTexts = extractedEvents.map((e) => e.title);
|
|
1554
1573
|
const contentTexts = extractedEvents.map((e) => `${e.title}\n\n${e.content}`);
|
|
1555
|
-
const [titleEmbeddings, contentEmbeddings] = await Promise.all([engine
|
|
1556
|
-
if (titleEmbeddings === null || contentEmbeddings === null) {
|
|
1557
|
-
onProgress?.({
|
|
1558
|
-
stage: "error",
|
|
1559
|
-
message: "event 向量嵌入失败"
|
|
1560
|
-
});
|
|
1561
|
-
throw new Error("event 向量嵌入失败");
|
|
1562
|
-
}
|
|
1574
|
+
const [titleEmbeddings, contentEmbeddings] = await Promise.all([embedOrNulls(engine, titleTexts, skipEmbed), embedOrNulls(engine, contentTexts, skipEmbed)]);
|
|
1563
1575
|
const events = [];
|
|
1564
1576
|
for (let i = 0; i < extractedEvents.length; i++) {
|
|
1565
1577
|
const extracted = extractedEvents[i];
|
|
@@ -1595,18 +1607,11 @@ async function ingestFile(store, llm, filePath, onProgress) {
|
|
|
1595
1607
|
if (!entityMap.has(key)) entityMap.set(key, entity);
|
|
1596
1608
|
}
|
|
1597
1609
|
const uniqueEntities = Array.from(entityMap.values());
|
|
1598
|
-
const entityEmbeddings = uniqueEntities.length > 0 ? await engine
|
|
1599
|
-
if (uniqueEntities.length > 0 && entityEmbeddings === null) {
|
|
1600
|
-
onProgress?.({
|
|
1601
|
-
stage: "error",
|
|
1602
|
-
message: "entity 向量嵌入失败"
|
|
1603
|
-
});
|
|
1604
|
-
throw new Error("entity 向量嵌入失败");
|
|
1605
|
-
}
|
|
1610
|
+
const entityEmbeddings = uniqueEntities.length > 0 ? await embedOrNulls(engine, uniqueEntities.map((e) => e.name), skipEmbed) : [];
|
|
1606
1611
|
const entityIdByEntityKey = /* @__PURE__ */ new Map();
|
|
1607
1612
|
for (let i = 0; i < uniqueEntities.length; i++) {
|
|
1608
1613
|
const entity = uniqueEntities[i];
|
|
1609
|
-
const embedding = entityEmbeddings
|
|
1614
|
+
const embedding = entityEmbeddings[i] ?? null;
|
|
1610
1615
|
const stored = await store.upsertEntity({
|
|
1611
1616
|
sourceId: source.id,
|
|
1612
1617
|
type: entity.type,
|
|
@@ -1632,14 +1637,7 @@ async function ingestFile(store, llm, filePath, onProgress) {
|
|
|
1632
1637
|
const event = events[eventIndex];
|
|
1633
1638
|
return entity.description.length > 0 ? entity.description : `${event.title} ${entity.name}`;
|
|
1634
1639
|
});
|
|
1635
|
-
const relationEmbeddings = relationPairs.length > 0 ? await engine
|
|
1636
|
-
if (relationPairs.length > 0 && relationEmbeddings === null) {
|
|
1637
|
-
onProgress?.({
|
|
1638
|
-
stage: "error",
|
|
1639
|
-
message: "relation 向量嵌入失败"
|
|
1640
|
-
});
|
|
1641
|
-
throw new Error("relation 向量嵌入失败");
|
|
1642
|
-
}
|
|
1640
|
+
const relationEmbeddings = relationPairs.length > 0 ? await embedOrNulls(engine, relationTexts, skipEmbed) : [];
|
|
1643
1641
|
for (let i = 0; i < relationPairs.length; i++) {
|
|
1644
1642
|
const pair = relationPairs[i];
|
|
1645
1643
|
const event = events[pair.eventIndex];
|
|
@@ -3,5 +3,5 @@ import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
|
|
|
3
3
|
import { dirname as __cjsShimDirname } from 'node:path';
|
|
4
4
|
const __filename = __cjsShimFileURLToPath(import.meta.url);
|
|
5
5
|
const __dirname = __cjsShimDirname(__filename);
|
|
6
|
-
import { n as multiSearchWithTrace } from "./src-
|
|
6
|
+
import { n as multiSearchWithTrace } from "./src-BHYcdFfg.mjs";
|
|
7
7
|
export { multiSearchWithTrace };
|