Hexo 博客 AI 摘要:从 TianliGPT 迁移到 DeepSeek 完整教程

Hexo 博客 AI 摘要:从 TianliGPT 迁移到 DeepSeek 完整教程

一、迁移说明

本文适用于 Hexo AnZhiYu 主题的「文章顶部 AI 摘要」功能,核心是将原有的 TianliGPT 服务替换为 DeepSeek 大模型 API,保留原有交互逻辑(打字机动画、推荐文章、模式切换等),仅修改核心依赖和配置,适配主题默认文件路径。

二、前置准备

  1. 已搭建 Hexo 博客环境(Node.js + npm 正常运行);
  2. 拥有 DeepSeek 账号及 API Key(从 DeepSeek 官网 个人中心获取);
  3. 备份以下文件(避免修改出错):
    • G:\Blog\config.anzhiyu.yml
    • G:\Blog\themes\anzhiyu\layout\includes\anzhiyu\ai-info.pug
    • G:\Blog\themes\anzhiyu\source\js\anzhiyu\ai_abstract.js
    • G:\Blog\themes\anzhiyu\sw-rules.js

三、核心修改步骤(共 4 步,含准确文件路径)

步骤 1:修改主题配置文件(config.anzhiyu.yml)

文件路径G:\Blog\config.anzhiyu.yml

找到 post_head_ai_description 配置项,按以下要求修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
post_head_ai_description:
enable: true # 保持启用文章顶部AI摘要
gptName: AnZhiYu # 本地模式显示的AI名称(可自定义)
mode: deepseek # 模式改为 deepseek(替换原 tianli)
switchBtn: false # 是否显示模式切换按钮(按需开启)
btnLink: https://afdian.net/item/886a79d4db6711eda42a52540025c377 # 保留原有按钮链接
randomNum: 3 # 最大随机字符数波动(不变)
basicWordCount: 1000 # 最低获取字符数(不变)

# 新增 DeepSeek 核心配置
deepseekKey: "sk-xxxxxxxx" # 替换为你的 DeepSeek API Key(必填)
model: "deepseek-chat" # 可选配置,默认使用 deepseek-chat(摘要推荐此模型)

# 旧 TianliGPT 配置(可注释或删除,不再生效)
# key: xxxx # 原 Tianli 密钥
# Referer: https://xx.xx/ # 原 Tianli 来源配置

步骤 2:修改 PUG 模板文件(AI 摘要 UI 结构)

文件路径G:\Blog\themes\anzhiyu\layout\includes\anzhiyu\ai-info.pug

仅修改模式判断和显示文本,不改动原有格式和结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- let pageFillDescription = get_page_fill_description()
- let gptName = theme.post_head_ai_description.gptName
- let mode = theme.post_head_ai_description.mode
- let switchBtn = theme.post_head_ai_description.switchBtn
if (pageFillDescription && page.ai)
.post-ai-description
.ai-title
i.anzhiyufont.anzhiyu-icon-bilibili
.ai-title-text AI-摘要
if (switchBtn)
#ai-Toggle 切换
i.anzhiyufont.anzhiyu-icon-arrow-rotate-right
i.anzhiyufont.anzhiyu-icon-circle-dot(title="朗读摘要")
#ai-tag
if mode == "deepseek" # 原 mode == "tianli" 改为 deepseek
= "DeepSeek AI" # 显示文本替换为 DeepSeek AI
else
= gptName + " GPT"
.ai-explanation AI初始化中...
.ai-btn-box
.ai-btn-item 介绍自己 🙈
.ai-btn-item 生成本文简介 👋
.ai-btn-item 推荐相关文章 📖
.ai-btn-item 前往主页 🏠
.ai-btn-item#go-tianli-blog 前往爱发电购买
script(data-pjax src=url_for(theme.asset.ai_abstract_js))

步骤 3:修改 AI 摘要核心 JS 文件(逻辑替换)

文件路径G:\Blog\themes\anzhiyu\source\js\anzhiyu\ai_abstract.js

替换 TianliGPT 相关逻辑为 DeepSeek API 调用,完整修改后代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
(function () {
const {
randomNum,
basicWordCount,
btnLink,
gptName,
switchBtn,
mode: initialMode,
deepseekKey, // 读取 DeepSeek API Key
model = "deepseek-chat" // 默认模型(可通过配置修改)
} = GLOBAL_CONFIG.postHeadAiDescription;

const { title, postAI, pageFillDescription } = GLOBAL_CONFIG_SITE;

let lastAiRandomIndex = -1;
let animationRunning = true;
let mode = initialMode;
let refreshNum = 0;
let prevParam;
let audio = null;
let isPaused = false;
let summaryID = null;

const post_ai = document.querySelector(".post-ai-description");
const aiTitleRefreshIcon = post_ai.querySelector(".ai-title .anzhiyufont.anzhiyu-icon-arrow-rotate-right");
let aiReadAloudIcon = post_ai.querySelector(".anzhiyu-icon-circle-dot");
const explanation = post_ai.querySelector(".ai-explanation");

let aiStr = "";
let aiStrLength = "";
let delayInit = 600;
let indexI = 0;
let indexJ = 0;
let timeouts = [];
let elapsed = 0;

const observer = createIntersectionObserver();
const aiFunctions = [introduce, aiTitleRefreshIconClick, aiRecommend, aiGoHome];

const aiBtnList = post_ai.querySelectorAll(".ai-btn-item");
const filteredHeadings = Array.from(aiBtnList).filter(heading => heading.id !== "go-tianli-blog");
filteredHeadings.forEach((item, index) => {
item.addEventListener("click", () => {
aiFunctions[index]();
});
});

document.getElementById("ai-tag").addEventListener("click", onAiTagClick);
aiTitleRefreshIcon.addEventListener("click", onAiTitleRefreshIconClick);
document.getElementById("go-tianli-blog").addEventListener("click", () => {
window.open(btnLink, "_blank");
});
aiReadAloudIcon.addEventListener("click", readAloud);

// 禁用朗读功能(DeepSeek 暂不支持文本转语音)
async function readAloud() {
anzhiyu.snackbarShow("DeepSeek 模式暂不支持朗读摘要");
return;
}

if (switchBtn) {
document.getElementById("ai-Toggle").addEventListener("click", changeShowMode);
}

aiAbstract();
showAiBtn();

function createIntersectionObserver() {
return new IntersectionObserver(
entries => {
let isVisible = entries[0].isIntersecting;
animationRunning = isVisible;
if (animationRunning) {
delayInit = indexI === 0 ? 200 : 20;
timeouts[1] = setTimeout(() => {
if (indexJ) {
indexI = 0;
indexJ = 0;
}
if (indexI === 0) {
explanation.innerHTML = aiStr.charAt(0);
}
requestAnimationFrame(animate);
}, delayInit);
}
},
{ threshold: 0 }
);
}

function animate(timestamp) {
if (!animationRunning) {
return;
}
if (!animate.start) animate.start = timestamp;
elapsed = timestamp - animate.start;
if (elapsed >= 20) {
animate.start = timestamp;
if (indexI < aiStrLength - 1) {
let char = aiStr.charAt(indexI + 1);
let delay = /[,.,。!?!?]/.test(char) ? 150 : 20;
if (explanation.firstElementChild) {
explanation.removeChild(explanation.firstElementChild);
}
explanation.innerHTML += char;
let div = document.createElement("div");
div.className = "ai-cursor";
explanation.appendChild(div);
indexI++;
if (delay === 150) {
post_ai.querySelector(".ai-explanation .ai-cursor").style.opacity = "0.2";
}
if (indexI === aiStrLength - 1) {
observer.disconnect();
explanation.removeChild(explanation.firstElementChild);
}
timeouts[0] = setTimeout(() => {
requestAnimationFrame(animate);
}, delay);
}
} else {
requestAnimationFrame(animate);
}
}

function clearTimeouts() {
if (timeouts.length) {
timeouts.forEach(item => {
if (item) {
clearTimeout(item);
}
});
}
}

function startAI(str, df = true) {
indexI = 0;
indexJ = 1;
clearTimeouts();
animationRunning = false;
elapsed = 0;
observer.disconnect();
explanation.innerHTML = df ? "生成中. . ." : "请等待. . .";
aiStr = str;
aiStrLength = aiStr.length;
observer.observe(post_ai);
}

// 摘要生成入口(切换为 deepseek 模式判断)
async function aiAbstract(num = basicWordCount) {
if (mode === "deepseek") {
await aiAbstractDeepSeek(num);
} else {
aiAbstractLocal();
}
}

// 新增:调用 DeepSeek API 生成摘要
async function aiAbstractDeepSeek(num) {
indexI = 0;
indexJ = 1;
clearTimeouts();
animationRunning = false;
elapsed = 0;
observer.disconnect();

num = Math.max(10, Math.min(2000, num));
const truncateDescription = (title + pageFillDescription).trim().substring(0, num);

// DeepSeek API 请求配置(符合官方接口规范)
const requestBody = {
model: model,
messages: [
{ role: "system", content: "你是专业的文章摘要助手,用简洁、准确的语言总结文章核心内容,字数控制在300字内" },
{ role: "user", content: truncateDescription }
],
max_tokens: 300, // 最大生成长度
temperature: 0.7 // 随机性(0-1,越低越精准)
};

const requestOptions = {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${deepseekKey}` // DeepSeek 密钥认证(必填)
},
body: JSON.stringify(requestBody)
};

try {
// 加载动画("生成中..." 循环打点)
let animationInterval = setInterval(() => {
const animationText = "生成中" + ".".repeat(indexJ);
explanation.innerHTML = animationText;
indexJ = (indexJ % 3) + 1;
}, 500);

// 调用 DeepSeek API
const response = await fetch("https://api.deepseek.com/v1/chat/completions", requestOptions);
const result = await response.json();

// 错误处理(非 200 状态码)
if (response.status !== 200) {
throw new Error(result.error?.message || `API请求失败,状态码:${response.status}`);
}

// 提取摘要文本
const summary = result.choices[0].message.content.trim();
summaryID = Date.now(); // 用时间戳作为临时摘要ID

// 显示摘要(触发打字机动画)
setTimeout(() => {
aiTitleRefreshIcon.style.opacity = "1";
}, 300);
if (summary) {
startAI(summary);
} else {
startAI("摘要生成失败,请点击刷新重试!");
}
clearInterval(animationInterval);
} catch (error) {
console.error("DeepSeek 摘要生成错误:", error);
explanation.innerHTML = `摘要生成异常:${error.message}`;
}
}

// 本地模式逻辑(不变,兼容原有预设摘要)
function aiAbstractLocal() {
const strArr = postAI.split(",").map(item => item.trim());
if (strArr.length !== 1) {
let randomIndex = Math.floor(Math.random() * strArr.length);
while (randomIndex === lastAiRandomIndex) {
randomIndex = Math.floor(Math.random() * strArr.length);
}
lastAiRandomIndex = randomIndex;
startAI(strArr[randomIndex]);
} else {
startAI(strArr[0]);
}
setTimeout(() => {
aiTitleRefreshIcon.style.opacity = "1";
}, 600);
}

// 推荐文章逻辑(不变,保持原有功能)
function aiRecommend() {
indexI = 0;
indexJ = 1;
clearTimeouts();
animationRunning = false;
elapsed = 0;
explanation.innerHTML = "生成中. . .";
aiStr = "";
aiStrLength = "";
observer.disconnect();
timeouts[2] = setTimeout(() => {
explanation.innerHTML = recommendList();
}, 600);
}

function recommendList() {
let thumbnail = document.querySelectorAll(".relatedPosts-list a");
if (!thumbnail.length) {
const cardRecentPost = document.querySelector(".card-widget.card-recent-post");
if (!cardRecentPost) return "";

thumbnail = cardRecentPost.querySelectorAll(".aside-list-item a");

let list = "";
for (let i = 0; i < thumbnail.length; i++) {
const item = thumbnail[i];
list += `<div class="ai-recommend-item"><span class="index">${
i + 1
}:</span><a href="javascript:;" onclick="pjax.loadUrl('${item.href}')" title="${
item.title
}" data-pjax-state="">${item.title}</a></div>`;
}

return `很抱歉,无法找到类似的文章,你也可以看看本站最新发布的文章:<br /><div class="ai-recommend">${list}</div>`;
}

let list = "";
for (let i = 0; i < thumbnail.length; i++) {
const item = thumbnail[i];
list += `<div class="ai-recommend-item"><span>推荐${
i + 1
}:</span><a href="javascript:;" onclick="pjax.loadUrl('${item.href}')" title="${
item.title
}" data-pjax-state="">${item.title}</a></div>`;
}

return `推荐文章:<br /><div class="ai-recommend">${list}</div>`;
}

// 前往主页逻辑(不变)
function aiGoHome() {
startAI("正在前往博客主页...", false);
timeouts[2] = setTimeout(() => {
if (window.pjax) {
pjax.loadUrl("/");
} else {
location.href = location.origin;
}
}, 1000);
}

// AI 自我介绍(更新为 DeepSeek 相关文本)
function introduce() {
if (mode == "deepseek") {
startAI("我是文章辅助AI: DeepSeek AI,点击下方的按钮,让我生成本文简介、推荐相关文章等。");
} else {
startAI(`我是文章辅助AI: ${gptName} GPT,点击下方的按钮,让我生成本文简介、推荐相关文章等。`);
}
}

function aiTitleRefreshIconClick() {
aiTitleRefreshIcon.click();
}

// AI 标签点击逻辑(更新文本)
function onAiTagClick() {
if (mode === "deepseek") {
post_ai.querySelectorAll(".ai-btn-item").forEach(item => (item.style.display = "none"));
document.getElementById("go-tianli-blog").style.display = "block";
startAI(
"你好,我是DeepSeek AI摘要助手,是一个基于DeepSeek大模型的生成式AI。我在这里只负责摘要的预生成和显示,你无法与我直接沟通。"
);
} else {
post_ai.querySelectorAll(".ai-btn-item").forEach(item => (item.style.display = "block"));
document.getElementById("go-tianli-blog").style.display = "none";
startAI(
`你好,我是本站摘要生成助理${gptName} GPT,是一个基于GPT-4的生成式AI。我在这里只负责摘要的预生成和显示,你无法与我直接沟通。`
);
}
}

// 刷新摘要逻辑(不变)
function onAiTitleRefreshIconClick() {
const truncateDescription = (title + pageFillDescription).trim().substring(0, basicWordCount);

aiTitleRefreshIcon.style.opacity = "0.2";
aiTitleRefreshIcon.style.transitionDuration = "0.3s";
aiTitleRefreshIcon.style.transform = "rotate(" + 360 * refreshNum + "deg)";
if (truncateDescription.length <= basicWordCount) {
let param = truncateDescription.length - Math.floor(Math.random() * randomNum);
while (param === prevParam || truncateDescription.length - param === prevParam) {
param = truncateDescription.length - Math.floor(Math.random() * randomNum);
}
prevParam = param;
aiAbstract(param);
} else {
let value = Math.floor(Math.random() * randomNum) + basicWordCount;
while (value === prevParam || truncateDescription.length - value === prevParam) {
value = Math.floor(Math.random() * randomNum) + basicWordCount;
}
aiAbstract(value);
}
refreshNum++;
}

// 模式切换逻辑(改为 deepseek ↔ local)
function changeShowMode() {
mode = mode === "deepseek" ? "local" : "deepseek";
if (mode === "deepseek") {
document.getElementById("ai-tag").innerHTML = "DeepSeek AI";
aiReadAloudIcon.style.opacity = "0"; // 隐藏朗读图标(暂不支持)
aiReadAloudIcon.style.cursor = "auto";
} else {
aiReadAloudIcon.style.opacity = "0";
aiReadAloudIcon.style.cursor = "auto";
if ((document.getElementById("go-tianli-blog").style.display = "block")) {
document.querySelectorAll(".ai-btn-item").forEach(item => (item.style.display = "block"));
document.getElementById("go-tianli-blog").style.display = "none";
}
document.getElementById("ai-tag").innerHTML = gptName + " GPT";
}
aiAbstract();
}

// 显示 AI 标签(更新为 DeepSeek 文本)
function showAiBtn() {
if (mode === "deepseek") {
document.getElementById("ai-tag").innerHTML = "DeepSeek AI";
} else {
document.getElementById("ai-tag").innerHTML = gptName + " GPT";
}
}
})();

步骤 4:修改 Service Worker 配置(忽略 DeepSeek API)

文件路径G:\Blog\themes\anzhiyu\sw-rules.js

添加规则让 SW 忽略 DeepSeek API 请求,避免缓存动态摘要数据:

1
2
3
4
5
6
7
/**
* 跳过处理番剧封面和 DeepSeek API 请求
* 核心修改:新增 DeepSeek API 忽略规则
*/
module.exports.skipRequest = request =>
request.url.startsWith('https://i0.hdslb.com') ||
request.url.startsWith('https://api.deepseek.com'); // 新增:忽略 DeepSeek API 请求

四、测试验证

  1. 清理 Hexo 缓存并启动本地服务:
    1
    hexo clean && hexo s
  2. 访问任意文章页,验证以下功能:
    • AI 标签显示「DeepSeek AI」;
    • 摘要自动生成(打字机动画正常播放);
    • 点击「刷新图标」可重新生成不同摘要;
    • 点击「推荐相关文章」可显示关联/最新文章列表;
    • 开发者工具(F12)控制台无报错。

五、常见问题排查

  1. 摘要生成失败
    • 检查 config.anzhiyu.ymldeepseekKey 是否正确(无多余空格、符号);
    • 确认网络可访问 https://api.deepseek.com(避免墙内网络限制);
    • 查看控制台报错:若提示「401 Unauthorized」,则密钥错误;若提示「403 Forbidden」,则可能是 IP 限制。
  2. UI 显示异常(如标签文本错误)
    • 检查 ai-info.pug 中模式判断是否为 mode == "deepseek"
    • 确认 ai_abstract.jsshowAiBtn 函数的文本更新正确。
  3. 模式切换无效
    • 确保 config.anzhiyu.ymlswitchBtn 配置为 true
    • 检查 ai_abstract.jschangeShowMode 函数的模式切换逻辑(deepseek ↔ local)。

六、注意事项

  1. DeepSeek API 按调用次数计费,建议合理控制 basicWordCount 和刷新频率,避免过度消耗;
  2. 若需启用朗读功能,需额外集成 DeepSeek TTS API(需单独申请并修改 readAloud 函数);
  3. 模型选择:deepseek-chat 适用于文本摘要,deepseek-coder 适用于代码相关场景,不可混用;
  4. 若后续主题更新覆盖修改后的文件,需重新按本教程配置。