@astro-minimax/cli 0.5.0 → 0.7.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 +69 -0
- package/dist/commands/ai.d.ts +2 -0
- package/dist/commands/ai.d.ts.map +1 -0
- package/dist/commands/ai.js +99 -0
- package/dist/commands/ai.js.map +1 -0
- package/dist/commands/data.d.ts +2 -0
- package/dist/commands/data.d.ts.map +1 -0
- package/dist/commands/data.js +111 -0
- package/dist/commands/data.js.map +1 -0
- package/dist/commands/hooks.d.ts +2 -0
- package/dist/commands/hooks.d.ts.map +1 -0
- package/dist/commands/hooks.js +378 -0
- package/dist/commands/hooks.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +50 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/post.d.ts +2 -0
- package/dist/commands/post.d.ts.map +1 -0
- package/dist/commands/post.js +190 -0
- package/dist/commands/post.js.map +1 -0
- package/dist/commands/profile.d.ts +2 -0
- package/dist/commands/profile.d.ts.map +1 -0
- package/dist/commands/profile.js +88 -0
- package/dist/commands/profile.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +81 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/ai-process.d.ts +20 -0
- package/dist/tools/ai-process.d.ts.map +1 -0
- package/dist/tools/ai-process.js +607 -0
- package/dist/tools/ai-process.js.map +1 -0
- package/dist/tools/build-author-context.d.ts +13 -0
- package/dist/tools/build-author-context.d.ts.map +1 -0
- package/dist/tools/build-author-context.js +313 -0
- package/dist/tools/build-author-context.js.map +1 -0
- package/dist/tools/build-voice-profile.d.ts +12 -0
- package/dist/tools/build-voice-profile.d.ts.map +1 -0
- package/dist/tools/build-voice-profile.js +270 -0
- package/dist/tools/build-voice-profile.js.map +1 -0
- package/dist/tools/eval-ai-chat.d.ts +17 -0
- package/dist/tools/eval-ai-chat.d.ts.map +1 -0
- package/dist/tools/eval-ai-chat.js +332 -0
- package/dist/tools/eval-ai-chat.js.map +1 -0
- package/dist/tools/generate-author-profile.d.ts +14 -0
- package/dist/tools/generate-author-profile.d.ts.map +1 -0
- package/dist/tools/generate-author-profile.js +289 -0
- package/dist/tools/generate-author-profile.js.map +1 -0
- package/dist/tools/generate-cover.d.ts +14 -0
- package/dist/tools/generate-cover.d.ts.map +1 -0
- package/dist/tools/generate-cover.js +95 -0
- package/dist/tools/generate-cover.js.map +1 -0
- package/dist/tools/generate-og.d.ts +3 -0
- package/dist/tools/generate-og.d.ts.map +1 -0
- package/dist/tools/generate-og.js +254 -0
- package/dist/tools/generate-og.js.map +1 -0
- package/dist/tools/generate-related.d.ts +11 -0
- package/dist/tools/generate-related.d.ts.map +1 -0
- package/dist/tools/generate-related.js +124 -0
- package/dist/tools/generate-related.js.map +1 -0
- package/dist/tools/generate-tags.d.ts +14 -0
- package/dist/tools/generate-tags.d.ts.map +1 -0
- package/dist/tools/generate-tags.js +182 -0
- package/dist/tools/generate-tags.js.map +1 -0
- package/dist/tools/lib/ai-provider.d.ts +43 -0
- package/dist/tools/lib/ai-provider.d.ts.map +1 -0
- package/dist/tools/lib/ai-provider.js +146 -0
- package/dist/tools/lib/ai-provider.js.map +1 -0
- package/dist/tools/lib/frontmatter.d.ts +11 -0
- package/dist/tools/lib/frontmatter.d.ts.map +1 -0
- package/dist/tools/lib/frontmatter.js +80 -0
- package/dist/tools/lib/frontmatter.js.map +1 -0
- package/dist/tools/lib/index.d.ts +7 -0
- package/dist/tools/lib/index.d.ts.map +1 -0
- package/{template/tools/lib/index.ts → dist/tools/lib/index.js} +1 -0
- package/dist/tools/lib/index.js.map +1 -0
- package/dist/tools/lib/markdown.d.ts +6 -0
- package/dist/tools/lib/markdown.d.ts.map +1 -0
- package/dist/tools/lib/markdown.js +34 -0
- package/dist/tools/lib/markdown.js.map +1 -0
- package/dist/tools/lib/posts.d.ts +25 -0
- package/dist/tools/lib/posts.d.ts.map +1 -0
- package/dist/tools/lib/posts.js +63 -0
- package/dist/tools/lib/posts.js.map +1 -0
- package/dist/tools/lib/utils.d.ts +18 -0
- package/dist/tools/lib/utils.d.ts.map +1 -0
- package/dist/tools/lib/utils.js +121 -0
- package/dist/tools/lib/utils.js.map +1 -0
- package/dist/tools/lib/vectors.d.ts +27 -0
- package/dist/tools/lib/vectors.d.ts.map +1 -0
- package/dist/tools/lib/vectors.js +64 -0
- package/dist/tools/lib/vectors.js.map +1 -0
- package/dist/tools/summarize.d.ts +16 -0
- package/dist/tools/summarize.d.ts.map +1 -0
- package/dist/tools/summarize.js +108 -0
- package/dist/tools/summarize.js.map +1 -0
- package/dist/tools/translate.d.ts +13 -0
- package/dist/tools/translate.d.ts.map +1 -0
- package/dist/tools/translate.js +46 -0
- package/dist/tools/translate.js.map +1 -0
- package/dist/tools/vectorize.d.ts +13 -0
- package/dist/tools/vectorize.d.ts.map +1 -0
- package/dist/tools/vectorize.js +87 -0
- package/dist/tools/vectorize.js.map +1 -0
- package/package.json +14 -9
- package/template/astro.config.ts +8 -28
- package/template/datas/ai-seo.json +8 -0
- package/template/datas/ai-skip-list.json +1 -0
- package/template/datas/author-profile-context.json +21 -0
- package/template/datas/author-profile-report.json +21 -0
- package/template/datas/eval/gold-set.json +72 -0
- package/template/functions/README.md +82 -0
- package/template/functions/api/ai-info.ts +2 -2
- package/template/functions/api/chat.ts +4 -1
- package/template/functions/api/notify/comment.ts +140 -68
- package/template/functions/api/notify/debug.ts +41 -0
- package/template/functions/api/notify/status.ts +97 -0
- package/template/functions/api/notify/test-ai-chat.ts +67 -0
- package/template/package.json +22 -25
- package/template/src/config.ts +11 -0
- package/template/src/content.config.ts +29 -16
- package/template/src/env.d.ts +0 -5
- package/index.js +0 -36
- package/template/tools/README.md +0 -169
- package/template/tools/ai-process.ts +0 -816
- package/template/tools/build-author-context.ts +0 -405
- package/template/tools/build-voice-profile.ts +0 -322
- package/template/tools/generate-author-profile.ts +0 -369
- package/template/tools/generate-cover.ts +0 -123
- package/template/tools/generate-og.ts +0 -280
- package/template/tools/generate-related.ts +0 -146
- package/template/tools/generate-tags.ts +0 -251
- package/template/tools/lib/ai-provider.ts +0 -240
- package/template/tools/lib/frontmatter.ts +0 -94
- package/template/tools/lib/markdown.ts +0 -40
- package/template/tools/lib/posts.ts +0 -89
- package/template/tools/lib/utils.ts +0 -138
- package/template/tools/lib/vectors.ts +0 -96
- package/template/tools/summarize.ts +0 -142
- package/template/tools/translate.ts +0 -60
- package/template/tools/vectorize.ts +0 -105
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 博客文章读取与遍历工具
|
|
3
|
+
*/
|
|
4
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { extractFrontmatter } from "./frontmatter.js";
|
|
7
|
+
import { stripMarkdown } from "./markdown.js";
|
|
8
|
+
export const BLOG_PATH = join(process.cwd(), "src/data/blog");
|
|
9
|
+
export function getPostURL(id) {
|
|
10
|
+
const parts = id.split("/");
|
|
11
|
+
const lang = parts[0];
|
|
12
|
+
const slug = parts.slice(1).join("/");
|
|
13
|
+
return `/${lang}/posts/${slug}/`;
|
|
14
|
+
}
|
|
15
|
+
export async function getAllPosts(opts) {
|
|
16
|
+
const posts = [];
|
|
17
|
+
async function walk(dir) {
|
|
18
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
const fullPath = join(dir, entry.name);
|
|
21
|
+
if (entry.isDirectory() && !entry.name.startsWith("_")) {
|
|
22
|
+
await walk(fullPath);
|
|
23
|
+
}
|
|
24
|
+
else if (entry.name.endsWith(".md") || entry.name.endsWith(".mdx")) {
|
|
25
|
+
const content = await readFile(fullPath, "utf-8");
|
|
26
|
+
const fm = extractFrontmatter(content);
|
|
27
|
+
const isDraft = fm.data.draft === true;
|
|
28
|
+
if (isDraft && !opts?.includeDrafts)
|
|
29
|
+
continue;
|
|
30
|
+
const relativePath = fullPath.replace(BLOG_PATH + "/", "");
|
|
31
|
+
const lang = relativePath.startsWith("en/") ? "en" : "zh";
|
|
32
|
+
const id = relativePath.replace(/\.(md|mdx)$/, "");
|
|
33
|
+
posts.push({
|
|
34
|
+
id,
|
|
35
|
+
filePath: fullPath,
|
|
36
|
+
lang,
|
|
37
|
+
title: fm.data.title || "",
|
|
38
|
+
description: fm.data.description || "",
|
|
39
|
+
tags: Array.isArray(fm.data.tags) ? fm.data.tags : [],
|
|
40
|
+
category: fm.data.category || "",
|
|
41
|
+
body: opts?.stripBody === false ? fm.body : stripMarkdown(content),
|
|
42
|
+
draft: isDraft,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
await walk(BLOG_PATH);
|
|
48
|
+
return posts;
|
|
49
|
+
}
|
|
50
|
+
export function getExistingTaxonomy(posts) {
|
|
51
|
+
const tagSet = new Set();
|
|
52
|
+
const catSet = new Set();
|
|
53
|
+
for (const post of posts) {
|
|
54
|
+
post.tags.forEach(t => tagSet.add(t));
|
|
55
|
+
if (post.category)
|
|
56
|
+
catSet.add(post.category);
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
tags: Array.from(tagSet).sort(),
|
|
60
|
+
categories: Array.from(catSet).sort(),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=posts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"posts.js","sourceRoot":"","sources":["../../../src/tools/lib/posts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 共享工具函数
|
|
3
|
+
*/
|
|
4
|
+
export declare const ROOT_DIR: string;
|
|
5
|
+
export declare const DATA_DIR: string;
|
|
6
|
+
export declare const BLOG_DIR: string;
|
|
7
|
+
export declare function loadEnv(): Promise<void>;
|
|
8
|
+
export declare function readJson<T>(filePath: string, fallback: T): Promise<T>;
|
|
9
|
+
export declare function writeJson(filePath: string, data: unknown): Promise<void>;
|
|
10
|
+
export declare function truncate(text: string, max?: number): string;
|
|
11
|
+
export declare function normalizeSpace(text: string): string;
|
|
12
|
+
export declare function parseCliArgs<T extends Record<string, unknown>>(defaults: T, parsers?: Partial<{
|
|
13
|
+
[K in keyof T]: (value: string) => T[K];
|
|
14
|
+
}>): T;
|
|
15
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
16
|
+
export declare function toPlainDate(value: unknown): string;
|
|
17
|
+
export declare function contentHash(text: string): string;
|
|
18
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/tools/lib/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,eAAO,MAAM,QAAQ,QAAgB,CAAC;AACtC,eAAO,MAAM,QAAQ,QAA0B,CAAC;AAChD,eAAO,MAAM,QAAQ,QAAkC,CAAC;AAIxD,wBAAsB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAwB7C;AAID,wBAAsB,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAO3E;AAED,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAG9E;AAID,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAM,GAAG,MAAM,CAIxD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAID,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5D,QAAQ,EAAE,CAAC,EACX,OAAO,CAAC,EAAE,OAAO,CAAC;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC;CAAE,CAAC,GAC7D,CAAC,CA2CH;AAID,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAKlD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 共享工具函数
|
|
3
|
+
*/
|
|
4
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
export const ROOT_DIR = process.cwd();
|
|
8
|
+
export const DATA_DIR = join(ROOT_DIR, "datas");
|
|
9
|
+
export const BLOG_DIR = join(ROOT_DIR, "src/data/blog");
|
|
10
|
+
// ─── 环境变量加载 ────────────────────────────────────────
|
|
11
|
+
export async function loadEnv() {
|
|
12
|
+
const envPath = join(ROOT_DIR, ".env");
|
|
13
|
+
try {
|
|
14
|
+
const content = await readFile(envPath, "utf-8");
|
|
15
|
+
for (const line of content.split("\n")) {
|
|
16
|
+
const trimmed = line.trim();
|
|
17
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
18
|
+
continue;
|
|
19
|
+
const eqIndex = trimmed.indexOf("=");
|
|
20
|
+
if (eqIndex === -1)
|
|
21
|
+
continue;
|
|
22
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
23
|
+
let value = trimmed.slice(eqIndex + 1).trim();
|
|
24
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
25
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
26
|
+
value = value.slice(1, -1);
|
|
27
|
+
}
|
|
28
|
+
if (!process.env[key]) {
|
|
29
|
+
process.env[key] = value;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// .env 不存在时继续
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// ─── JSON 文件操作 ────────────────────────────────────────
|
|
38
|
+
export async function readJson(filePath, fallback) {
|
|
39
|
+
try {
|
|
40
|
+
const raw = await readFile(filePath, "utf-8");
|
|
41
|
+
return JSON.parse(raw);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return fallback;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export async function writeJson(filePath, data) {
|
|
48
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
49
|
+
await writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
50
|
+
}
|
|
51
|
+
// ─── 文本处理 ────────────────────────────────────────────────
|
|
52
|
+
export function truncate(text, max = 120) {
|
|
53
|
+
if (!text)
|
|
54
|
+
return "";
|
|
55
|
+
if (text.length <= max)
|
|
56
|
+
return text;
|
|
57
|
+
return `${text.slice(0, max - 1)}…`;
|
|
58
|
+
}
|
|
59
|
+
export function normalizeSpace(text) {
|
|
60
|
+
return String(text ?? "").replace(/\s+/g, " ").trim();
|
|
61
|
+
}
|
|
62
|
+
// ─── CLI 参数解析 ─────────────────────────────────────────────
|
|
63
|
+
export function parseCliArgs(defaults, parsers) {
|
|
64
|
+
const args = process.argv.slice(2);
|
|
65
|
+
const result = { ...defaults };
|
|
66
|
+
// Build a map of all possible key variants
|
|
67
|
+
const keyMap = new Map();
|
|
68
|
+
for (const key of Object.keys(defaults)) {
|
|
69
|
+
const keyStr = String(key);
|
|
70
|
+
keyMap.set(keyStr, key);
|
|
71
|
+
keyMap.set(keyStr.toLowerCase(), key);
|
|
72
|
+
// kebab-case from camelCase (noAI -> no-a-i for each capital)
|
|
73
|
+
const kebabKey = keyStr.replace(/[A-Z]/g, (c) => `-${c}`).toLowerCase();
|
|
74
|
+
keyMap.set(kebabKey, key);
|
|
75
|
+
// Also handle natural kebab-case (noAI -> no-ai)
|
|
76
|
+
// This handles cases like noAI -> no-ai (user types --no-ai)
|
|
77
|
+
const naturalKebab = keyStr.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
78
|
+
keyMap.set(naturalKebab, key);
|
|
79
|
+
}
|
|
80
|
+
for (const arg of args) {
|
|
81
|
+
if (arg.startsWith("--")) {
|
|
82
|
+
const eqIndex = arg.indexOf("=");
|
|
83
|
+
if (eqIndex === -1) {
|
|
84
|
+
// Boolean flag
|
|
85
|
+
const key = arg.slice(2);
|
|
86
|
+
const mappedKey = keyMap.get(key) || keyMap.get(key.toLowerCase());
|
|
87
|
+
if (mappedKey && typeof result[mappedKey] === "boolean") {
|
|
88
|
+
result[String(mappedKey)] = true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const key = arg.slice(2, eqIndex);
|
|
93
|
+
const value = arg.slice(eqIndex + 1);
|
|
94
|
+
const mappedKey = keyMap.get(key) || keyMap.get(key.toLowerCase());
|
|
95
|
+
if (mappedKey) {
|
|
96
|
+
const parser = parsers?.[mappedKey];
|
|
97
|
+
result[String(mappedKey)] = parser
|
|
98
|
+
? parser(value)
|
|
99
|
+
: value;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
// ─── 其他工具 ─────────────────────────────────────────────────
|
|
107
|
+
export function sleep(ms) {
|
|
108
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
109
|
+
}
|
|
110
|
+
export function toPlainDate(value) {
|
|
111
|
+
if (!value)
|
|
112
|
+
return "";
|
|
113
|
+
const parsed = new Date(value);
|
|
114
|
+
if (Number.isNaN(parsed.getTime()))
|
|
115
|
+
return String(value).slice(0, 10);
|
|
116
|
+
return parsed.toISOString().slice(0, 10);
|
|
117
|
+
}
|
|
118
|
+
export function contentHash(text) {
|
|
119
|
+
return createHash("sha256").update(text).digest("hex").slice(0, 12);
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/tools/lib/utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 向量计算与 TF-IDF 工具
|
|
3
|
+
*/
|
|
4
|
+
export declare function cosineSimilarity(a: number[], b: number[]): number;
|
|
5
|
+
export declare function tokenize(text: string): string[];
|
|
6
|
+
export declare function buildVocabulary(documents: string[][]): string[];
|
|
7
|
+
export declare function computeTfIdf(tokens: string[], vocabulary: string[], idf: Map<string, number>): number[];
|
|
8
|
+
export interface ContentChunk {
|
|
9
|
+
postId: string;
|
|
10
|
+
title: string;
|
|
11
|
+
lang: string;
|
|
12
|
+
chunkIndex: number;
|
|
13
|
+
text: string;
|
|
14
|
+
vector?: number[];
|
|
15
|
+
}
|
|
16
|
+
export interface VectorIndex {
|
|
17
|
+
version: number;
|
|
18
|
+
method: "tfidf" | "openai";
|
|
19
|
+
createdAt: string;
|
|
20
|
+
vocabulary?: string[];
|
|
21
|
+
chunks: ContentChunk[];
|
|
22
|
+
}
|
|
23
|
+
export declare function generateTfIdfVectors(chunks: ContentChunk[]): {
|
|
24
|
+
vocabulary: string[];
|
|
25
|
+
vectors: number[][];
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=vectors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vectors.d.ts","sourceRoot":"","sources":["../../../src/tools/lib/vectors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAYjE;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAS/C;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,GAAG,MAAM,EAAE,CAc/D;AAED,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EAAE,EAChB,UAAU,EAAE,MAAM,EAAE,EACpB,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACvB,MAAM,EAAE,CAUV;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,EAAE,YAAY,EAAE,CAAC;CACxB;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,YAAY,EAAE,GACrB;IAAE,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAA;CAAE,CAe/C"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 向量计算与 TF-IDF 工具
|
|
3
|
+
*/
|
|
4
|
+
export function cosineSimilarity(a, b) {
|
|
5
|
+
if (a.length !== b.length)
|
|
6
|
+
return 0;
|
|
7
|
+
let dot = 0, magA = 0, magB = 0;
|
|
8
|
+
for (let i = 0; i < a.length; i++) {
|
|
9
|
+
dot += a[i] * b[i];
|
|
10
|
+
magA += a[i] * a[i];
|
|
11
|
+
magB += b[i] * b[i];
|
|
12
|
+
}
|
|
13
|
+
const mag = Math.sqrt(magA) * Math.sqrt(magB);
|
|
14
|
+
return mag === 0 ? 0 : dot / mag;
|
|
15
|
+
}
|
|
16
|
+
export function tokenize(text) {
|
|
17
|
+
const CJK = /[\u4e00-\u9fff\u3400-\u4dbf]/g;
|
|
18
|
+
const cjkChars = text.match(CJK) || [];
|
|
19
|
+
const latin = text
|
|
20
|
+
.replace(CJK, " ")
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.split(/\W+/)
|
|
23
|
+
.filter(w => w.length > 2);
|
|
24
|
+
return [...cjkChars, ...latin];
|
|
25
|
+
}
|
|
26
|
+
export function buildVocabulary(documents) {
|
|
27
|
+
const df = new Map();
|
|
28
|
+
for (const doc of documents) {
|
|
29
|
+
const unique = new Set(doc);
|
|
30
|
+
for (const term of unique) {
|
|
31
|
+
df.set(term, (df.get(term) || 0) + 1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const maxDf = documents.length * 0.8;
|
|
35
|
+
return Array.from(df.entries())
|
|
36
|
+
.filter(([, count]) => count >= 2 && count <= maxDf)
|
|
37
|
+
.sort((a, b) => b[1] - a[1])
|
|
38
|
+
.slice(0, 2000)
|
|
39
|
+
.map(([term]) => term);
|
|
40
|
+
}
|
|
41
|
+
export function computeTfIdf(tokens, vocabulary, idf) {
|
|
42
|
+
const tf = new Map();
|
|
43
|
+
for (const t of tokens)
|
|
44
|
+
tf.set(t, (tf.get(t) || 0) + 1);
|
|
45
|
+
const maxTf = Math.max(...tf.values(), 1);
|
|
46
|
+
return vocabulary.map(term => {
|
|
47
|
+
const termTf = (tf.get(term) || 0) / maxTf;
|
|
48
|
+
const termIdf = idf.get(term) || 0;
|
|
49
|
+
return +(termTf * termIdf).toFixed(6);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
export function generateTfIdfVectors(chunks) {
|
|
53
|
+
const tokenizedDocs = chunks.map(c => tokenize(c.text));
|
|
54
|
+
const vocabulary = buildVocabulary(tokenizedDocs);
|
|
55
|
+
const N = tokenizedDocs.length;
|
|
56
|
+
const idf = new Map();
|
|
57
|
+
for (const term of vocabulary) {
|
|
58
|
+
const docCount = tokenizedDocs.filter(doc => doc.includes(term)).length;
|
|
59
|
+
idf.set(term, Math.log(N / (docCount + 1)) + 1);
|
|
60
|
+
}
|
|
61
|
+
const vectors = tokenizedDocs.map(tokens => computeTfIdf(tokens, vocabulary, idf));
|
|
62
|
+
return { vocabulary, vectors };
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=vectors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vectors.js","sourceRoot":"","sources":["../../../src/tools/lib/vectors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* AI 辅助写作 - 文章摘要生成
|
|
4
|
+
*
|
|
5
|
+
* 用法:
|
|
6
|
+
* pnpm run tools:summarize <文章路径> # 为单篇文章生成摘要
|
|
7
|
+
* pnpm run tools:summarize <文章路径> --write # 生成并写入 frontmatter
|
|
8
|
+
* pnpm run tools:summarize --all # 分析所有缺少 description 的文章
|
|
9
|
+
* pnpm run tools:summarize --all --dry-run # 预览所有推荐但不写入
|
|
10
|
+
* pnpm run tools:summarize --all --write # 批量生成并写入
|
|
11
|
+
*
|
|
12
|
+
* 环境变量:
|
|
13
|
+
* AI_API_KEY / OPENAI_API_KEY(可选,无 key 时使用前 200 字摘录)
|
|
14
|
+
*/
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=summarize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summarize.d.ts","sourceRoot":"","sources":["../../src/tools/summarize.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* AI 辅助写作 - 文章摘要生成
|
|
4
|
+
*
|
|
5
|
+
* 用法:
|
|
6
|
+
* pnpm run tools:summarize <文章路径> # 为单篇文章生成摘要
|
|
7
|
+
* pnpm run tools:summarize <文章路径> --write # 生成并写入 frontmatter
|
|
8
|
+
* pnpm run tools:summarize --all # 分析所有缺少 description 的文章
|
|
9
|
+
* pnpm run tools:summarize --all --dry-run # 预览所有推荐但不写入
|
|
10
|
+
* pnpm run tools:summarize --all --write # 批量生成并写入
|
|
11
|
+
*
|
|
12
|
+
* 环境变量:
|
|
13
|
+
* AI_API_KEY / OPENAI_API_KEY(可选,无 key 时使用前 200 字摘录)
|
|
14
|
+
*/
|
|
15
|
+
import { readFile } from "node:fs/promises";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { extractFrontmatter, updateFrontmatterField, } from "./lib/frontmatter.js";
|
|
18
|
+
import { stripMarkdown } from "./lib/markdown.js";
|
|
19
|
+
import { getAllPosts } from "./lib/posts.js";
|
|
20
|
+
import { chatCompletion, hasAPIKey } from "./lib/ai-provider.js";
|
|
21
|
+
import { writeFile } from "node:fs/promises";
|
|
22
|
+
async function generateSummary(content, title) {
|
|
23
|
+
if (!hasAPIKey()) {
|
|
24
|
+
return content.replace(/\n+/g, " ").slice(0, 200).trim() + "...";
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
return await chatCompletion([
|
|
28
|
+
{
|
|
29
|
+
role: "system",
|
|
30
|
+
content: "你是一个技术博客编辑,擅长撰写简洁的文章摘要。根据文章内容生成 100-150 字的摘要,要求准确概括核心内容。使用与文章相同的语言。",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
role: "user",
|
|
34
|
+
content: `文章标题: ${title}\n\n正文:\n${content.slice(0, 4000)}`,
|
|
35
|
+
},
|
|
36
|
+
], { maxTokens: 200 });
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
console.error(" 摘要生成失败:", err.message);
|
|
40
|
+
return content.replace(/\n+/g, " ").slice(0, 200).trim() + "...";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function main() {
|
|
44
|
+
const args = process.argv.slice(2);
|
|
45
|
+
const all = args.includes("--all");
|
|
46
|
+
const dryRun = args.includes("--dry-run");
|
|
47
|
+
const write = args.includes("--write");
|
|
48
|
+
const filePath = args.find(a => !a.startsWith("--"));
|
|
49
|
+
const mode = hasAPIKey() ? "AI" : "文本摘录";
|
|
50
|
+
console.log(`📝 摘要生成模式: ${mode}\n`);
|
|
51
|
+
if (all) {
|
|
52
|
+
const allPosts = await getAllPosts();
|
|
53
|
+
const needsSummary = allPosts.filter(p => !p.description || p.description.length < 10);
|
|
54
|
+
console.log(`📚 共 ${allPosts.length} 篇文章,${needsSummary.length} 篇需要摘要\n`);
|
|
55
|
+
if (needsSummary.length === 0) {
|
|
56
|
+
console.log("✅ 所有文章都已有摘要!");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
for (const post of needsSummary) {
|
|
60
|
+
const relativePath = post.filePath.replace(process.cwd() + "/", "");
|
|
61
|
+
console.log(`📄 ${relativePath}`);
|
|
62
|
+
console.log(` 标题: ${post.title}`);
|
|
63
|
+
const summary = await generateSummary(post.body, post.title);
|
|
64
|
+
console.log(` 摘要: ${summary.slice(0, 100)}${summary.length > 100 ? "..." : ""}`);
|
|
65
|
+
if (write && !dryRun) {
|
|
66
|
+
const content = await readFile(post.filePath, "utf-8");
|
|
67
|
+
const newContent = updateFrontmatterField(content, "description", JSON.stringify(summary));
|
|
68
|
+
await writeFile(post.filePath, newContent, "utf-8");
|
|
69
|
+
console.log(` ✍️ 已写入 frontmatter`);
|
|
70
|
+
}
|
|
71
|
+
else if (dryRun) {
|
|
72
|
+
console.log(` (dry-run 模式,未写入)`);
|
|
73
|
+
}
|
|
74
|
+
console.log("");
|
|
75
|
+
if (hasAPIKey())
|
|
76
|
+
await new Promise(r => setTimeout(r, 500));
|
|
77
|
+
}
|
|
78
|
+
console.log(`✅ 完成!处理了 ${needsSummary.length} 篇文章`);
|
|
79
|
+
}
|
|
80
|
+
else if (filePath) {
|
|
81
|
+
const fullPath = join(process.cwd(), filePath);
|
|
82
|
+
const content = await readFile(fullPath, "utf-8");
|
|
83
|
+
const fm = extractFrontmatter(content);
|
|
84
|
+
const body = stripMarkdown(content);
|
|
85
|
+
const title = fm.data.title || "";
|
|
86
|
+
console.log(`📄 文件: ${filePath}`);
|
|
87
|
+
console.log(` 标题: ${title}`);
|
|
88
|
+
console.log(` 当前摘要: ${fm.data.description || "(无)"}\n`);
|
|
89
|
+
const summary = await generateSummary(body, title);
|
|
90
|
+
console.log(`🔍 生成的摘要:\n ${summary}\n`);
|
|
91
|
+
if (write) {
|
|
92
|
+
const newContent = updateFrontmatterField(content, "description", JSON.stringify(summary));
|
|
93
|
+
await writeFile(fullPath, newContent, "utf-8");
|
|
94
|
+
console.log(`✍️ 已写入 frontmatter`);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
console.log(`💡 使用 --write 参数写入 frontmatter`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
console.error("用法:");
|
|
102
|
+
console.error(" pnpm run tools:summarize <文章路径> [--write]");
|
|
103
|
+
console.error(" pnpm run tools:summarize --all [--dry-run] [--write]");
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
main().catch(console.error);
|
|
108
|
+
//# sourceMappingURL=summarize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summarize.js","sourceRoot":"","sources":["../../src/tools/summarize.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* 多语言翻译工具 - 将文章翻译为目标语言
|
|
4
|
+
*
|
|
5
|
+
* 用法:
|
|
6
|
+
* pnpm run tools:translate <源文件> [目标语言]
|
|
7
|
+
* 示例: pnpm run tools:translate src/data/blog/zh/post.md en
|
|
8
|
+
*
|
|
9
|
+
* 环境变量:
|
|
10
|
+
* AI_API_KEY / OPENAI_API_KEY — API key
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=translate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translate.d.ts","sourceRoot":"","sources":["../../src/tools/translate.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* 多语言翻译工具 - 将文章翻译为目标语言
|
|
4
|
+
*
|
|
5
|
+
* 用法:
|
|
6
|
+
* pnpm run tools:translate <源文件> [目标语言]
|
|
7
|
+
* 示例: pnpm run tools:translate src/data/blog/zh/post.md en
|
|
8
|
+
*
|
|
9
|
+
* 环境变量:
|
|
10
|
+
* AI_API_KEY / OPENAI_API_KEY — API key
|
|
11
|
+
*/
|
|
12
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
13
|
+
import { resolve, dirname, basename, join } from "node:path";
|
|
14
|
+
import { chatCompletion } from "./lib/ai-provider.js";
|
|
15
|
+
async function main() {
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
const filePath = args[0];
|
|
18
|
+
const targetLang = args[1] || "en";
|
|
19
|
+
if (!filePath) {
|
|
20
|
+
console.error("用法: pnpm run tools:translate <源文件> [目标语言]");
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
const fullPath = resolve(process.cwd(), filePath);
|
|
24
|
+
const content = await readFile(fullPath, "utf-8");
|
|
25
|
+
const langName = targetLang === "en" ? "英文" : targetLang === "zh" ? "中文" : targetLang;
|
|
26
|
+
console.log(`🌐 翻译为 ${langName}...`);
|
|
27
|
+
const translated = await chatCompletion([
|
|
28
|
+
{
|
|
29
|
+
role: "system",
|
|
30
|
+
content: `你是一个专业的技术文档翻译。将内容翻译为${langName},保持 Markdown 格式和 frontmatter 结构。只输出翻译结果。`,
|
|
31
|
+
},
|
|
32
|
+
{ role: "user", content },
|
|
33
|
+
], { maxTokens: 4000 });
|
|
34
|
+
const outDir = dirname(fullPath)
|
|
35
|
+
.replace(/\/zh\/?$/, `/${targetLang}`)
|
|
36
|
+
.replace(/\/en\/?$/, `/${targetLang}`);
|
|
37
|
+
const outPath = join(outDir, basename(fullPath));
|
|
38
|
+
await mkdir(outDir, { recursive: true });
|
|
39
|
+
await writeFile(outPath, translated, "utf-8");
|
|
40
|
+
console.log(`✅ 已翻译并保存到: ${outPath}`);
|
|
41
|
+
}
|
|
42
|
+
main().catch(err => {
|
|
43
|
+
console.error("❌ 错误:", err.message || err);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
});
|
|
46
|
+
//# sourceMappingURL=translate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translate.js","sourceRoot":"","sources":["../../src/tools/translate.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACzB,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IAEnC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;QAC3D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,QAAQ,CAAC,CAAC;IAClD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAElD,MAAM,QAAQ,GACZ,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC;IAEvE,OAAO,CAAC,GAAG,CAAC,UAAU,QAAQ,KAAK,CAAC,CAAC;IAErC,MAAM,UAAU,GAAG,MAAM,cAAc,CACrC;QACE;YACE,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,uBAAuB,QAAQ,0CAA0C;SACnF;QACD,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;KAC1B,EACD,EAAE,SAAS,EAAE,IAAI,EAAE,CACpB,CAAC;IAEF,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;SAC7B,OAAO,CAAC,UAAU,EAAE,IAAI,UAAU,EAAE,CAAC;SACrC,OAAO,CAAC,UAAU,EAAE,IAAI,UAAU,EAAE,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEjD,MAAM,KAAK,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,MAAM,SAAS,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;IAE9C,OAAO,CAAC,GAAG,CAAC,cAAc,OAAO,EAAE,CAAC,CAAC;AACvC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;IACjB,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,CAAC;IAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* 博客内容向量化与索引生成
|
|
4
|
+
*
|
|
5
|
+
* 用法:
|
|
6
|
+
* pnpm run tools:vectorize # TF-IDF 模式(无需 API Key)
|
|
7
|
+
* pnpm run tools:vectorize --openai # OpenAI Embeddings 模式
|
|
8
|
+
*
|
|
9
|
+
* 环境变量:
|
|
10
|
+
* AI_API_KEY / OPENAI_API_KEY(仅 --openai 模式需要)
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=vectorize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vectorize.d.ts","sourceRoot":"","sources":["../../src/tools/vectorize.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* 博客内容向量化与索引生成
|
|
4
|
+
*
|
|
5
|
+
* 用法:
|
|
6
|
+
* pnpm run tools:vectorize # TF-IDF 模式(无需 API Key)
|
|
7
|
+
* pnpm run tools:vectorize --openai # OpenAI Embeddings 模式
|
|
8
|
+
*
|
|
9
|
+
* 环境变量:
|
|
10
|
+
* AI_API_KEY / OPENAI_API_KEY(仅 --openai 模式需要)
|
|
11
|
+
*/
|
|
12
|
+
import { writeFile, mkdir } from "node:fs/promises";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { chunkText } from "./lib/markdown.js";
|
|
15
|
+
import { getAllPosts } from "./lib/posts.js";
|
|
16
|
+
import { generateEmbeddings } from "./lib/ai-provider.js";
|
|
17
|
+
import { generateTfIdfVectors, } from "./lib/vectors.js";
|
|
18
|
+
const OUTPUT_DIR = join(process.cwd(), "src/data/vectors");
|
|
19
|
+
const OUTPUT_FILE = join(OUTPUT_DIR, "index.json");
|
|
20
|
+
const CHUNK_SIZE = 500;
|
|
21
|
+
const CHUNK_OVERLAP = 50;
|
|
22
|
+
async function main() {
|
|
23
|
+
const useOpenAI = process.argv.includes("--openai");
|
|
24
|
+
const method = useOpenAI ? "openai" : "tfidf";
|
|
25
|
+
console.log(`📚 读取博客文章...`);
|
|
26
|
+
const posts = await getAllPosts();
|
|
27
|
+
console.log(` 找到 ${posts.length} 篇文章\n`);
|
|
28
|
+
console.log(`📝 分割内容块...`);
|
|
29
|
+
const chunks = [];
|
|
30
|
+
for (const post of posts) {
|
|
31
|
+
const fullText = `${post.title}\n${post.description}\n${post.body}`;
|
|
32
|
+
const textChunks = chunkText(fullText, CHUNK_SIZE, CHUNK_OVERLAP);
|
|
33
|
+
for (let i = 0; i < textChunks.length; i++) {
|
|
34
|
+
chunks.push({
|
|
35
|
+
postId: post.id,
|
|
36
|
+
title: post.title,
|
|
37
|
+
lang: post.lang,
|
|
38
|
+
chunkIndex: i,
|
|
39
|
+
text: textChunks[i],
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
console.log(` 生成 ${chunks.length} 个内容块\n`);
|
|
44
|
+
if (method === "openai") {
|
|
45
|
+
console.log(`🧠 使用 OpenAI Embeddings 生成向量...`);
|
|
46
|
+
const texts = chunks.map(c => c.text);
|
|
47
|
+
const vectors = await generateEmbeddings(texts);
|
|
48
|
+
chunks.forEach((c, i) => {
|
|
49
|
+
c.vector = vectors[i];
|
|
50
|
+
});
|
|
51
|
+
const index = {
|
|
52
|
+
version: 1,
|
|
53
|
+
method: "openai",
|
|
54
|
+
createdAt: new Date().toISOString(),
|
|
55
|
+
chunks,
|
|
56
|
+
};
|
|
57
|
+
await mkdir(OUTPUT_DIR, { recursive: true });
|
|
58
|
+
await writeFile(OUTPUT_FILE, JSON.stringify(index, null, 0), "utf-8");
|
|
59
|
+
const fileSize = (JSON.stringify(index).length / 1024).toFixed(1);
|
|
60
|
+
console.log(`\n✅ 索引已生成: ${OUTPUT_FILE}`);
|
|
61
|
+
console.log(` 文件大小: ${fileSize} KB`);
|
|
62
|
+
console.log(` 内容块: ${chunks.length}`);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.log(`🔢 使用 TF-IDF 生成向量...`);
|
|
66
|
+
const { vocabulary, vectors } = generateTfIdfVectors(chunks);
|
|
67
|
+
chunks.forEach((c, i) => {
|
|
68
|
+
c.vector = vectors[i];
|
|
69
|
+
});
|
|
70
|
+
const index = {
|
|
71
|
+
version: 1,
|
|
72
|
+
method: "tfidf",
|
|
73
|
+
createdAt: new Date().toISOString(),
|
|
74
|
+
vocabulary,
|
|
75
|
+
chunks,
|
|
76
|
+
};
|
|
77
|
+
await mkdir(OUTPUT_DIR, { recursive: true });
|
|
78
|
+
await writeFile(OUTPUT_FILE, JSON.stringify(index, null, 0), "utf-8");
|
|
79
|
+
const fileSize = (JSON.stringify(index).length / 1024).toFixed(1);
|
|
80
|
+
console.log(`\n✅ 索引已生成: ${OUTPUT_FILE}`);
|
|
81
|
+
console.log(` 文件大小: ${fileSize} KB`);
|
|
82
|
+
console.log(` 词汇量: ${vocabulary.length}`);
|
|
83
|
+
console.log(` 内容块: ${chunks.length}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
main().catch(console.error);
|
|
87
|
+
//# sourceMappingURL=vectorize.js.map
|