@s-hirano-ist/s-scripts 4.1.0 → 4.3.0
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.
- package/README.md +8 -0
- package/dist/{chunk-7XX57D7G.js → chunk-3INTMHPH.js} +1 -1
- package/dist/chunk-4CXH2QIX.js +445 -0
- package/dist/chunk-67LIMNXH.js +36 -0
- package/dist/{chunk-OCEV2QX3.js → chunk-BYEEIQ7O.js} +1 -1
- package/dist/{chunk-2PZXXOYG.js → chunk-GBQOCN2C.js} +24 -25
- package/dist/chunk-MJYGMGH5.js +382 -0
- package/dist/chunk-MVYAZJB2.js +160 -0
- package/dist/{chunk-DIG75T3J.js → chunk-RLAYINCA.js} +10 -13
- package/dist/{chunk-MNBEFEFR.js → chunk-RO5QUZFR.js} +1 -1
- package/dist/chunk-SFKQ2RAP.js +87 -0
- package/dist/{chunk-FVK44UTM.js → chunk-ZMQUO6OA.js} +1 -1
- package/dist/cleanup-minio-images.js +62 -91
- package/dist/enrich-books.js +6 -4
- package/dist/fetch-articles.js +6 -24
- package/dist/fetch-books.js +12 -35
- package/dist/fetch-images.js +12 -35
- package/dist/fetch-notes.js +6 -24
- package/dist/find-duplicate-json-articles.js +11 -13
- package/dist/fix-book-frontmatter.js +6 -4
- package/dist/fix-image-formats.js +7 -5
- package/dist/ingest-articles.js +7 -5
- package/dist/ingest-books.js +10 -8
- package/dist/ingest-images.js +7 -5
- package/dist/ingest-notes.js +10 -8
- package/dist/rag/delete-collection.js +5 -30
- package/dist/rag/ingest.js +5 -379
- package/dist/rag/search.js +5 -84
- package/dist/reset-articles.js +8 -23
- package/dist/reset-books.js +8 -23
- package/dist/reset-images.js +8 -23
- package/dist/reset-notes.js +8 -23
- package/dist/revert-articles.js +8 -23
- package/dist/revert-books.js +8 -23
- package/dist/revert-images.js +8 -23
- package/dist/revert-notes.js +8 -23
- package/dist/s-content-format.js +9 -11
- package/dist/update-json-articles.js +19 -41
- package/dist/update-raw-articles.js +14 -37
- package/dist/validate-json-articles.js +8 -5
- package/package.json +13 -11
- package/dist/chunk-7PVU2Z32.js +0 -71
- package/dist/chunk-F4J6JBR4.js +0 -115
|
@@ -6,6 +6,10 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
globPaths
|
|
8
8
|
} from "./chunk-AJMTRCPJ.js";
|
|
9
|
+
import {
|
|
10
|
+
reportCliError,
|
|
11
|
+
runCli
|
|
12
|
+
} from "./chunk-4CXH2QIX.js";
|
|
9
13
|
import "./chunk-JZOR5GA2.js";
|
|
10
14
|
|
|
11
15
|
// src/fix-book-frontmatter.ts
|
|
@@ -94,6 +98,7 @@ ${body}`;
|
|
|
94
98
|
}
|
|
95
99
|
return "fixed";
|
|
96
100
|
} catch (error) {
|
|
101
|
+
reportCliError(error, "fix-book-frontmatter");
|
|
97
102
|
console.error(`\u274C ${fileName}: ${String(error)}`);
|
|
98
103
|
return "error";
|
|
99
104
|
}
|
|
@@ -127,7 +132,4 @@ async function main() {
|
|
|
127
132
|
\u{1F4CA} \u7D50\u679C: \u4FEE\u6B63 ${fixedCount} \u4EF6, \u30B9\u30AD\u30C3\u30D7 ${skippedCount} \u4EF6, \u30A8\u30E9\u30FC ${errorCount} \u4EF6${dryRun ? " (dry-run)" : ""}`
|
|
128
133
|
);
|
|
129
134
|
}
|
|
130
|
-
main
|
|
131
|
-
console.error("\u274C \u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F:", error);
|
|
132
|
-
process.exit(1);
|
|
133
|
-
});
|
|
135
|
+
await runCli("fix-book-frontmatter", main);
|
|
@@ -6,6 +6,10 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
globPaths
|
|
8
8
|
} from "./chunk-AJMTRCPJ.js";
|
|
9
|
+
import {
|
|
10
|
+
reportCliError,
|
|
11
|
+
runCli
|
|
12
|
+
} from "./chunk-4CXH2QIX.js";
|
|
9
13
|
import "./chunk-JZOR5GA2.js";
|
|
10
14
|
|
|
11
15
|
// src/fix-image-formats.ts
|
|
@@ -64,6 +68,7 @@ async function processDirectory(directory, label, dryRun) {
|
|
|
64
68
|
console.log(`\u2705 \u5909\u63DB\u5B8C\u4E86: ${fileName} \u2192 ${basename(outputPath)}`);
|
|
65
69
|
converted++;
|
|
66
70
|
} catch (error) {
|
|
71
|
+
reportCliError(error, "fix-image-formats");
|
|
67
72
|
console.error(`\u274C ${fileName}: ${String(error)}`);
|
|
68
73
|
errors++;
|
|
69
74
|
}
|
|
@@ -87,10 +92,7 @@ async function main() {
|
|
|
87
92
|
\u{1F4CA} \u5408\u8A08: \u5909\u63DB ${converted} \u4EF6, \u30B9\u30AD\u30C3\u30D7 ${skipped} \u4EF6, \u30A8\u30E9\u30FC ${errors} \u4EF6${dryRun ? " (dry-run)" : ""}`
|
|
88
93
|
);
|
|
89
94
|
if (errors > 0) {
|
|
90
|
-
process.
|
|
95
|
+
process.exitCode = 1;
|
|
91
96
|
}
|
|
92
97
|
}
|
|
93
|
-
main
|
|
94
|
-
console.error("\u274C \u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F:", error);
|
|
95
|
-
process.exit(1);
|
|
96
|
-
});
|
|
98
|
+
await runCli("fix-image-formats", main);
|
package/dist/ingest-articles.js
CHANGED
|
@@ -5,14 +5,16 @@ import {
|
|
|
5
5
|
} from "./chunk-IYGLKY27.js";
|
|
6
6
|
import {
|
|
7
7
|
runContentImport
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-GBQOCN2C.js";
|
|
9
9
|
import "./chunk-KKZP7ZF3.js";
|
|
10
|
-
import "./chunk-F4J6JBR4.js";
|
|
11
|
-
import "./chunk-DIG75T3J.js";
|
|
12
|
-
import "./chunk-7PVU2Z32.js";
|
|
13
10
|
import {
|
|
14
11
|
globPaths
|
|
15
12
|
} from "./chunk-AJMTRCPJ.js";
|
|
13
|
+
import "./chunk-MVYAZJB2.js";
|
|
14
|
+
import "./chunk-RLAYINCA.js";
|
|
15
|
+
import {
|
|
16
|
+
runCli
|
|
17
|
+
} from "./chunk-4CXH2QIX.js";
|
|
16
18
|
import "./chunk-JZOR5GA2.js";
|
|
17
19
|
|
|
18
20
|
// src/ingest-articles.ts
|
|
@@ -33,4 +35,4 @@ async function* source(root) {
|
|
|
33
35
|
};
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
|
-
await runContentImport("articles", source);
|
|
38
|
+
await runCli("ingest-articles", () => runContentImport("articles", source));
|
package/dist/ingest-books.js
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
parseMarkdownFrontmatter
|
|
5
|
+
} from "./chunk-HDYQV2Y2.js";
|
|
3
6
|
import {
|
|
4
7
|
makeISBN,
|
|
5
8
|
runContentImport
|
|
6
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-GBQOCN2C.js";
|
|
7
10
|
import "./chunk-KKZP7ZF3.js";
|
|
8
|
-
import "./chunk-F4J6JBR4.js";
|
|
9
|
-
import "./chunk-DIG75T3J.js";
|
|
10
|
-
import "./chunk-7PVU2Z32.js";
|
|
11
|
-
import {
|
|
12
|
-
parseMarkdownFrontmatter
|
|
13
|
-
} from "./chunk-HDYQV2Y2.js";
|
|
14
11
|
import {
|
|
15
12
|
globPaths
|
|
16
13
|
} from "./chunk-AJMTRCPJ.js";
|
|
14
|
+
import "./chunk-MVYAZJB2.js";
|
|
15
|
+
import "./chunk-RLAYINCA.js";
|
|
16
|
+
import {
|
|
17
|
+
runCli
|
|
18
|
+
} from "./chunk-4CXH2QIX.js";
|
|
17
19
|
import "./chunk-JZOR5GA2.js";
|
|
18
20
|
|
|
19
21
|
// src/ingest-books.ts
|
|
@@ -65,4 +67,4 @@ async function* source(root) {
|
|
|
65
67
|
};
|
|
66
68
|
}
|
|
67
69
|
}
|
|
68
|
-
await runContentImport("books", source);
|
|
70
|
+
await runCli("ingest-books", () => runContentImport("books", source));
|
package/dist/ingest-images.js
CHANGED
|
@@ -3,14 +3,16 @@ import { createRequire as __cliCreateRequire } from "node:module"; const require
|
|
|
3
3
|
import {
|
|
4
4
|
runContentImport,
|
|
5
5
|
sharpImageProcessor
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-GBQOCN2C.js";
|
|
7
7
|
import "./chunk-KKZP7ZF3.js";
|
|
8
|
-
import "./chunk-F4J6JBR4.js";
|
|
9
|
-
import "./chunk-DIG75T3J.js";
|
|
10
|
-
import "./chunk-7PVU2Z32.js";
|
|
11
8
|
import {
|
|
12
9
|
globPaths
|
|
13
10
|
} from "./chunk-AJMTRCPJ.js";
|
|
11
|
+
import "./chunk-MVYAZJB2.js";
|
|
12
|
+
import "./chunk-RLAYINCA.js";
|
|
13
|
+
import {
|
|
14
|
+
runCli
|
|
15
|
+
} from "./chunk-4CXH2QIX.js";
|
|
14
16
|
import "./chunk-JZOR5GA2.js";
|
|
15
17
|
|
|
16
18
|
// src/ingest-images.ts
|
|
@@ -34,4 +36,4 @@ async function* source(root) {
|
|
|
34
36
|
};
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
|
-
await runContentImport("images", source);
|
|
39
|
+
await runCli("ingest-images", () => runContentImport("images", source));
|
package/dist/ingest-notes.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
|
|
3
|
-
import {
|
|
4
|
-
runContentImport
|
|
5
|
-
} from "./chunk-2PZXXOYG.js";
|
|
6
|
-
import "./chunk-KKZP7ZF3.js";
|
|
7
|
-
import "./chunk-F4J6JBR4.js";
|
|
8
|
-
import "./chunk-DIG75T3J.js";
|
|
9
|
-
import "./chunk-7PVU2Z32.js";
|
|
10
3
|
import {
|
|
11
4
|
parseMarkdownFrontmatter
|
|
12
5
|
} from "./chunk-HDYQV2Y2.js";
|
|
6
|
+
import {
|
|
7
|
+
runContentImport
|
|
8
|
+
} from "./chunk-GBQOCN2C.js";
|
|
9
|
+
import "./chunk-KKZP7ZF3.js";
|
|
13
10
|
import {
|
|
14
11
|
globPaths
|
|
15
12
|
} from "./chunk-AJMTRCPJ.js";
|
|
13
|
+
import "./chunk-MVYAZJB2.js";
|
|
14
|
+
import "./chunk-RLAYINCA.js";
|
|
15
|
+
import {
|
|
16
|
+
runCli
|
|
17
|
+
} from "./chunk-4CXH2QIX.js";
|
|
16
18
|
import "./chunk-JZOR5GA2.js";
|
|
17
19
|
|
|
18
20
|
// src/ingest-notes.ts
|
|
@@ -46,4 +48,4 @@ async function* source(root) {
|
|
|
46
48
|
if (values) yield { values };
|
|
47
49
|
}
|
|
48
50
|
}
|
|
49
|
-
await runContentImport("notes", source);
|
|
51
|
+
await runCli("ingest-notes", () => runContentImport("notes", source));
|
|
@@ -1,37 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from "../chunk-J4VLH4VA.js";
|
|
4
|
+
runCli
|
|
5
|
+
} from "../chunk-4CXH2QIX.js";
|
|
7
6
|
import "../chunk-JZOR5GA2.js";
|
|
8
7
|
|
|
9
8
|
// src/rag/delete-collection.ts
|
|
10
|
-
async
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
console.error(
|
|
14
|
-
"Usage: rag-delete-collection <collection-name>\nExample: rag-delete-collection knowledge_v1"
|
|
15
|
-
);
|
|
16
|
-
process.exit(1);
|
|
17
|
-
}
|
|
18
|
-
if (!process.env.QDRANT_URL) {
|
|
19
|
-
throw new Error("QDRANT_URL environment variable is required.");
|
|
20
|
-
}
|
|
21
|
-
const qdrant = getQdrantClient();
|
|
22
|
-
const collections = await qdrant.getCollections();
|
|
23
|
-
const exists = collections.collections.some((c) => c.name === collectionName);
|
|
24
|
-
if (!exists) {
|
|
25
|
-
console.log(
|
|
26
|
-
`Collection "${collectionName}" does not exist. Nothing to delete.`
|
|
27
|
-
);
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
console.log(`Deleting collection "${collectionName}"...`);
|
|
31
|
-
await deleteCollection(collectionName);
|
|
32
|
-
console.log(`Collection "${collectionName}" deleted successfully.`);
|
|
33
|
-
}
|
|
34
|
-
main().catch((error) => {
|
|
35
|
-
console.error("Failed to delete collection:", error);
|
|
36
|
-
process.exit(1);
|
|
9
|
+
await runCli("rag-delete-collection", async () => {
|
|
10
|
+
const { main } = await import("../chunk-67LIMNXH.js");
|
|
11
|
+
await main();
|
|
37
12
|
});
|
package/dist/rag/ingest.js
CHANGED
|
@@ -1,386 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire as __cliCreateRequire } from "node:module"; const require = __cliCreateRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
} from "../chunk-
|
|
6
|
-
import {
|
|
7
|
-
globPaths
|
|
8
|
-
} from "../chunk-AJMTRCPJ.js";
|
|
9
|
-
import {
|
|
10
|
-
RAG_CONFIG,
|
|
11
|
-
deletePointsByChunkIds,
|
|
12
|
-
ensureCollection,
|
|
13
|
-
getCollectionStats,
|
|
14
|
-
getExistingHashes,
|
|
15
|
-
listAllChunkIds,
|
|
16
|
-
upsertPoints
|
|
17
|
-
} from "../chunk-J4VLH4VA.js";
|
|
4
|
+
runCli
|
|
5
|
+
} from "../chunk-4CXH2QIX.js";
|
|
18
6
|
import "../chunk-JZOR5GA2.js";
|
|
19
7
|
|
|
20
|
-
// ../search/dist/chunker.js
|
|
21
|
-
import { createHash } from "node:crypto";
|
|
22
|
-
function generateHash(content) {
|
|
23
|
-
return createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
24
|
-
}
|
|
25
|
-
function generateChunkId(docId, index) {
|
|
26
|
-
return `${docId}#${index}`;
|
|
27
|
-
}
|
|
28
|
-
function parseJsonArticle(filePath, content) {
|
|
29
|
-
const json = JSON.parse(content);
|
|
30
|
-
const docId = `file:${filePath}`;
|
|
31
|
-
const chunks = [];
|
|
32
|
-
for (let i = 0; i < json.body.length; i++) {
|
|
33
|
-
const item = json.body[i];
|
|
34
|
-
const textParts = [];
|
|
35
|
-
if (item.title)
|
|
36
|
-
textParts.push(item.title);
|
|
37
|
-
if (item.ogTitle && item.ogTitle !== item.title)
|
|
38
|
-
textParts.push(item.ogTitle);
|
|
39
|
-
if (item.ogDescription)
|
|
40
|
-
textParts.push(item.ogDescription);
|
|
41
|
-
if (item.quote)
|
|
42
|
-
textParts.push(item.quote);
|
|
43
|
-
const text = textParts.join("\n");
|
|
44
|
-
if (!text.trim())
|
|
45
|
-
continue;
|
|
46
|
-
const chunkId = generateChunkId(docId, i);
|
|
47
|
-
chunks.push({
|
|
48
|
-
type: "bookmark_json",
|
|
49
|
-
content_type: "articles",
|
|
50
|
-
top_heading: json.heading,
|
|
51
|
-
doc_id: docId,
|
|
52
|
-
chunk_id: chunkId,
|
|
53
|
-
title: item.title || item.ogTitle || "Untitled",
|
|
54
|
-
url: item.url,
|
|
55
|
-
heading_path: [json.heading],
|
|
56
|
-
text,
|
|
57
|
-
content_hash: generateHash(text)
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
return chunks;
|
|
61
|
-
}
|
|
62
|
-
function parseFrontmatter(content) {
|
|
63
|
-
const parsed = parseMarkdownFrontmatter(content);
|
|
64
|
-
const frontmatter = {
|
|
65
|
-
heading: typeof parsed.data.heading === "string" ? parsed.data.heading : "unknown"
|
|
66
|
-
};
|
|
67
|
-
if (typeof parsed.data.description === "string") {
|
|
68
|
-
frontmatter.description = parsed.data.description;
|
|
69
|
-
}
|
|
70
|
-
if (typeof parsed.data.draft === "boolean") {
|
|
71
|
-
frontmatter.draft = parsed.data.draft;
|
|
72
|
-
}
|
|
73
|
-
return { frontmatter, body: parsed.content };
|
|
74
|
-
}
|
|
75
|
-
function pushPreambleSection(state) {
|
|
76
|
-
if (state.preambleContent.trim()) {
|
|
77
|
-
state.sections.push({
|
|
78
|
-
headingPath: [],
|
|
79
|
-
title: "",
|
|
80
|
-
content: state.preambleContent,
|
|
81
|
-
level: 0
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
function handleHeadingLine(state, headingMatch) {
|
|
86
|
-
if (!state.preambleDone) {
|
|
87
|
-
state.preambleDone = true;
|
|
88
|
-
pushPreambleSection(state);
|
|
89
|
-
}
|
|
90
|
-
if (state.currentSection?.content.trim()) {
|
|
91
|
-
state.sections.push(state.currentSection);
|
|
92
|
-
}
|
|
93
|
-
const level = headingMatch[1].length;
|
|
94
|
-
const title = headingMatch[2];
|
|
95
|
-
const headingStack = state.headingStack;
|
|
96
|
-
let top = headingStack.at(-1);
|
|
97
|
-
while (top && top.level >= level) {
|
|
98
|
-
headingStack.pop();
|
|
99
|
-
top = headingStack.at(-1);
|
|
100
|
-
}
|
|
101
|
-
headingStack.push({ level, title });
|
|
102
|
-
state.currentSection = {
|
|
103
|
-
headingPath: headingStack.map((h) => h.title),
|
|
104
|
-
title,
|
|
105
|
-
content: "",
|
|
106
|
-
level
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
function splitMarkdownByHeadings(content) {
|
|
110
|
-
const state = {
|
|
111
|
-
sections: [],
|
|
112
|
-
currentSection: null,
|
|
113
|
-
headingStack: [],
|
|
114
|
-
// Preamble buffer (content before first H2/H3)
|
|
115
|
-
preambleContent: "",
|
|
116
|
-
preambleDone: false
|
|
117
|
-
};
|
|
118
|
-
for (const line of content.split("\n")) {
|
|
119
|
-
if (/^#\s+\S/u.test(line)) {
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
const headingMatch = /^(#{2,3})\s+(\S.*)$/u.exec(line);
|
|
123
|
-
if (headingMatch) {
|
|
124
|
-
handleHeadingLine(state, headingMatch);
|
|
125
|
-
} else if (!state.preambleDone) {
|
|
126
|
-
state.preambleContent += `${line}
|
|
127
|
-
`;
|
|
128
|
-
} else if (state.currentSection) {
|
|
129
|
-
state.currentSection.content += `${line}
|
|
130
|
-
`;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (!state.preambleDone) {
|
|
134
|
-
pushPreambleSection(state);
|
|
135
|
-
}
|
|
136
|
-
if (state.currentSection?.content.trim()) {
|
|
137
|
-
state.sections.push(state.currentSection);
|
|
138
|
-
}
|
|
139
|
-
return state.sections;
|
|
140
|
-
}
|
|
141
|
-
function splitByParagraphs(text, maxLength) {
|
|
142
|
-
if (text.length <= maxLength) {
|
|
143
|
-
return [text];
|
|
144
|
-
}
|
|
145
|
-
const paragraphs = text.split(/\n{2,}/u);
|
|
146
|
-
const chunks = [];
|
|
147
|
-
let currentChunk = "";
|
|
148
|
-
for (const para of paragraphs) {
|
|
149
|
-
if (currentChunk.length + para.length > maxLength && currentChunk) {
|
|
150
|
-
chunks.push(currentChunk.trim());
|
|
151
|
-
currentChunk = para;
|
|
152
|
-
} else {
|
|
153
|
-
currentChunk += (currentChunk ? "\n\n" : "") + para;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
if (currentChunk.trim()) {
|
|
157
|
-
chunks.push(currentChunk.trim());
|
|
158
|
-
}
|
|
159
|
-
return chunks;
|
|
160
|
-
}
|
|
161
|
-
function parseMarkdownWithDocId(docId, content, contentType = "notes") {
|
|
162
|
-
const { frontmatter, body } = parseFrontmatter(content);
|
|
163
|
-
const chunks = [];
|
|
164
|
-
if (frontmatter.draft) {
|
|
165
|
-
return [];
|
|
166
|
-
}
|
|
167
|
-
const sections = splitMarkdownByHeadings(body);
|
|
168
|
-
let chunkIndex = 0;
|
|
169
|
-
for (const section of sections) {
|
|
170
|
-
const textChunks = splitByParagraphs(section.content, RAG_CONFIG.chunking.maxChunkLength);
|
|
171
|
-
for (const text of textChunks) {
|
|
172
|
-
if (!text.trim())
|
|
173
|
-
continue;
|
|
174
|
-
const isPreamble = section.level === 0 && section.title === "";
|
|
175
|
-
const sectionTitle = isPreamble ? frontmatter.description ?? frontmatter.heading : section.title;
|
|
176
|
-
const fullHeadingPath = isPreamble ? [frontmatter.heading] : [frontmatter.heading, ...section.headingPath];
|
|
177
|
-
const chunkId = generateChunkId(docId, chunkIndex);
|
|
178
|
-
chunks.push({
|
|
179
|
-
type: "markdown_note",
|
|
180
|
-
content_type: contentType,
|
|
181
|
-
top_heading: frontmatter.heading,
|
|
182
|
-
doc_id: docId,
|
|
183
|
-
chunk_id: chunkId,
|
|
184
|
-
title: sectionTitle,
|
|
185
|
-
heading_path: fullHeadingPath,
|
|
186
|
-
text,
|
|
187
|
-
content_hash: generateHash(text)
|
|
188
|
-
});
|
|
189
|
-
chunkIndex++;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
return chunks;
|
|
193
|
-
}
|
|
194
|
-
function parseMarkdown(filePath, content, contentType = "notes") {
|
|
195
|
-
return parseMarkdownWithDocId(`file:${filePath}`, content, contentType);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// ../search/dist/ingest.js
|
|
199
|
-
import { setTimeout } from "node:timers/promises";
|
|
200
|
-
var BATCH_SIZE = 20;
|
|
201
|
-
var MAX_RETRIES = 3;
|
|
202
|
-
var RETRY_DELAY_MS = 2e3;
|
|
203
|
-
async function withRetry(fn, retries = MAX_RETRIES) {
|
|
204
|
-
for (let i = 0; i < retries; i++) {
|
|
205
|
-
try {
|
|
206
|
-
return await fn();
|
|
207
|
-
} catch (error) {
|
|
208
|
-
if (i === retries - 1)
|
|
209
|
-
throw error;
|
|
210
|
-
console.log(` Retry ${i + 1}/${retries} after error...`);
|
|
211
|
-
await setTimeout(RETRY_DELAY_MS);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
throw new Error("Unreachable");
|
|
215
|
-
}
|
|
216
|
-
async function ingestChunks(chunks, options) {
|
|
217
|
-
console.log(`Total chunks: ${chunks.length}
|
|
218
|
-
`);
|
|
219
|
-
let changedChunks;
|
|
220
|
-
let skippedChunks;
|
|
221
|
-
if (options.force) {
|
|
222
|
-
changedChunks = chunks;
|
|
223
|
-
skippedChunks = 0;
|
|
224
|
-
} else {
|
|
225
|
-
console.log("Checking for changes...");
|
|
226
|
-
const chunkIds = chunks.map((c) => c.chunk_id);
|
|
227
|
-
const existingHashes = await getExistingHashes(chunkIds);
|
|
228
|
-
changedChunks = chunks.filter((chunk) => {
|
|
229
|
-
const existingHash = existingHashes.get(chunk.chunk_id);
|
|
230
|
-
return existingHash !== chunk.content_hash;
|
|
231
|
-
});
|
|
232
|
-
skippedChunks = chunks.length - changedChunks.length;
|
|
233
|
-
}
|
|
234
|
-
console.log(`Changed chunks: ${changedChunks.length}`);
|
|
235
|
-
console.log(`Skipped (unchanged): ${skippedChunks}
|
|
236
|
-
`);
|
|
237
|
-
if (changedChunks.length === 0) {
|
|
238
|
-
console.log("No changes detected. Done!");
|
|
239
|
-
return {
|
|
240
|
-
totalChunks: chunks.length,
|
|
241
|
-
changedChunks: 0,
|
|
242
|
-
skippedChunks
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
console.log("Upserting to Qdrant...");
|
|
246
|
-
let processed = 0;
|
|
247
|
-
for (let i = 0; i < changedChunks.length; i += BATCH_SIZE) {
|
|
248
|
-
const batch = changedChunks.slice(i, i + BATCH_SIZE);
|
|
249
|
-
const points = batch.map((chunk) => ({
|
|
250
|
-
id: chunk.chunk_id,
|
|
251
|
-
text: chunk.text,
|
|
252
|
-
payload: chunk
|
|
253
|
-
}));
|
|
254
|
-
await withRetry(() => upsertPoints(points));
|
|
255
|
-
processed += batch.length;
|
|
256
|
-
console.log(` Progress: ${processed}/${changedChunks.length}`);
|
|
257
|
-
await setTimeout(100);
|
|
258
|
-
}
|
|
259
|
-
return {
|
|
260
|
-
totalChunks: chunks.length,
|
|
261
|
-
changedChunks: changedChunks.length,
|
|
262
|
-
skippedChunks
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
async function pruneOrphans(currentChunkIds) {
|
|
266
|
-
const existingIds = await listAllChunkIds();
|
|
267
|
-
const currentSet = new Set(currentChunkIds);
|
|
268
|
-
const orphanIds = existingIds.filter((id) => !currentSet.has(id));
|
|
269
|
-
console.log(`Existing Qdrant chunks: ${existingIds.length}`);
|
|
270
|
-
console.log(`Orphan chunks to delete: ${orphanIds.length}`);
|
|
271
|
-
if (orphanIds.length === 0) {
|
|
272
|
-
return { deletedCount: 0 };
|
|
273
|
-
}
|
|
274
|
-
let deleted = 0;
|
|
275
|
-
for (let i = 0; i < orphanIds.length; i += BATCH_SIZE) {
|
|
276
|
-
const batch = orphanIds.slice(i, i + BATCH_SIZE);
|
|
277
|
-
await withRetry(() => deletePointsByChunkIds(batch));
|
|
278
|
-
deleted += batch.length;
|
|
279
|
-
console.log(` Prune progress: ${deleted}/${orphanIds.length}`);
|
|
280
|
-
await setTimeout(100);
|
|
281
|
-
}
|
|
282
|
-
return { deletedCount: deleted };
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// src/rag/ingest.ts
|
|
286
|
-
import { readFileSync } from "node:fs";
|
|
287
|
-
|
|
288
|
-
// src/rag/ingest-config.ts
|
|
289
|
-
var INGEST_CONFIG = {
|
|
290
|
-
paths: {
|
|
291
|
-
notes: "markdown/note/**/*.md",
|
|
292
|
-
books: "markdown/book/**/*.md",
|
|
293
|
-
articles: "json/article/**/*.json"
|
|
294
|
-
}
|
|
295
|
-
};
|
|
296
|
-
|
|
297
8
|
// src/rag/ingest.ts
|
|
298
|
-
async
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
for (const path of articleFiles) {
|
|
302
|
-
files.push({ path, type: "json", contentType: "articles" });
|
|
303
|
-
}
|
|
304
|
-
const noteFiles = await globPaths(INGEST_CONFIG.paths.notes);
|
|
305
|
-
for (const path of noteFiles) {
|
|
306
|
-
files.push({ path, type: "markdown", contentType: "notes" });
|
|
307
|
-
}
|
|
308
|
-
const bookFiles = await globPaths(INGEST_CONFIG.paths.books);
|
|
309
|
-
for (const path of bookFiles) {
|
|
310
|
-
files.push({ path, type: "markdown", contentType: "books" });
|
|
311
|
-
}
|
|
312
|
-
return files;
|
|
313
|
-
}
|
|
314
|
-
function parseFile(file) {
|
|
315
|
-
const content = readFileSync(file.path, "utf-8");
|
|
316
|
-
if (file.type === "json") {
|
|
317
|
-
return parseJsonArticle(file.path, content);
|
|
318
|
-
}
|
|
319
|
-
return parseMarkdown(file.path, content, file.contentType);
|
|
320
|
-
}
|
|
321
|
-
async function ingest(flags) {
|
|
322
|
-
console.log("Starting ingest...\n");
|
|
323
|
-
if (flags.dryRun) {
|
|
324
|
-
console.log("Dry-run mode: no writes to Qdrant\n");
|
|
325
|
-
} else {
|
|
326
|
-
await ensureCollection();
|
|
327
|
-
const initialStats = await getCollectionStats();
|
|
328
|
-
console.log(`Initial points count: ${initialStats.pointsCount}
|
|
329
|
-
`);
|
|
330
|
-
}
|
|
331
|
-
if (flags.force) {
|
|
332
|
-
console.log("Force mode enabled: skipping change detection\n");
|
|
333
|
-
}
|
|
334
|
-
const files = await listFiles();
|
|
335
|
-
const articleCount = files.filter((f) => f.contentType === "articles").length;
|
|
336
|
-
const noteCount = files.filter((f) => f.contentType === "notes").length;
|
|
337
|
-
const bookCount = files.filter((f) => f.contentType === "books").length;
|
|
338
|
-
console.log(`Found ${files.length} files to process`);
|
|
339
|
-
console.log(` - Articles: ${articleCount}`);
|
|
340
|
-
console.log(` - Notes: ${noteCount}`);
|
|
341
|
-
console.log(` - Books: ${bookCount}
|
|
342
|
-
`);
|
|
343
|
-
console.log("Parsing files...");
|
|
344
|
-
const allChunks = [];
|
|
345
|
-
for (const file of files) {
|
|
346
|
-
try {
|
|
347
|
-
const chunks = parseFile(file);
|
|
348
|
-
allChunks.push(...chunks);
|
|
349
|
-
} catch (error) {
|
|
350
|
-
console.error(`Error parsing ${file.path}:`, error);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
console.log(`Total chunks parsed: ${allChunks.length}`);
|
|
354
|
-
if (flags.dryRun) {
|
|
355
|
-
console.log("Dry-run complete. Skipping upsert and prune.");
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
const result = await ingestChunks(allChunks, { force: flags.force });
|
|
359
|
-
const pruneResult = await pruneOrphans(allChunks.map((c) => c.chunk_id));
|
|
360
|
-
const finalStats = await getCollectionStats();
|
|
361
|
-
console.log(`
|
|
362
|
-
Final points count: ${finalStats.pointsCount}`);
|
|
363
|
-
console.log(
|
|
364
|
-
`Ingest completed! (${result.changedChunks} changed, ${result.skippedChunks} skipped, ${pruneResult.deletedCount} pruned)`
|
|
365
|
-
);
|
|
366
|
-
}
|
|
367
|
-
async function main() {
|
|
368
|
-
if (!process.env.QDRANT_URL) {
|
|
369
|
-
console.error("QDRANT_URL environment variable is required.");
|
|
370
|
-
process.exit(1);
|
|
371
|
-
}
|
|
372
|
-
const flags = {
|
|
373
|
-
force: process.argv.includes("--force"),
|
|
374
|
-
dryRun: process.argv.includes("--dry-run")
|
|
375
|
-
};
|
|
376
|
-
try {
|
|
377
|
-
await ingest(flags);
|
|
378
|
-
} catch (error) {
|
|
379
|
-
console.error("\u274C \u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F:", error);
|
|
380
|
-
process.exit(2);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
main().catch((error) => {
|
|
384
|
-
console.error(error);
|
|
385
|
-
process.exit(2);
|
|
9
|
+
await runCli("rag-ingest", async () => {
|
|
10
|
+
const { main } = await import("../chunk-MJYGMGH5.js");
|
|
11
|
+
await main();
|
|
386
12
|
});
|