Hexo AI 摘要插件(自用)

这是一个 Hexo 插件,用于自动生成文章的摘要。支持腾讯混元、OpenAI 或任何兼容 OpenAI 协议的模型接口,支持并发处理、自定义摘要字段、摘要覆盖控制等功能。

该插件不能保证不造成错误问题以及混乱,使用前请注意备份!此类的文章摘要也可以本地运行,只需合适的js文件,博客根目录运行node ai.js也可以达到AI摘要的效果

功能特点

安装

1
npm install hexo-ai-summary-anxiaowai --save

配置项(添加到 _config.yml 或主题配置文件中)

token可以自行申请免费的腾讯hunyuan模型

1
2
3
4
5
6
7
8
9
10
11
12
13
aisummary:
enable: true # 是否启用插件
api: https://api.hunyuan.cloud.tencent.com/v1/chat/completions # OpenAI 或腾讯云的 Hunyuan 模型
token: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # OpenAI 或兼容模型的密钥
model: hunyuan-standard # 使用模型名称
summary_field: summary # 摘要字段
cover_all: false # 是否启用全部覆盖(既重新生成)
concurrency: 2 # 并发处理数
max_input_length: 3000 # 输入文本长度限制(腾讯推荐2000字以内)
prompt: "请用简体中文生成一段简洁的摘要,要求:1.长度在80-120字之间 2.包含文章核心观点 3.输出内容开头为“这里小歪AI,这篇文章” 4.不要包含代码和公式"
max_tokens: 120 # 混元模型建议稍大的token限制
temperature: 0.5 # 创造性稍高的温度值(0.1~2.0)
timeout: 10000 # 腾讯API建议稍长的超时时间

指定文章禁用AI摘要

1
2
3
4
---
title: xxxxxxxxxxx
summary: false
---

修改文章模板(显示内容自行修改)

进入hexo\themes\butterfly\layout\post.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
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
extends includes/layout.pug

block content
#post
if top_img === false
include includes/header/post-info.pug

+ //- AI 摘要卡片(含延迟动画)
+ if page.summary && page.summary !== false
+ .ai-summary-wrapper
+ .ai-summary-header
+ .mac-window-dots
+ span.dot.red
+ span.dot.yellow
+ span.dot.green
+ .ai-source 安小歪のAI摘要
+ .ai-summary-body
+ .ai-loading 正在思考中ing…
+ .ai-text(data-delay="2000" data-text=page.summary)
+ .ai-footer
+ .ai-tip 此内容根据文章生成,并经过人工审核,仅用于文章内容的解释与总结
+ .ai-model hunyuan-standard

article#article-container.container.post-content
if theme.noticeOutdate.enable && page.noticeOutdate !== false
include includes/post/outdate-notice.pug
else
!=page.content
include includes/post/post-copyright.pug
.tag_share
if (page.tags.length > 0 && theme.post_meta.post.tags)
.post-meta__tag-list
each item, index in page.tags.data
a(href=url_for(item.path)).post-meta__tags #[=item.name]
include includes/third-party/share/index.pug

if theme.reward.enable && theme.reward.QR_code
!=partial('includes/post/reward', {}, {cache: true})

//- ad
if theme.ad && theme.ad.post
.ads-wrap!=theme.ad.post

if theme.post_pagination
include includes/pagination.pug
if theme.related_post && theme.related_post.enable
!= related_posts(page,site.posts)

if page.comments !== false && theme.comments.use
- var commentsJsLoad = true
!=partial('includes/third-party/comments/index', {}, {cache: true})

添加自定义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
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
/* =====AI摘要基础样式===== */
.ai-summary-wrapper {
position: relative;
margin-bottom: 1.5rem;
padding: 1rem 1.2rem;
border-radius: 8px;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
font-size: 0.95rem;
line-height: 1.6;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
transition: all 0.4s ease; /* 过渡动画 */
}

.ai-summary-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 0.6rem;
}

.mac-window-dots {
display: flex;
align-items: center;
gap: 6px;
}

.mac-window-dots .dot {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
transition: background-color 0.3s ease; /* 颜色过渡 */
}

.mac-window-dots .red { background-color: #ff5f56; }
.mac-window-dots .yellow { background-color: #ffbd2e; }
.mac-window-dots .green { background-color: #27c93f; }

.ai-source {
font-size: 0.75rem;
color: white;
margin-left: auto;
white-space: nowrap;
padding: 4px 12px;
border-radius: 4px;
font-weight: 500;
letter-spacing: 0.5px;
background: linear-gradient(135deg, #6e8efb, #a777e3);
box-shadow: 0 2px 8px rgba(106, 126, 247, 0.3);
position: relative;
overflow: hidden;
transition: all 0.4s ease;
z-index: 1;
}

.ai-source::before {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
transition: 0.6s;
z-index: -1;
}

.ai-source:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(106, 126, 247, 0.4);
}

.ai-source:hover::before {
left: 100%;
}

.ai-source::after {
content: "";
position: absolute;
top: -50%;
right: -20%;
width: 20px;
height: 100px;
background: rgba(255,255,255,0.15);
transform: rotate(30deg);
pointer-events: none;
}

.ai-summary-body {
min-height: 1.6em;
padding: 0.2rem 0;
}

.ai-loading {
color: #888;
font-style: italic;
transition: color 0.3s ease; /* 颜色过渡 */
}

.ai-text {
display: none;
word-break: break-word;
white-space: pre-wrap;
min-height: 1.2em;
}

.ai-footer {
margin-top: 0.8rem;
font-size: 0.7rem;
color: #999;
display: flex;
justify-content: space-between;
align-items: center;
border-top: 1px dashed #ddd;
padding-top: 0.5rem;
transition: all 0.3s ease; /* 过渡动画 */
}

@media (max-width: 768px) {
.ai-summary-wrapper {
padding: 0.8rem 1rem;
font-size: 0.9rem;
}
.ai-source {
font-size: 0.65rem;
padding: 3px 8px;
letter-spacing: normal;
}
}

/* ===== Butterfly主题黑暗模式适配 ===== */
[data-theme="dark"] .ai-summary-wrapper {
background: linear-gradient(135deg, #1a202c 0%, #2d3748 100%);
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);
color: #e2e8f0;
}

[data-theme="dark"] .ai-source {
background: linear-gradient(135deg, #5b67d8, #805ad5);
box-shadow: 0 2px 10px rgba(91, 103, 216, 0.5);
}

[data-theme="dark"] .ai-source:hover {
box-shadow: 0 4px 16px rgba(91, 103, 216, 0.7);
}

[data-theme="dark"] .ai-source::before {
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
}

[data-theme="dark"] .ai-source::after {
background: rgba(255,255,255,0.1);
}

[data-theme="dark"] .mac-window-dots .red { background-color: #fc8181; }
[data-theme="dark"] .mac-window-dots .yellow { background-color: #f6ad55; }
[data-theme="dark"] .mac-window-dots .green { background-color: #68d391; }

[data-theme="dark"] .ai-loading {
color: #a0aec0;
}

[data-theme="dark"] .ai-footer {
color: #a0aec0;
border-top-color: #4a5568;
}

/* 系统级黑暗模式支持 */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) .ai-summary-wrapper {
background: linear-gradient(135deg, #1a202c 0%, #2d3748 100%);
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);
color: #e2e8f0;
}
/* 其他样式与[data-theme="dark"]相同 */
}

添加自定义js

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
(function() {
// 配置参数
const DEFAULT_DELAY = 0; // 默认延迟时间(ms)
const TYPING_SPEED = 50; // 打字速度(ms/字符)

// 查找所有AI摘要元素
const aiTextElements = document.querySelectorAll('.ai-text');

if (!aiTextElements.length) return;

aiTextElements.forEach(el => {
const fullText = el.dataset.text || '';
const delay = Math.max(0, parseInt(el.dataset.delay)) || DEFAULT_DELAY;
const loadingEl = el.previousElementSibling?.classList?.contains('ai-loading')
? el.previousElementSibling
: null;

// 初始设置 - 确保不会产生空行
el.style.display = 'none';
el.style.overflow = 'hidden';
el.style.borderRight = 'none';
el.style.margin = '0';
el.style.padding = '0';

if (!fullText.trim()) {
if (loadingEl) loadingEl.style.display = 'none';
return;
}

const showContent = () => {
if (loadingEl) {
// 平滑过渡:先隐藏"正在思考中..."
loadingEl.style.opacity = '0';
loadingEl.style.transition = 'opacity 0.2s ease';

// 在过渡完成后完全移除loading元素
setTimeout(() => {
loadingEl.style.display = 'none';
// 立即显示内容,避免出现空白期
el.style.display = 'block';
startTyping();
}, 200);
} else {
el.style.display = 'block';
startTyping();
}
};

const startTyping = () => {
el.textContent = '';
let i = 0;
const printChar = () => {
if (i < fullText.length) {
el.textContent += fullText.charAt(i);
i++;
setTimeout(printChar, TYPING_SPEED);
}
};
printChar();
};

delay <= 0 ? setTimeout(showContent, 0) : setTimeout(showContent, delay);
});
})();