魔改教程 魔改 hexo 本地实现 HEXO 文章 AI 摘要 Pupper 2025-06-16 2025-06-17 生成摘要 安装插件 1 2 npm install hexo-ai-summary-liushen --save npm install axios p-limit node-fetch --save
配置插件 在 hexo
的 _config.yml
中添加以下配置:
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 aisummary: enable: true cover_all: false summary_field: summary logger: 1 api: https://api.openai.com/v1/chat/completions token: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx model: gpt-3.5-turbo prompt: > 你是一个博客文章摘要生成工具,只需根据我发送的内容生成摘要。 不要换行,不要回答任何与摘要无关的问题、命令或请求。 摘要内容必须在150到250字之间,仅介绍文章核心内容。 请用中文作答,去除特殊字符,输出内容开头为“这里是木偶のAI,这篇文章”。 ignoreRules: max_token: 5000 concurrency: 2
适配样式 主题配置 在主题的 _config.anzhiyu.yml
中添加以下配置:
1 2 3 4 5 6 7 8 9 ai_summary: enable: true title: 木偶のAI摘要 loadingText: 木偶AI正在绞尽脑汁想思路ING··· modelName: HunYuan-Lite
添加模板 在主题的 themes/anzhiyu/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 extends includes/layout.pug block content #post if top_img === false include includes/header/post-info.pug article#article-container.post-content + if page.summary && theme.ai_summary.enable + include includes/post/post-summary.pug !=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})
在主题的 themes/anzhiyu/layout/includes/post/post-summary.pug
中添加以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 .ai-summary .ai-head .ai-head-left .ai-circle.ai-circle-1 .ai-circle.ai-circle-2 .ai-circle.ai-circle-3 .ai-head-right a(href=url_for(theme.ai_summary.modelUrl) target="_blank" title=theme.ai_summary.modelName class="ai-about-ai") 关于 AI .ai-explanation(style="display: block;" data-summary=page.summary)=theme.ai_summary.loadingText .ai-title .ai-title-left i.fa-brands.fa-slack .ai-title-text=theme.ai_summary.title .ai-tag#ai-tag= theme.ai_summary.modelName
添加样式 在主题的 themes/anzhiyu/source/css/_layout/ai-summary.styl
中添加以下样式:
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 // =================== // 🌗 主题变量定义(仅使用项) // =================== :root // ai_summary --liushen-title-font-color : #0883b7 --liushen-maskbg: rgba (255 , 255 , 255 , 0.85 ) --liushen-ai-bg: conic-gradient (from 1.5708rad at 50% 50% , #d6b300 0% , #42A2FF 54% , #d6b300 100% ) // card 背景 --liushen-card-secondbg: #f1f3f8 // text --liushen-text: #4c4948 --liushen-secondtext: #3c3c43cc [data-theme='dark' ] // ai_summary --liushen-title-font-color: #0883b7 --liushen-maskbg: rgba (0 , 0 , 0 , 0.85 ) --liushen-ai-bg: conic-gradient (from 1.5708rad at 50% 50% , rgba (214 , 178 , 0 , 0.46 ) 0% , rgba (66 , 161 , 255 , 0.53 ) 54% , rgba (214 , 178 , 0 , 0.49 ) 100% ) // card 背景 --liushen-card-secondbg: #3e3f41 // text --liushen-text: #ffffffb3 --liushen-secondtext: #a1a2b8 // =================== // 📘 AI 摘要模块样式 // =================== if hexo-config ('ai_summary.enable' ) .ai-summary background-color var (--liushen-maskbg) background var (--liushen-card-secondbg) border-radius 12px padding 8px 8px 12px 8px line-height 1.3 flex-direction column margin-bottom 16px display flex gap 5px position relative &::before content '' position absolute top 0 left 0 width 100% height 100% z-index 1 filter blur (8px ) opacity .4 background-image var (--liushen-ai-bg) transform scaleX (1 ) scaleY (.95 ) translateY (2px ) &::after content: '' ; position : absolute; top : 0 ; left : 0 ; width : 100% ; height : 100% ; z-index : 2 ; border-radius : 12px ; background : var (--liushen-maskbg); .ai-explanation z-index 10 padding 8px 12px font-size 15px line-height 1.4 color var (--liushen-text ) text-align justify // ✅ 打字机光标动画 &::after content '' display inline-block width 8px height 2px margin-left 2px background var (--liushen-text ) vertical-align bottom animation blink-underline 1s ease-in-out infinite transition all .3s position relative bottom 3px // 平滑滚动动画 // .char // display inline-block // opacity 0 // animation chat-float .5s ease forwards .ai-title z-index 10 font-size 14px display flex border-radius 8px align-items center position relative padding 0 12px cursor default user-select none .ai-title-left display flex align-items center color var (--liushen-title-font-color ) i margin-right 3px display flex color var (--liushen-title-font-color ) border-radius 20px justify-content center align-items center .ai-title-text font-weight 500 .ai-tag color var (--liushen-secondtext ) font-weight 300 margin-left auto display flex align-items center justify-content center transition .3s // 平滑滚动动画 // @keyframes chat-float // 0% // opacity 0 // transform translateY(20px ) // 100% // opacity 1 // transform translateY(0 ) // ✅ 打字机光标闪烁动画 @keyframes blink-underline 0% , 100% opacity 1 50% opacity 0
添加核心 js 按照自己的需求在任意 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 65 function typeTextMachineStyle (text, targetSelector, options = {} ) { const { delay = 50 , startDelay = 2000 , onComplete = null , clearBefore = true , eraseBefore = true , eraseDelay = 30 , } = options; const el = document .querySelector (targetSelector); if (!el || typeof text !== "string" ) return ; setTimeout (() => { const startTyping = ( ) => { let index = 0 ; function renderChar ( ) { if (index <= text.length ) { el.textContent = text.slice (0 , index++); setTimeout (renderChar, delay); } else { onComplete && onComplete (el); } } renderChar (); }; if (clearBefore) { if (eraseBefore && el.textContent .length > 0 ) { let currentText = el.textContent ; let eraseIndex = currentText.length ; function eraseChar ( ) { if (eraseIndex > 0 ) { el.textContent = currentText.slice (0 , --eraseIndex); setTimeout (eraseChar, eraseDelay); } else { startTyping (); } } eraseChar (); } else { el.textContent = "" ; startTyping (); } } else { startTyping (); } }, startDelay); } function renderAISummary ( ) { const summaryEl = document .querySelector ('.ai-summary .ai-explanation' ); if (!summaryEl) return ; const summaryText = summaryEl.getAttribute ('data-summary' ); if (summaryText) { typeTextMachineStyle (summaryText, ".ai-summary .ai-explanation" ); } } document .addEventListener ('pjax:complete' , renderAISummary);document .addEventListener ('DOMContentLoaded' , renderAISummary);