@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.
Files changed (142) hide show
  1. package/README.md +69 -0
  2. package/dist/commands/ai.d.ts +2 -0
  3. package/dist/commands/ai.d.ts.map +1 -0
  4. package/dist/commands/ai.js +99 -0
  5. package/dist/commands/ai.js.map +1 -0
  6. package/dist/commands/data.d.ts +2 -0
  7. package/dist/commands/data.d.ts.map +1 -0
  8. package/dist/commands/data.js +111 -0
  9. package/dist/commands/data.js.map +1 -0
  10. package/dist/commands/hooks.d.ts +2 -0
  11. package/dist/commands/hooks.d.ts.map +1 -0
  12. package/dist/commands/hooks.js +378 -0
  13. package/dist/commands/hooks.js.map +1 -0
  14. package/dist/commands/init.d.ts +2 -0
  15. package/dist/commands/init.d.ts.map +1 -0
  16. package/dist/commands/init.js +50 -0
  17. package/dist/commands/init.js.map +1 -0
  18. package/dist/commands/post.d.ts +2 -0
  19. package/dist/commands/post.d.ts.map +1 -0
  20. package/dist/commands/post.js +190 -0
  21. package/dist/commands/post.js.map +1 -0
  22. package/dist/commands/profile.d.ts +2 -0
  23. package/dist/commands/profile.d.ts.map +1 -0
  24. package/dist/commands/profile.js +88 -0
  25. package/dist/commands/profile.js.map +1 -0
  26. package/dist/index.d.ts +3 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +81 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/tools/ai-process.d.ts +20 -0
  31. package/dist/tools/ai-process.d.ts.map +1 -0
  32. package/dist/tools/ai-process.js +607 -0
  33. package/dist/tools/ai-process.js.map +1 -0
  34. package/dist/tools/build-author-context.d.ts +13 -0
  35. package/dist/tools/build-author-context.d.ts.map +1 -0
  36. package/dist/tools/build-author-context.js +313 -0
  37. package/dist/tools/build-author-context.js.map +1 -0
  38. package/dist/tools/build-voice-profile.d.ts +12 -0
  39. package/dist/tools/build-voice-profile.d.ts.map +1 -0
  40. package/dist/tools/build-voice-profile.js +270 -0
  41. package/dist/tools/build-voice-profile.js.map +1 -0
  42. package/dist/tools/eval-ai-chat.d.ts +17 -0
  43. package/dist/tools/eval-ai-chat.d.ts.map +1 -0
  44. package/dist/tools/eval-ai-chat.js +332 -0
  45. package/dist/tools/eval-ai-chat.js.map +1 -0
  46. package/dist/tools/generate-author-profile.d.ts +14 -0
  47. package/dist/tools/generate-author-profile.d.ts.map +1 -0
  48. package/dist/tools/generate-author-profile.js +289 -0
  49. package/dist/tools/generate-author-profile.js.map +1 -0
  50. package/dist/tools/generate-cover.d.ts +14 -0
  51. package/dist/tools/generate-cover.d.ts.map +1 -0
  52. package/dist/tools/generate-cover.js +95 -0
  53. package/dist/tools/generate-cover.js.map +1 -0
  54. package/dist/tools/generate-og.d.ts +3 -0
  55. package/dist/tools/generate-og.d.ts.map +1 -0
  56. package/dist/tools/generate-og.js +254 -0
  57. package/dist/tools/generate-og.js.map +1 -0
  58. package/dist/tools/generate-related.d.ts +11 -0
  59. package/dist/tools/generate-related.d.ts.map +1 -0
  60. package/dist/tools/generate-related.js +124 -0
  61. package/dist/tools/generate-related.js.map +1 -0
  62. package/dist/tools/generate-tags.d.ts +14 -0
  63. package/dist/tools/generate-tags.d.ts.map +1 -0
  64. package/dist/tools/generate-tags.js +182 -0
  65. package/dist/tools/generate-tags.js.map +1 -0
  66. package/dist/tools/lib/ai-provider.d.ts +43 -0
  67. package/dist/tools/lib/ai-provider.d.ts.map +1 -0
  68. package/dist/tools/lib/ai-provider.js +146 -0
  69. package/dist/tools/lib/ai-provider.js.map +1 -0
  70. package/dist/tools/lib/frontmatter.d.ts +11 -0
  71. package/dist/tools/lib/frontmatter.d.ts.map +1 -0
  72. package/dist/tools/lib/frontmatter.js +80 -0
  73. package/dist/tools/lib/frontmatter.js.map +1 -0
  74. package/dist/tools/lib/index.d.ts +7 -0
  75. package/dist/tools/lib/index.d.ts.map +1 -0
  76. package/{template/tools/lib/index.ts → dist/tools/lib/index.js} +1 -0
  77. package/dist/tools/lib/index.js.map +1 -0
  78. package/dist/tools/lib/markdown.d.ts +6 -0
  79. package/dist/tools/lib/markdown.d.ts.map +1 -0
  80. package/dist/tools/lib/markdown.js +34 -0
  81. package/dist/tools/lib/markdown.js.map +1 -0
  82. package/dist/tools/lib/posts.d.ts +25 -0
  83. package/dist/tools/lib/posts.d.ts.map +1 -0
  84. package/dist/tools/lib/posts.js +63 -0
  85. package/dist/tools/lib/posts.js.map +1 -0
  86. package/dist/tools/lib/utils.d.ts +18 -0
  87. package/dist/tools/lib/utils.d.ts.map +1 -0
  88. package/dist/tools/lib/utils.js +121 -0
  89. package/dist/tools/lib/utils.js.map +1 -0
  90. package/dist/tools/lib/vectors.d.ts +27 -0
  91. package/dist/tools/lib/vectors.d.ts.map +1 -0
  92. package/dist/tools/lib/vectors.js +64 -0
  93. package/dist/tools/lib/vectors.js.map +1 -0
  94. package/dist/tools/summarize.d.ts +16 -0
  95. package/dist/tools/summarize.d.ts.map +1 -0
  96. package/dist/tools/summarize.js +108 -0
  97. package/dist/tools/summarize.js.map +1 -0
  98. package/dist/tools/translate.d.ts +13 -0
  99. package/dist/tools/translate.d.ts.map +1 -0
  100. package/dist/tools/translate.js +46 -0
  101. package/dist/tools/translate.js.map +1 -0
  102. package/dist/tools/vectorize.d.ts +13 -0
  103. package/dist/tools/vectorize.d.ts.map +1 -0
  104. package/dist/tools/vectorize.js +87 -0
  105. package/dist/tools/vectorize.js.map +1 -0
  106. package/package.json +14 -9
  107. package/template/astro.config.ts +8 -28
  108. package/template/datas/ai-seo.json +8 -0
  109. package/template/datas/ai-skip-list.json +1 -0
  110. package/template/datas/author-profile-context.json +21 -0
  111. package/template/datas/author-profile-report.json +21 -0
  112. package/template/datas/eval/gold-set.json +72 -0
  113. package/template/functions/README.md +82 -0
  114. package/template/functions/api/ai-info.ts +2 -2
  115. package/template/functions/api/chat.ts +4 -1
  116. package/template/functions/api/notify/comment.ts +140 -68
  117. package/template/functions/api/notify/debug.ts +41 -0
  118. package/template/functions/api/notify/status.ts +97 -0
  119. package/template/functions/api/notify/test-ai-chat.ts +67 -0
  120. package/template/package.json +22 -25
  121. package/template/src/config.ts +11 -0
  122. package/template/src/content.config.ts +29 -16
  123. package/template/src/env.d.ts +0 -5
  124. package/index.js +0 -36
  125. package/template/tools/README.md +0 -169
  126. package/template/tools/ai-process.ts +0 -816
  127. package/template/tools/build-author-context.ts +0 -405
  128. package/template/tools/build-voice-profile.ts +0 -322
  129. package/template/tools/generate-author-profile.ts +0 -369
  130. package/template/tools/generate-cover.ts +0 -123
  131. package/template/tools/generate-og.ts +0 -280
  132. package/template/tools/generate-related.ts +0 -146
  133. package/template/tools/generate-tags.ts +0 -251
  134. package/template/tools/lib/ai-provider.ts +0 -240
  135. package/template/tools/lib/frontmatter.ts +0 -94
  136. package/template/tools/lib/markdown.ts +0 -40
  137. package/template/tools/lib/posts.ts +0 -89
  138. package/template/tools/lib/utils.ts +0 -138
  139. package/template/tools/lib/vectors.ts +0 -96
  140. package/template/tools/summarize.ts +0 -142
  141. package/template/tools/translate.ts +0 -60
  142. package/template/tools/vectorize.ts +0 -105
@@ -0,0 +1,332 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * AI 对话评估工具
4
+ *
5
+ * 基于黄金测试集自动评估 AI 对话质量。
6
+ * 支持本地 dev server 和远程部署两种模式。
7
+ *
8
+ * 用法:
9
+ * astro-minimax ai eval 评估所有用例
10
+ * astro-minimax ai eval --url=http://localhost:4321 指定 API 地址
11
+ * astro-minimax ai eval --category=no_answer 只评估特定分类
12
+ * astro-minimax ai eval --id=about-001 只评估特定用例
13
+ * astro-minimax ai eval --verbose 显示详细输出
14
+ * astro-minimax ai eval --json 输出 JSON 报告
15
+ */
16
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
17
+ import { join } from "node:path";
18
+ const DATA_DIR = join(process.cwd(), "datas");
19
+ const GOLD_SET_FILE = join(DATA_DIR, "eval", "gold-set.json");
20
+ const REPORT_FILE = join(DATA_DIR, "eval", "report.json");
21
+ const DEFAULT_API_URL = "http://localhost:4321";
22
+ const REQUEST_TIMEOUT_MS = 30_000;
23
+ function parseArgs() {
24
+ const args = process.argv.slice(2);
25
+ const flags = {
26
+ url: DEFAULT_API_URL,
27
+ category: null,
28
+ id: null,
29
+ verbose: false,
30
+ json: false,
31
+ };
32
+ for (const arg of args) {
33
+ if (arg.startsWith("--url="))
34
+ flags.url = arg.split("=")[1];
35
+ else if (arg.startsWith("--category="))
36
+ flags.category = arg.split("=")[1];
37
+ else if (arg.startsWith("--id="))
38
+ flags.id = arg.split("=")[1];
39
+ else if (arg === "--verbose")
40
+ flags.verbose = true;
41
+ else if (arg === "--json")
42
+ flags.json = true;
43
+ }
44
+ return flags;
45
+ }
46
+ // ─── API Client ────────────────────────────────────────────
47
+ async function sendChatRequest(apiUrl, question, lang) {
48
+ const url = `${apiUrl.replace(/\/$/, "")}/api/chat`;
49
+ const start = Date.now();
50
+ const controller = new AbortController();
51
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
52
+ try {
53
+ const res = await fetch(url, {
54
+ method: "POST",
55
+ headers: { "Content-Type": "application/json" },
56
+ body: JSON.stringify({
57
+ messages: [
58
+ {
59
+ id: `eval-${Date.now()}`,
60
+ role: "user",
61
+ parts: [{ type: "text", text: question }],
62
+ },
63
+ ],
64
+ context: { scope: "global" },
65
+ lang,
66
+ }),
67
+ signal: controller.signal,
68
+ });
69
+ if (!res.ok) {
70
+ throw new Error(`HTTP ${res.status}: ${await res.text()}`);
71
+ }
72
+ const text = await res.text();
73
+ const latency = Date.now() - start;
74
+ const responseText = extractTextFromSSE(text);
75
+ return { response: responseText, latency };
76
+ }
77
+ finally {
78
+ clearTimeout(timeout);
79
+ }
80
+ }
81
+ function extractTextFromSSE(raw) {
82
+ const parts = [];
83
+ for (const line of raw.split("\n")) {
84
+ if (!line.startsWith("0:"))
85
+ continue;
86
+ try {
87
+ const json = JSON.parse(line.slice(2));
88
+ if (typeof json === "string") {
89
+ parts.push(json);
90
+ }
91
+ }
92
+ catch {
93
+ const textMatch = line.match(/^0:"(.*)"/);
94
+ if (textMatch) {
95
+ parts.push(textMatch[1].replace(/\\n/g, "\n").replace(/\\"/g, '"'));
96
+ }
97
+ }
98
+ }
99
+ return parts.join("");
100
+ }
101
+ // ─── Evaluation Checks ────────────────────────────────────
102
+ function checkTopicCoverage(response, expectedTopics) {
103
+ if (!expectedTopics.length)
104
+ return { name: "topic_coverage", passed: true, detail: "No topics required" };
105
+ const lower = response.toLowerCase();
106
+ const found = expectedTopics.filter((t) => lower.includes(t.toLowerCase()));
107
+ const missing = expectedTopics.filter((t) => !lower.includes(t.toLowerCase()));
108
+ const passed = found.length >= Math.ceil(expectedTopics.length * 0.5);
109
+ return {
110
+ name: "topic_coverage",
111
+ passed,
112
+ detail: missing.length
113
+ ? `Missing: ${missing.join(", ")} | Found: ${found.join(", ")}`
114
+ : `All topics covered: ${found.join(", ")}`,
115
+ };
116
+ }
117
+ function checkForbiddenClaims(response, forbiddenClaims) {
118
+ if (!forbiddenClaims.length)
119
+ return { name: "forbidden_claims", passed: true, detail: "No forbidden claims" };
120
+ const lower = response.toLowerCase();
121
+ const violations = forbiddenClaims.filter((c) => lower.includes(c.toLowerCase()));
122
+ const passed = violations.length === 0;
123
+ return {
124
+ name: "forbidden_claims",
125
+ passed,
126
+ detail: violations.length
127
+ ? `Violations: ${violations.join(", ")}`
128
+ : "No violations",
129
+ };
130
+ }
131
+ function checkHasLinks(response, mustHaveLinks) {
132
+ if (!mustHaveLinks)
133
+ return { name: "has_links", passed: true, detail: "Links not required" };
134
+ const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
135
+ const links = [...response.matchAll(linkPattern)];
136
+ const passed = links.length > 0;
137
+ return {
138
+ name: "has_links",
139
+ passed,
140
+ detail: passed ? `Found ${links.length} link(s)` : "No Markdown links found",
141
+ };
142
+ }
143
+ function checkNotEmpty(response) {
144
+ const passed = response.trim().length > 10;
145
+ return {
146
+ name: "not_empty",
147
+ passed,
148
+ detail: passed ? `Response length: ${response.length}` : "Response too short or empty",
149
+ };
150
+ }
151
+ function checkAnswerMode(response, answerMode) {
152
+ const lower = response.toLowerCase();
153
+ switch (answerMode) {
154
+ case "unknown": {
155
+ const refusalPatterns = [
156
+ "未公开", "未在博客", "不提供", "不回答", "私人信息",
157
+ "not disclosed", "not public", "not available", "private",
158
+ ];
159
+ const hasRefusal = refusalPatterns.some((p) => lower.includes(p));
160
+ return {
161
+ name: "answer_mode",
162
+ passed: hasRefusal,
163
+ detail: hasRefusal ? "Correctly refused sensitive question" : "Should have refused but answered",
164
+ };
165
+ }
166
+ case "count": {
167
+ const hasNumber = /\d+/.test(response);
168
+ return {
169
+ name: "answer_mode",
170
+ passed: hasNumber,
171
+ detail: hasNumber ? "Contains numeric answer" : "Missing numeric answer for count question",
172
+ };
173
+ }
174
+ case "list": {
175
+ const hasList = /[-•*]\s|^\d+\./m.test(response) || /\[.*\]\(.*\)/g.test(response);
176
+ return {
177
+ name: "answer_mode",
178
+ passed: hasList,
179
+ detail: hasList ? "Contains list-style answer" : "Expected list format",
180
+ };
181
+ }
182
+ default:
183
+ return { name: "answer_mode", passed: true, detail: `Mode: ${answerMode}` };
184
+ }
185
+ }
186
+ function evaluateResponse(evalCase, response) {
187
+ return [
188
+ checkNotEmpty(response),
189
+ checkTopicCoverage(response, evalCase.expectedTopics),
190
+ checkForbiddenClaims(response, evalCase.forbiddenClaims),
191
+ checkHasLinks(response, evalCase.mustHaveLinks),
192
+ checkAnswerMode(response, evalCase.answerMode),
193
+ ];
194
+ }
195
+ // ─── Main ──────────────────────────────────────────────────
196
+ async function main() {
197
+ const flags = parseArgs();
198
+ let goldSet;
199
+ try {
200
+ const raw = await readFile(GOLD_SET_FILE, "utf-8");
201
+ goldSet = JSON.parse(raw);
202
+ }
203
+ catch {
204
+ console.error(`❌ 未找到评估数据集: ${GOLD_SET_FILE}`);
205
+ console.error(" 请先创建 datas/eval/gold-set.json");
206
+ process.exit(1);
207
+ }
208
+ let cases = goldSet.cases;
209
+ if (flags.category) {
210
+ cases = cases.filter((c) => c.category === flags.category);
211
+ }
212
+ if (flags.id) {
213
+ cases = cases.filter((c) => c.id === flags.id);
214
+ }
215
+ if (cases.length === 0) {
216
+ console.error("❌ 没有匹配的评估用例");
217
+ process.exit(1);
218
+ }
219
+ console.log("🧪 AI 对话评估");
220
+ console.log("━".repeat(60));
221
+ console.log(` API: ${flags.url}`);
222
+ console.log(` 用例: ${cases.length} / ${goldSet.cases.length}`);
223
+ console.log("");
224
+ const results = [];
225
+ let passedCount = 0;
226
+ let totalLatency = 0;
227
+ for (let i = 0; i < cases.length; i++) {
228
+ const evalCase = cases[i];
229
+ const progress = `[${i + 1}/${cases.length}]`;
230
+ process.stdout.write(`${progress} ${evalCase.id}: ${evalCase.question.slice(0, 40)}... `);
231
+ try {
232
+ const { response, latency } = await sendChatRequest(flags.url, evalCase.question, evalCase.lang);
233
+ totalLatency += latency;
234
+ const checks = evaluateResponse(evalCase, response);
235
+ const passedChecks = checks.filter((c) => c.passed).length;
236
+ const score = passedChecks;
237
+ const maxScore = checks.length;
238
+ const allPassed = checks.every((c) => c.passed);
239
+ if (allPassed)
240
+ passedCount++;
241
+ const result = {
242
+ caseId: evalCase.id,
243
+ category: evalCase.category,
244
+ question: evalCase.question,
245
+ passed: allPassed,
246
+ score,
247
+ maxScore,
248
+ response: response.slice(0, 500),
249
+ latency,
250
+ checks,
251
+ };
252
+ results.push(result);
253
+ const icon = allPassed ? "✅" : "❌";
254
+ console.log(`${icon} ${score}/${maxScore} (${latency}ms)`);
255
+ if (flags.verbose || !allPassed) {
256
+ for (const check of checks) {
257
+ const checkIcon = check.passed ? " ✓" : " ✗";
258
+ console.log(` ${checkIcon} ${check.name}: ${check.detail}`);
259
+ }
260
+ if (flags.verbose && response) {
261
+ console.log(` Response: ${response.slice(0, 200)}${response.length > 200 ? "..." : ""}`);
262
+ }
263
+ console.log("");
264
+ }
265
+ await new Promise((r) => setTimeout(r, 500));
266
+ }
267
+ catch (err) {
268
+ const errorMsg = err instanceof Error ? err.message : String(err);
269
+ console.log(`❌ ERROR: ${errorMsg}`);
270
+ results.push({
271
+ caseId: evalCase.id,
272
+ category: evalCase.category,
273
+ question: evalCase.question,
274
+ passed: false,
275
+ score: 0,
276
+ maxScore: 5,
277
+ response: "",
278
+ latency: 0,
279
+ checks: [],
280
+ error: errorMsg,
281
+ });
282
+ }
283
+ }
284
+ // ── Summary ─────────────────────────────────────────────
285
+ console.log("");
286
+ console.log("━".repeat(60));
287
+ console.log("📊 评估结果");
288
+ console.log("");
289
+ console.log(` 总用例: ${results.length}`);
290
+ console.log(` 通过: ${passedCount} ✅`);
291
+ console.log(` 失败: ${results.length - passedCount} ❌`);
292
+ console.log(` 通过率: ${((passedCount / results.length) * 100).toFixed(1)}%`);
293
+ console.log(` 平均延迟: ${results.length > 0 ? Math.round(totalLatency / results.length) : 0}ms`);
294
+ console.log("");
295
+ const byCategory = new Map();
296
+ for (const r of results) {
297
+ const cat = byCategory.get(r.category) ?? { passed: 0, total: 0 };
298
+ cat.total++;
299
+ if (r.passed)
300
+ cat.passed++;
301
+ byCategory.set(r.category, cat);
302
+ }
303
+ console.log(" 分类详情:");
304
+ for (const [cat, stats] of byCategory) {
305
+ const rate = ((stats.passed / stats.total) * 100).toFixed(0);
306
+ console.log(` ${cat}: ${stats.passed}/${stats.total} (${rate}%)`);
307
+ }
308
+ console.log("");
309
+ // ── Save Report ─────────────────────────────────────────
310
+ const report = {
311
+ generatedAt: new Date().toISOString(),
312
+ apiUrl: flags.url,
313
+ totalCases: results.length,
314
+ passed: passedCount,
315
+ failed: results.length - passedCount,
316
+ passRate: `${((passedCount / results.length) * 100).toFixed(1)}%`,
317
+ avgLatency: `${results.length > 0 ? Math.round(totalLatency / results.length) : 0}ms`,
318
+ results,
319
+ };
320
+ await mkdir(join(DATA_DIR, "eval"), { recursive: true });
321
+ await writeFile(REPORT_FILE, JSON.stringify(report, null, 2), "utf-8");
322
+ console.log(`📄 报告已保存: ${REPORT_FILE}`);
323
+ if (flags.json) {
324
+ console.log(JSON.stringify(report, null, 2));
325
+ }
326
+ process.exit(passedCount === results.length ? 0 : 1);
327
+ }
328
+ main().catch((err) => {
329
+ console.error("❌ 评估失败:", err.message);
330
+ process.exit(1);
331
+ });
332
+ //# sourceMappingURL=eval-ai-chat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eval-ai-chat.js","sourceRoot":"","sources":["../../src/tools/eval-ai-chat.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * 生成作者画像报告
4
+ *
5
+ * 基于作者上下文数据生成用于 About 页面的结构化简介。
6
+ * 支持 AI 生成和规则模板两种模式。
7
+ *
8
+ * 用法:
9
+ * pnpm profile:generate AI 生成画像报告
10
+ * pnpm profile:generate --no-ai 使用规则模板
11
+ * pnpm profile:generate --force 强制重新生成(不回退)
12
+ */
13
+ export {};
14
+ //# sourceMappingURL=generate-author-profile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-author-profile.d.ts","sourceRoot":"","sources":["../../src/tools/generate-author-profile.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG"}
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * 生成作者画像报告
4
+ *
5
+ * 基于作者上下文数据生成用于 About 页面的结构化简介。
6
+ * 支持 AI 生成和规则模板两种模式。
7
+ *
8
+ * 用法:
9
+ * pnpm profile:generate AI 生成画像报告
10
+ * pnpm profile:generate --no-ai 使用规则模板
11
+ * pnpm profile:generate --force 强制重新生成(不回退)
12
+ */
13
+ import { readdir, readFile } from "node:fs/promises";
14
+ import { join } from "node:path";
15
+ import { loadEnv, readJson, writeJson, truncate, parseCliArgs, DATA_DIR, BLOG_DIR, } from "./lib/utils.js";
16
+ import { stripMarkdown } from "./lib/markdown.js";
17
+ import { extractFrontmatter } from "./lib/frontmatter.js";
18
+ import { chatCompletion, hasAPIKey, getConfig } from "./lib/ai-provider.js";
19
+ // ─── 常量 ─────────────────────────────────────────────────────
20
+ const OUTPUT_REPORT = join(DATA_DIR, "author-profile-report.json");
21
+ const OUTPUT_CONTEXT = join(DATA_DIR, "author-profile-context.json");
22
+ const DEFAULT_SITE_URL = "https://example.com";
23
+ function parseArgs() {
24
+ return parseCliArgs({ force: false, noAI: false });
25
+ }
26
+ async function collectPosts(_siteUrl) {
27
+ const entries = await readdir(BLOG_DIR, { withFileTypes: true });
28
+ const aiSummaries = await readJson(join(DATA_DIR, "ai-summaries.json"), {
29
+ articles: {},
30
+ });
31
+ const posts = [];
32
+ for (const entry of entries) {
33
+ if (!entry.isDirectory() || entry.name.startsWith("_"))
34
+ continue;
35
+ const subDir = join(BLOG_DIR, entry.name);
36
+ const subEntries = await readdir(subDir, { withFileTypes: true });
37
+ for (const subEntry of subEntries) {
38
+ if (!subEntry.isFile() || !subEntry.name.endsWith(".md"))
39
+ continue;
40
+ const filePath = join(subDir, subEntry.name);
41
+ const raw = await readFile(filePath, "utf-8");
42
+ const fm = extractFrontmatter(raw);
43
+ if (!fm.data.title || fm.data.draft)
44
+ continue;
45
+ const relativePath = filePath.replace(BLOG_DIR + "/", "");
46
+ const id = relativePath.replace(/\.md$/, "");
47
+ const lang = relativePath.startsWith("en/") ? "en" : "zh";
48
+ const slug = id.split("/").slice(1).join("/");
49
+ const summaryEntry = aiSummaries.articles?.[id]?.data;
50
+ posts.push({
51
+ title: String(fm.data.title),
52
+ date: String(fm.data.pubDatetime),
53
+ lang,
54
+ category: String(fm.data.category || ""),
55
+ tags: Array.isArray(fm.data.tags) ? fm.data.tags : [],
56
+ description: String(fm.data.description || ""),
57
+ summary: summaryEntry?.summary || truncate(stripMarkdown(fm.body), 100),
58
+ keyPoints: summaryEntry?.keyPoints || [],
59
+ url: `/${lang}/posts/${slug}/`,
60
+ });
61
+ }
62
+ }
63
+ posts.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
64
+ return posts;
65
+ }
66
+ // ─── 上下文构建 ───────────────────────────────────────────────
67
+ function buildContext(posts, siteUrl) {
68
+ const selectedPosts = posts.slice(0, 12);
69
+ const zhPosts = posts.filter((p) => p.lang === "zh");
70
+ const enPosts = posts.filter((p) => p.lang === "en");
71
+ // 标签聚合
72
+ const tagCounts = new Map();
73
+ for (const post of posts) {
74
+ for (const tag of post.tags) {
75
+ tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1);
76
+ }
77
+ }
78
+ const topTags = [...tagCounts.entries()]
79
+ .sort((a, b) => b[1] - a[1])
80
+ .slice(0, 15)
81
+ .map(([tag]) => tag);
82
+ // 分类聚合
83
+ const categoryCounts = new Map();
84
+ for (const post of posts) {
85
+ if (post.category) {
86
+ categoryCounts.set(post.category, (categoryCounts.get(post.category) || 0) + 1);
87
+ }
88
+ }
89
+ const topCategories = [...categoryCounts.entries()]
90
+ .sort((a, b) => b[1] - a[1])
91
+ .slice(0, 5)
92
+ .map(([cat]) => cat);
93
+ return {
94
+ generatedAt: new Date().toISOString(),
95
+ siteUrl,
96
+ sourceInfo: {
97
+ totalPosts: posts.length,
98
+ zhPosts: zhPosts.length,
99
+ enPosts: enPosts.length,
100
+ selectedPosts: selectedPosts.length,
101
+ },
102
+ profile: {
103
+ name: process.env.SITE_AUTHOR || "博主",
104
+ siteUrl,
105
+ },
106
+ posts: selectedPosts.map((post) => ({
107
+ title: post.title,
108
+ date: post.date,
109
+ categories: [post.category].filter(Boolean),
110
+ tags: post.tags.slice(0, 5),
111
+ summary: post.summary,
112
+ keyPoints: post.keyPoints,
113
+ url: post.url,
114
+ })),
115
+ topTags,
116
+ topCategories,
117
+ contentStats: {
118
+ totalPosts: posts.length,
119
+ avgPostPerMonth: Math.round(posts.length / Math.max(1, calculateMonthsSpan(posts))),
120
+ },
121
+ };
122
+ }
123
+ function calculateMonthsSpan(posts) {
124
+ if (posts.length < 2)
125
+ return 1;
126
+ const latest = new Date(posts[0].date);
127
+ const earliest = new Date(posts[posts.length - 1].date);
128
+ return ((latest.getFullYear() - earliest.getFullYear()) * 12 +
129
+ (latest.getMonth() - earliest.getMonth()) +
130
+ 1);
131
+ }
132
+ // ─── AI 生成 ───────────────────────────────────────────────────
133
+ async function generateReportWithAI(context) {
134
+ if (!hasAPIKey()) {
135
+ throw new Error("未配置 AI API Key");
136
+ }
137
+ const config = getConfig();
138
+ const systemPrompt = `你是一位中文科技写作编辑。请基于给定上下文,以第三方视角生成作者画像 JSON。
139
+ 要求:
140
+ 1. 严格输出 JSON,不要输出 Markdown 或多余文本。
141
+ 2. 语气客观、克制、具体,不要夸张和空泛。
142
+ 3. 结论必须可由上下文支撑,避免编造。
143
+ 4. 文案使用中文,第三人称,不使用"我"。
144
+
145
+ 输出 schema:
146
+ {
147
+ "report": {
148
+ "hero": {"title":"AI 视角下的作者","summary":"...","intro":"..."},
149
+ "identities":[{"name":"...","description":"...","evidence":"..."}],
150
+ "strengths":[{"title":"...","points":["..."]}],
151
+ "styles":[{"trait":"...","description":"..."}],
152
+ "proofs":{
153
+ "posts":[{"title":"...","url":"...","reason":"...","date":"YYYY-MM-DD"}]
154
+ },
155
+ "disclaimer":"..."
156
+ }
157
+ }`;
158
+ const contextText = JSON.stringify(context, null, 2).slice(0, 25000);
159
+ const userPrompt = `上下文数据如下,请根据这些信息生成报告:\n${contextText}`;
160
+ const content = await chatCompletion([
161
+ { role: "system", content: systemPrompt },
162
+ { role: "user", content: userPrompt },
163
+ ], { maxTokens: 3000, responseFormat: "json" });
164
+ const parsed = JSON.parse(content);
165
+ return {
166
+ meta: {
167
+ lastUpdated: new Date().toISOString(),
168
+ model: config.model,
169
+ generatedBy: "ai",
170
+ },
171
+ report: parsed.report || parsed,
172
+ };
173
+ }
174
+ // ─── 规则模板生成 ─────────────────────────────────────────────
175
+ function buildRuleBasedReport(context) {
176
+ const posts = context.posts.slice(0, 5).map((post) => ({
177
+ title: post.title,
178
+ url: post.url,
179
+ reason: post.summary?.slice(0, 60) || "该文章体现了作者的写作风格。",
180
+ date: post.date,
181
+ }));
182
+ const topCategories = context.topCategories.slice(0, 3).join("、");
183
+ const authorName = context.profile.name;
184
+ return {
185
+ meta: {
186
+ lastUpdated: new Date().toISOString(),
187
+ model: "rule-based-template",
188
+ generatedBy: "rule-based",
189
+ },
190
+ report: {
191
+ hero: {
192
+ title: `AI 视角下的 ${authorName}`,
193
+ summary: `一位专注于${topCategories}领域的博主,持续输出高质量内容。`,
194
+ intro: `博客已发布 ${context.sourceInfo.totalPosts} 篇文章,涵盖 ${context.topTags.length} 个主题标签。`,
195
+ },
196
+ identities: [
197
+ {
198
+ name: "技术博主",
199
+ description: "持续分享技术经验与实践心得。",
200
+ evidence: `已发布 ${context.sourceInfo.totalPosts} 篇文章。`,
201
+ },
202
+ {
203
+ name: "内容创作者",
204
+ description: "注重内容质量与读者体验。",
205
+ evidence: posts[0]
206
+ ? `近期文章《${posts[0].title}》体现了专业的写作风格。`
207
+ : "",
208
+ },
209
+ ],
210
+ strengths: [
211
+ {
212
+ title: "内容深度",
213
+ points: [
214
+ "文章结构清晰,逻辑性强",
215
+ "注重实践,配合代码示例",
216
+ "持续更新,覆盖多个技术领域",
217
+ ],
218
+ },
219
+ {
220
+ title: "表达风格",
221
+ points: ["语言简洁明了", "注重可读性", "善于总结提炼"],
222
+ },
223
+ ],
224
+ styles: [
225
+ {
226
+ trait: "技术导向",
227
+ description: "专注于技术内容的深度讲解与实践分享。",
228
+ },
229
+ {
230
+ trait: "结构化表达",
231
+ description: "文章结构清晰,便于读者理解和学习。",
232
+ },
233
+ ],
234
+ proofs: { posts },
235
+ disclaimer: "该页面由 AI 归纳与规则模板联合生成,旨在帮助访客快速建立认知,可能存在概括偏差,请以原始文章信息为准。",
236
+ },
237
+ };
238
+ }
239
+ // ─── 主流程 ───────────────────────────────────────────────────
240
+ async function main() {
241
+ const args = parseArgs();
242
+ await loadEnv();
243
+ console.log("👤 生成作者画像报告");
244
+ console.log("━".repeat(50));
245
+ const siteUrl = process.env.SITE_URL || DEFAULT_SITE_URL;
246
+ console.log(` 站点 URL: ${siteUrl}`);
247
+ console.log(` 输出目录: ${DATA_DIR}`);
248
+ console.log("");
249
+ // 收集数据
250
+ console.log("📂 收集文章数据...");
251
+ const posts = await collectPosts(siteUrl);
252
+ console.log(` 找到 ${posts.length} 篇文章`);
253
+ const context = buildContext(posts, siteUrl);
254
+ await writeJson(OUTPUT_CONTEXT, context);
255
+ console.log(` 上下文已保存: ${OUTPUT_CONTEXT}`);
256
+ // 生成报告
257
+ let report;
258
+ if (args.noAI) {
259
+ console.log("\n📝 使用规则模板生成...");
260
+ report = buildRuleBasedReport(context);
261
+ }
262
+ else {
263
+ console.log("\n🤖 使用 AI 生成...");
264
+ try {
265
+ report = await generateReportWithAI(context);
266
+ console.log(" ✅ AI 生成成功");
267
+ }
268
+ catch (error) {
269
+ const err = error;
270
+ if (!args.force) {
271
+ console.warn(` ⚠️ AI 生成失败: ${err.message}`);
272
+ console.log(" 📝 回退使用规则模板...");
273
+ report = buildRuleBasedReport(context);
274
+ }
275
+ else {
276
+ throw error;
277
+ }
278
+ }
279
+ }
280
+ await writeJson(OUTPUT_REPORT, report);
281
+ console.log("\n✅ 画像报告生成完成");
282
+ console.log(`📄 报告文件: ${OUTPUT_REPORT}`);
283
+ console.log(`🧩 上下文文件: ${OUTPUT_CONTEXT}`);
284
+ }
285
+ main().catch((error) => {
286
+ console.error("❌ 生成失败:", error.message);
287
+ process.exit(1);
288
+ });
289
+ //# sourceMappingURL=generate-author-profile.js.map