最近在看别人的博客的时候发现AI摘要挺方便,遂自己研究了一下hexo的命令以及函数的使用,决定自己写一个小脚本来实现。

于是我想实现在文章最前面生成摘要,有三种情况:

  1. 不生成AI摘要
  2. 手动填写摘要,并显示成手动编写摘要
  3. 自动生成的AI摘要

话不多说,开始敲代码QAQ:

准备工作

安装必要的库:

1
npm install axios striptags gray-matter

axios库用来发送网络请求 gray-matter库用来解析 Markdown 的 Front-matter striptags库用来把 HTML 转为纯文本传给 AI

编写hexo脚本

在你的hexo博客的根目录下创建一个scripts文件夹,用来存放有关hexo的脚本:

1
sudo mkdir scripts

进入该文件夹,创建脚本文件:

1
2
cd scripts
vim ai_summary

编写脚本:

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
const axios = require('axios');
const fs = require('fs');
const matter = require('gray-matter');
const striptags = require('striptags');

// 配置有关你所使用的AI模型
const API_URL = 'xxx'; // 你所接入的AI模型地址
const API_KEY = 'xxx'; // 你的 api-Key
const MODEL = 'xxx';

// 在文章 HTML 渲染完成后触发脚本
hexo.extend.filter.register('before_post_render', async function(data) {
// 1. 如果页面类型不是post(文章),跳过AI生成
if (data.layout !== 'post') return data;
// 2.如果在文章的front-matter中配置参数ai_summary为false,表示这篇文章不需要AI摘要,跳过AI生成
if (data.ai_summary === false) return data;

// 3. 如果ai_summary已经是一个字符串(手动填写的),直接跳过AI生成
if (typeof data.ai_summary === 'string' && data.ai_summary.trim().length > 0) {
return data;
}

// 获取文章文件的物理路径
const postPath = data.full_source;
if (!fs.existsSync(postPath)) return data;

// 清理正文:去掉HTML标签,去掉换行(同时截取1500字节省tokens,可以自定义)
const cleanText = striptags(data.content)
.replace(/\n/g, ' ')
.substring(0, 1500);

console.log(`[AI_Summary] 正在自动生成摘要: ${data.title}...`);

try {
// 这里可以自定义AI请求过程,包括自定义提示词等,这里举一个例子
const response = await axios.post(API_URL, {
model: MODEL,
messages: [
{ role: "system", content: "你是一个博客摘要助手。请根据提供的文章内容,写一段简练的摘要。要求:100字以内,不含换行符,语气专业。" },
{ role: "user", content: cleanText }
],
temperature: 0.7
}, {
headers: { 'Authorization': `Bearer ${API_KEY}` },
timeout: 30000 // 30秒超时
});

// 将生成的摘要挂载到文章data对象上
const result = response.data.choices[0].message.content.trim();

// 读取原始文件内容
const file = fs.readFileSync(postPath, 'utf8');
// 使用gray-matter解析
const { data: frontMatter, content } = matter(file);

// 将相应摘要配置插入到 文章的Front-matter中
frontMatter.ai_summary = result;
frontMatter.ai_generated = true;

// 重新生成带摘要的Markdown文件
const updatedFile = matter.stringify(content, frontMatter);
fs.writeFileSync(postPath, updatedFile, 'utf8');

console.log(`[AI_Summary] 生成成功: ${data.title}`);


} catch (err) {
console.error(`[AI_Summary] 生成失败: ${err.message}`);
data.ai_summary = ""; // 失败则设为空,防止模板报错
}

return data;
});

修改模板文件

注意:修改模板文件可能会导致模板主题更新时被覆盖,请注意及时保存修改过后的文件

我是用的是Butterfly框架,找到对应的模板文件夹(themes)修改下面的pug模板:

1
vim themes/butterfly/layout/post.pug

找到下面这段:

1
2
3
4
5
6
block content
#post
if top_img === false
include includes/header/post-info.pug

article#article-container.container.post-content

修改成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
block content
#post
if top_img === false
include includes/header/post-info.pug

if page.ai_summary !== false && page.ai_summary
.ai-summary-card
.ai-title
if page.ai_generated
i.fas.fa-robot
span AI 摘要 由xxx 强力驱动
else
i.fas.fa-user-edit
span AI 摘要 由xxx的大脑强力驱动(?)
.ai-content= page.ai_summary

article#article-container.container.post-content

修改AI摘要显示样式

在博客根目录下面创建自定义css文件夹:

1
sudo mkdir -p source/css

创建自定义css文件:

1
sudo vim ai_summary.css

以下为我的css格式,可以在这里任意的修改css代码美化你自己的样式:

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
/* 容器基础样式 */
.ai-summary-card {
position: relative;
background: rgba(255, 255, 255, 0.9);
border-radius: 12px;
padding: 15px 20px;
margin-bottom: 30px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}

/* 顶部流光条 (伪元素实现) */
.ai-summary-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: linear-gradient(90deg, #49b1f5, #ff0000, #49b1f5);
background-size: 200% 100%;
animation: streamLight 3s linear infinite;
}

/* 标题样式 */
.ai-title {
font-weight: bold;
color: #49b1f5;
margin-bottom: 10px;
display: flex;
align-items: center;
}

.ai-title i {
margin-right: 8px;
font-size: 1.2em;
animation: shake-robot 2s infinite;
}

/* 内容文字 */
.ai-content {
font-size: 15px;
line-height: 1.6;
color: #444;
text-align: justify;
}

/* 手动编辑的标签颜色可以稍微变一下,比如变成紫色或绿色 */
.ai-summary-card .fa-user-edit {
color: #2ecc71; /* 绿色 */
}

/* 动画定义 */
@keyframes streamLight {
0% { background-position: 100% 0; }
100% { background-position: -100% 0; }
}

@keyframes shake-robot {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(10deg); }
75% { transform: rotate(-10deg); }
}

/* 暗黑模式适配 */
[data-theme="dark"] .ai-summary-card {
background: #232323;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
[data-theme="dark"] .ai-content {
color: #ccc;
}

别忘了在配置文件中导入自定义css文件!!!

ps:用于展示的容器结构从pug文件中可以看出

至此,我们可以通过三种方式控制AI摘要的生成:

  1. 自动生成AI摘要:

    1
    2
    3
    ---
    title: 文章标题
    ---

    AI摘要总结后:

    1
    2
    3
    4
    5
    ---
    title: 文章标题
    ai_summary: "AI生成的摘要"
    ai_generated: true
    ---
  2. 手动写摘要:

    1
    2
    3
    4
    ---
    title: 文章标题
    ai_summary: 手写摘要
    ---
  3. 这篇文章不想显示摘要

    1
    2
    3
    4
    ---
    title: 秘密文章
    ai_summary: false
    ---

注意

  1. 如果要手动修改.md文件将ai_summary改成false后,可以删除生成的ai_generated: true字段,保持文件的整洁
  2. 想重新生成AI摘要(修改过文章或者不满意AI生成的摘要),或从其他方式切换到AI摘要生成,直接将ai_summary和ai_generated字段删除即可
  3. 无论如何切换方式,注意每一个方法所对应的Front-matter的配置即可
  4. 如果你使用一些插件,例如永久化文章链接(addrlink),由于hexo的过滤器竞争,可能会导致请求两次API,第一次API写入错误,可以修改如下位置:
    1
    2
    3
    hexo.extend.filter.register('before_post_render', async function(data) {
    xxxxxx
    }, 15);
    这样降低该脚本的执行优先级,保证仅请求1次

本脚本可能存在一些不太成熟的代码,你也可以寻找更成熟的AI摘要生成插件,防止框架升级后修改的模板文件被覆盖。