文章摘要、相关推荐生成AI
发表于更新于
字数总计:6k阅读时长:28分钟阅读量:
前言
@Heo又整了新活,给文章加上了AI,我也开写,竞速。
项目地址Post-Summary-AI
大半夜领先一点就睡不着的屑Heo
有这些特性:
- 真AI!,接入tianliGPT,JS动态获取全文所有纯文本,传给api实时获取文章摘要(受限于tianliGPT的成本,目前仍有缓存机制)
- 基于tianliGPT标注关键词、Python分析相关度的相关文章AI推荐
- 遇到标点符号慢下来,动态打字速度
- 使用requestAnimationFrame优化性能,动态清除setTimeout、打断fetch请求,交互放心
- 使用IntersectionObserver监听,当容器在视口不可见后停止生成摘要,重新出现在视口后继续生成
- 模拟GPT的光标效果
- 多个按钮实现多个功能,自我介绍、文章摘要、推荐相关文章等
- 良好兼容性,性能消耗小。
- 适配pjax
- 简单引入js后,即可生成QX-AI并自动挂载初始化
- 配置项多样,高度自定义
- 支持切换摘要、摘要语音朗读
未来的计划:
- 目前数据库文章量已经够多,计划构建一个博文社区
5.0积累更新
待 5.* 版本稳定后发布 6.0 版本
项目地址Post-Summary-AI
更多详情请看项目仓库README
新特性:
- 优化文章内容的获取
- 新增适配pjax的配置项
- 自定义css配置项
- 添加svg图标
- 新增文章截取相关配置项
- 更新CSS,优化UI样式
- 新增注入额外CSS配置项
- 新增切换简介功能及相关配置
- 新增摘要语音朗读功能
- 新增UI文字自定义配置
- 新增页面排除配置项
- 标识访客唯一ID
- 新增直接显示摘要的配置项
- 优化JS的业务逻辑,精简代码
- 新增自动挂载:自动获取文章内容所在容器元素,而无需el配置项
- 新增白名单:只让指定页面显示摘要AI
- js动态插入css,无需额外引入
- 新增控制打字机效果的配置项,包括:是否启用打字机效果、打字速度
- 新增矩阵穿梭功能及按钮,矩阵穿梭:随机前往一个部署了AI摘要的网站
- 新增是否启用矩阵穿梭功能的配置项,默认开启
4.0重磅更新
新特性:
- 新增相关文章AI推荐
- 引入缓存机制,减轻后端压力
- 优化代码及性能
快速上手
项目地址Post-Summary-AI
非常简单,引入下面这些代码到你的网站内,并修改配置项后即可
TIP: 为避免CDN和浏览器缓存的影响,建议指定资源版本号使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <link rel="stylesheet" href="https://cdn1.tianli0.top/gh/qxchuckle/Post-Summary-AI/chuckle-post-ai.css">
<script src="https://cdn1.tianli0.top/gh/qxchuckle/Post-Summary-AI/chuckle-post-ai.js"></script>
<script data-pjax defer> new ChucklePostAI({ el: '#post>#article-container', key:'123456', title_el: '.post-title', rec_method: 'web', exclude: ['highlight', 'Copyright-Notice', 'post-ai', 'post-series', 'mini-sandbox'] }) </script>
|
AI构造函数 ChucklePostAI({ /* 传入配置对象 */ })
详解
el
文章内容所在的元素属性的选择器,也是AI挂载的容器,AI将会挂载到该容器的最前面
key
驱动AI所必须的key,即是tianliGPT后端服务所必须的key
title_el
文章标题所在的元素属性的选择器,默认获取当前网页的标题
rec_method
文章推荐方式,all:匹配数据库内所有文章进行推荐,web:仅当前站内的文章,默认all
exclude
获取文章内容时,需要排除的元素及其子元素
tianliGPT的key请到爱发电中购买,10元5万字符(常有优惠)。请求过的内容再次请求不会消耗key,可以无限期使用。
更多详情请看项目仓库README
3.0全新版本
新特性:
- 简单引入js后,即可生成QX-AI并自动挂载初始化
- 新增项目地址Post-Summary-AI
- 优化代码逻辑,引入外部配置
快速上手
非常简单,引入下面这些代码到你的网站内即可,其中JS的引入的位置应该在文章内容之后
1 2 3 4 5 6 7 8
| <link rel="stylesheet" href="https://cdn1.tianli0.top/gh/qxchuckle/Post-Summary-AI/chuckle-post-ai.css"> <script data-pjax defer="true"> var ai_option = { el: '#post #article-container', key:'123456' } </script> <script src="https://cdn1.tianli0.top/gh/qxchuckle/Post-Summary-AI/chuckle-post-ai.js" data-pjax defer="true"></script>
|
ai_option配置详解:
- el 文章内容所在的元素属性的选择器,也是AI挂载的容器,AI将会挂载到该容器的最前面
- key 驱动AI所必须的key,即是tianliGPT后端服务所必须的key
更多详情请看项目仓库README
2.0之前旧版配置
注意:
1、为确保 recommendList() 函数正常运行、相关推荐能正常生成,btf主题用户请打开文章页侧边栏中的两个板块:最新文章和相关推荐,非btf用户也许需要手动更改相关js
2、tianliGPT中国大陆访问速度快,key与域名相绑定,无需担心盗刷,一次生成后续不消耗key字数,tianliGPT的key购买渠道:购买tianliGPT-Key
3、如有bug,请在评论区讨论,请忽略js中的不规范命名和一堆变量,会优化的咕咕咕。
手动修改js中这部分常量以使用AI实时简介
1 2 3 4 5 6
| const choiceApi = true; const apiKey = "填入chatGPT的apiKey";
const tlReferer = 'https://你的授权域名/'; const tlKey = '填入tianliGPT的key';
|
虽然以及有AI实时生成了,但还是需要预设的,在markdown文件的matter添加ai配置,放入ai事先生成好的文章简介或提升去点AI生成按钮,不想出现AI模块的文章就删去这个配置项
1 2 3 4
| --- title: ai: 暂无预设简介,请点击下方生成AI简介按钮。 ---
|
2.0版
新特性:
- 真!AI,接入tianliGPT或是使用官方api接口,随意选择
- 前端限制请求频率、动态打断fetch
- 压缩文本降低key压力,纯文本1000字以上的文章,截取前500后200中间300字生成摘要,降低key字数消耗,当然,可以给 getTextContent 传入第二个参数为false,从而不压缩文本
修改post.pug
,将下面代码加在合适的位置,如 article#article-container.post-content 后,注意缩进
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| if page.ai .post-ai .ai-title i.fa-brands.fa-airbnb .ai-title-text QX-AI .ai-tag GPT-4 .ai-explanation QX-AI初始化中... .ai-explanation-none #{page.ai} .ai-btn-box .ai-btn-item 介绍自己 .ai-btn-item 生成本文简介 .ai-btn-item 推荐相关文章 .ai-btn-item 前往主页 script(src="/js/post-ai.js" defer="true" data-pjax)
|
新建post-ai.js
4-16修复了若干bug
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
| function allAI() { let animationRunning = true; let explanation = document.querySelector('.ai-explanation'); let abstract_value = document.querySelector('.ai-explanation-none').innerHTML; let post_ai = document.querySelector('.post-ai'); let ai_btn_item = document.querySelectorAll('.ai-btn-item'); let ai_str = ''; let ai_str_length = ''; let delay_init = 600; let i = 0; let j = 0; let sto = []; let elapsed = 0; let completeGenerate = false; let controller = new AbortController(); let signal = controller.signal; const choiceApi = true; const apiKey = "填入chatGPT的apiKey"; const tlReferer = 'https://你的授权域名/'; const tlKey = '填入tianliGPT的key'; const animate = (timestamp) => { if (!animationRunning) { return; } if (!animate.start) animate.start = timestamp; elapsed = timestamp - animate.start; if (elapsed >= 20) { animate.start = timestamp; if (i < ai_str_length - 1) { let char = ai_str.charAt(i + 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); i++; if (delay === 150) { document.querySelector('.ai-explanation .ai-cursor').style.opacity = "0"; } if (i === ai_str_length - 1) { observer.disconnect(); explanation.removeChild(explanation.firstElementChild); } sto[0] = setTimeout(() => { requestAnimationFrame(animate); }, delay); } } else { requestAnimationFrame(animate); } }; const observer = new IntersectionObserver((entries) => { let isVisible = entries[0].isIntersecting; animationRunning = isVisible; if (animationRunning) { delay_init = i === 0 ? 200 : 20; sto[1] = setTimeout(() => { if (j) { i = 0; j = 0; } if (i === 0) { explanation.innerHTML = ai_str.charAt(0); } requestAnimationFrame(animate); }, delay_init); } }, { threshold: 0 }); function clearSTO() { if (sto.length) { sto.forEach((item) => { if (item) { clearTimeout(item); } }); } } function resetAI(df = true) { i = 0; j = 1; clearSTO(); animationRunning = false; elapsed = 0; if (df) { explanation.innerHTML = '生成中. . .'; } else { explanation.innerHTML = '请等待. . .'; } if (!completeGenerate) { controller.abort(); } ai_str = ''; ai_str_length = ''; observer.disconnect(); } function startAI(str, df = true) { resetAI(df); ai_str = str; ai_str_length = ai_str.length; observer.observe(post_ai); } function aiIntroduce() { startAI('我是文章辅助AI: QX-AI,点击下方的按钮,让我生成本文简介、推荐相关文章等。'); } function aiAbstract() { startAI(abstract_value); } function aiRecommend() { resetAI(); sto[2] = setTimeout(() => { explanation.innerHTML = recommendList(); }, 300); } function aiGoHome() { startAI('正在前往博客主页...', false); sto[2] = setTimeout(() => { pjax.loadUrl("/"); }, 1000); } async function aiGenerateAbstract() { if (clickFrequency()) { return; } localStorage.setItem('aiTime', Date.now()); resetAI(); const ele = document.querySelector('#article-container'); const content = getTextContent(ele); console.log(content); const response = await getGptResponse(content, choiceApi); console.log(response); startAI(response); } function recommendList() { let info = `推荐文章:<br />`; let thumbnail = document.querySelectorAll('.card-recommend-post .aside-list .aside-list-item .thumbnail'); if (!thumbnail||thumbnail.length===0) { info = '很抱歉,无法找到类似的文章,你也可以看看本站最近更新的文章:<br />'; thumbnail = document.querySelectorAll('.card-recent-post .aside-list .aside-list-item .thumbnail'); } info += '<div class="ai-recommend">'; thumbnail.forEach((item, index) => { info += `<div class="ai-recommend-item"><span>推荐${index + 1}:</span><a href="javascript:;" onclick="pjax.loadUrl('${item.href}')" title="${item.title}" data-pjax-state="">${item.title}</a></div>`; }); info += '</div>' return info; } function ai_init() { explanation = document.querySelector('.ai-explanation'); abstract_value = document.querySelector('.ai-explanation-none').innerHTML; post_ai = document.querySelector('.post-ai'); ai_btn_item = document.querySelectorAll('.ai-btn-item'); const funArr = [aiIntroduce, aiAbstract, aiRecommend, aiGenerateAbstract]; ai_btn_item.forEach((item, index) => { item.addEventListener('click', () => { funArr[index](); }); }); aiIntroduce(); } function clickFrequency(t = 3000) { let time = Date.now() - localStorage.getItem('aiTime'); if (time < t) { btf.snackbarShow(`${3 - parseInt(time / 1000)}后才能再次点击真AI简介`); return true; } else { return false; } }
function getText(element) { const excludeClasses = ['highlight', 'Copyright-Notice', 'post-ai', 'post-series', 'mini-sandbox',]; let textContent = ''; for (let node of element.childNodes) { if (node.nodeType === Node.TEXT_NODE) { textContent += node.textContent.trim(); } else if (node.nodeType === Node.ELEMENT_NODE) { let hasExcludeClass = false; for (let className of node.classList) { if (excludeClasses.includes(className)) { hasExcludeClass = true; break; } } if (!hasExcludeClass) { let innerTextContent = getText(node); textContent += innerTextContent; } } } return textContent.replace(/\s+/g, ''); } function extractHeadings(element) { const headings = element.querySelectorAll('h1, h2, h3, h4'); const result = []; for (let i = 0; i < headings.length; i++) { const heading = headings[i]; const headingText = heading.textContent.trim(); result.push(headingText); const childHeadings = extractHeadings(heading); result.push(...childHeadings); } return result.join(";"); } function extractString(str) { var first500 = str.substring(0, 500); var last200 = str.substring(str.length - 200); var midStartIndex = (str.length - 300) / 2; var middle300 = str.substring(midStartIndex, midStartIndex + 300); var result = first500 + middle300 + last200; return result; } function getTextContent(element, i = true) { let content; if (i) { content = `文章的各级标题:${extractHeadings(element)}。文章内容的截取:${extractString(getText(element))}`; } else { content = `${getText(element)}`; } return content; } async function getGptResponse(content, i = true) { completeGenerate = false; controller = new AbortController(); signal = controller.signal; let response = ''; if (i) { try { response = await fetch('https://summary.tianli0.top/', { signal: signal, method: "POST", headers: { "Content-Type": "application/json", "Referer": tlReferer }, body: JSON.stringify({ content: content, key: tlKey }) }); if (response.status === 429) { startAI('请求过于频繁,请稍后再请求AI。'); } if (!response.ok) { throw new Error('Response not ok'); } } catch (error) { console.error('Error occurred:', error); startAI("QX-AI请求tianliGPT出错了,请稍后再试。"); } const data = await response.json(); const outputText = data.summary; completeGenerate = true; return outputText; } else { const prompt = `你是一个摘要生成工具,你需要解释我发送给你的内容,不要换行,不要超过200字,只需要介绍文章的内容,不需要提出建议和缺少的东西。请用中文回答,文章内容为:${content}`; const apiUrl = "https://api.openai.com/v1/chat/completions"; try { response = await fetch(apiUrl, { signal: signal, method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` }, body: JSON.stringify({ model: "gpt-3.5-turbo", messages: [{ "role": "user", "content": prompt }], }) }); if (response.status === 429) { startAI('请求过于频繁,请稍后再请求AI。'); } if (!response.ok) { throw new Error('Response not ok'); } } catch (error) { console.error('Error occurred:', error); startAI("QX-AI请求chatGPT出错了,请稍后再试。"); } const data = await response.json(); const outputText = data.choices[0].message.content; completeGenerate = true; return outputText; } } ai_init(); } allAI();
|
添加CSS,颜色变量可F12自取,建议自己重新配色(2.0版本无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
| .post-ai{ background: var(--ai-post-bg); border-radius: 12px; padding: 12px 16px; line-height: 1.3; border: var(--ai-border); margin-top: 10px; margin-bottom: 6px; } .ai-title{ display: flex; color: var(--font-color); border-radius: 8px; align-items: center; padding: 0 5px; } .ai-title i{ font-weight: 800; } .ai-title-text{ font-weight: bold; margin-left: 8px; } .ai-tag{ font-size: 12px; background-color: var(--ai-tag-bg); color: rgba(255,255,255,0.9); border-radius: 4px; margin-left: auto; line-height: 1; padding: 4px 5px; } .ai-explanation{ margin-top: 11px; font-size: 15.5px; line-height: 1.4; } .ai-cursor{ display: inline-block; width: 7px; background: #333; height: 16px; margin-bottom: -2px; opacity: 0.95; margin-left: 3px; transition: all 0.3s; -webkit-transition: all 0.3s; -moz-transition: all 0.3s; -ms-transition: all 0.3s; -o-transition: all 0.3s; } [data-theme=dark] .ai-cursor{ background: rgb(255, 255, 255, 0.9); } .ai-btn-box{ font-size: 15.5px; width: 100%; display: flex; flex-direction: row; flex-wrap: wrap; } .ai-btn-item{ padding: 5px 10px; margin: 10px 16px 0px 5px; width: fit-content; line-height: 1; background: rgba(48, 52, 63, 0.75); color: #fff; border-radius: 6px 6px 6px 0; -webkit-border-radius: 6px 6px 6px 0; -moz-border-radius: 6px 6px 6px 0; -ms-border-radius: 6px 6px 6px 0; -o-border-radius: 6px 6px 6px 0; user-select: none; transition: all 0.3s; -webkit-transition: all 0.3s; -moz-transition: all 0.3s; -ms-transition: all 0.3s; -o-transition: all 0.3s; } .ai-btn-item:hover{ background: #49b0f5dc; } .ai-recommend{ display: flex; flex-direction: row; flex-wrap: wrap; } .ai-recommend-item{ width: 50%; margin-top: 2px; } @media screen and (max-width:768px){ .ai-btn-box{ justify-content: center; } .ai-recommend .ai-recommend-item{ width: 100%; } } .ai-explanation-none{ position: absolute; opacity: 0; width: 0; height: 0; z-index: -999; }
|
1.1版
修改post.pug
,将下面代码加在合适的位置,如 article#article-container.post-content 后,注意缩进
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| if page.ai .post-ai .ai-title i.fa-brands.fa-airbnb .ai-title-text QX-AI .ai-tag GPT-4 .ai-explanation QX-AI初始化中... .ai-explanation-none #{page.ai} .ai-btn-box .ai-btn-item 介绍自己 .ai-btn-item 生成本文简介 .ai-btn-item 推荐相关文章 .ai-btn-item 前往主页 script(src="/js/post-ai.js?2" defer="true" data-pjax)
|
新建post-ai.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 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
| if (true) { let animationRunning = true; const explanation = document.querySelector('.ai-explanation'); const abstract_value = document.querySelector('.ai-explanation-none').innerHTML; const post_ai = document.querySelector('.post-ai'); let ai_str = ''; let ai_str_length = ''; let delay_init = 600; let i = 0; let j = 0; let sto = []; let elapsed = 0; const animate = (timestamp) => { if (!animationRunning) { return; } if (!animate.start) animate.start = timestamp; elapsed = timestamp - animate.start; if (elapsed >= 20) { animate.start = timestamp; if (i < ai_str_length-1) { let char = ai_str.charAt(i+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); i++; if(delay === 150){ document.querySelector('.ai-explanation .ai-cursor').style.opacity = "0"; } if(i === ai_str_length-1){ observer.disconnect(); explanation.removeChild(explanation.firstElementChild); } sto[0] = setTimeout(() => { requestAnimationFrame(animate); }, delay); } } else { requestAnimationFrame(animate); } }; const observer = new IntersectionObserver((entries) => { let isVisible = entries[0].isIntersecting; animationRunning = isVisible; if(animationRunning){ delay_init = i===0 ? 200 : 20; sto[1] = setTimeout(() => { if(j){ i = 0; j = 0; } if(i===0){ explanation.innerHTML = ai_str.charAt(0); } requestAnimationFrame(animate); }, delay_init); } }, { threshold: 0 }); function clearSTO(){ if(sto.length){ sto.forEach((item)=>{ if(item){ clearTimeout(item); } }); } } function startAI(str,df=true){ i = 0; j = 1; clearSTO(); animationRunning = false; elapsed = 0; if(df){ explanation.innerHTML = '生成中. . .'; }else{ explanation.innerHTML = '请等待. . .'; } ai_str = str; ai_str_length = ai_str.length; observer.disconnect(); observer.observe(post_ai); } function aiIntroduce(){ startAI('我是文章辅助AI: QX-AI,点击下方的按钮,让我生成本文简介、推荐相关文章等。'); } function aiAbstract(){ startAI(abstract_value); } function aiRecommend(){ i = 0; j = 1; clearSTO(); animationRunning = false; elapsed = 0; explanation.innerHTML = '生成中. . .'; ai_str = ''; ai_str_length = ''; observer.disconnect(); sto[2] = setTimeout(() => { explanation.innerHTML = recommendList(); }, 600); } function aiGoHome(){ startAI('正在前往博客主页...',false); sto[2] = setTimeout(() => { pjax.loadUrl("/"); }, 1000); } const ai_btn_item = document.querySelectorAll('.ai-btn-item'); ai_btn_item.forEach((item, index)=>{ item.addEventListener('click', ()=>{ switch(index) { case 0: aiIntroduce(); break; case 1: aiAbstract(); break; case 2: aiRecommend(); break; case 3: aiGoHome(); break; } }); }); function recommendList(){ let info = `推荐文章:<br />`; let thumbnail = document.querySelectorAll('.card-recommend-post .aside-list .aside-list-item .thumbnail'); if(!thumbnail){ info = '很抱歉,无法找到类似的文章,你也可以看看本站最新发布的文章:<br />'; thumbnail = document.querySelectorAll('.card-recent-post .aside-list .aside-list-item .thumbnail'); } info += '<div class="ai-recommend">'; thumbnail.forEach((item, index)=>{ info += `<div class="ai-recommend-item"><span>推荐${index+1}:</span><a href="javascript:;" onclick="pjax.loadUrl('${item.href}')" title="${item.title}" data-pjax-state="">${item.title}</a></div>`; }); info += '</div>' return info; } aiIntroduce(); }
|
添加CSS,颜色变量可F12自取
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
| .post-ai{ background: var(--ai-post-bg); border-radius: 12px; padding: 12px 16px; line-height: 1.3; border: var(--ai-border); margin-top: 10px; margin-bottom: 6px; } .ai-title{ display: flex; color: var(--font-color); border-radius: 8px; align-items: center; padding: 0 5px; } .ai-title i{ font-weight: 800; } .ai-title-text{ font-weight: bold; margin-left: 8px; } .ai-tag{ font-size: 12px; background-color: var(--ai-tag-bg); color: rgba(255,255,255,0.9); border-radius: 4px; margin-left: auto; line-height: 1; padding: 4px 5px; } .ai-explanation{ margin-top: 11px; font-size: 15.5px; line-height: 1.4; } .ai-cursor{ display: inline-block; width: 7px; background: #333; height: 16px; margin-bottom: -2px; opacity: 0.95; margin-left: 3px; transition: all 0.3s; -webkit-transition: all 0.3s; -moz-transition: all 0.3s; -ms-transition: all 0.3s; -o-transition: all 0.3s; } [data-theme=dark] .ai-cursor{ background: rgb(255, 255, 255, 0.9); } .ai-btn-box{ font-size: 15.5px; width: 100%; display: flex; flex-direction: row; flex-wrap: wrap; } .ai-btn-item{ padding: 5px 10px; margin: 10px 16px 0px 5px; width: fit-content; line-height: 1; background: rgba(48, 52, 63, 0.75); color: #fff; border-radius: 6px 6px 6px 0; -webkit-border-radius: 6px 6px 6px 0; -moz-border-radius: 6px 6px 6px 0; -ms-border-radius: 6px 6px 6px 0; -o-border-radius: 6px 6px 6px 0; user-select: none; transition: all 0.3s; -webkit-transition: all 0.3s; -moz-transition: all 0.3s; -ms-transition: all 0.3s; -o-transition: all 0.3s; } .ai-btn-item:hover{ background: #49b0f5dc; } .ai-recommend{ display: flex; flex-direction: row; flex-wrap: wrap; } .ai-recommend-item{ width: 50%; margin-top: 2px; } @media screen and (max-width:768px){ .ai-btn-box{ justify-content: center; } .ai-recommend .ai-recommend-item{ width: 100%; } } .ai-explanation-none{ position: absolute; opacity: 0; width: 0; height: 0; z-index: -999; }
|
使用:在markdown文件的matter添加ai配置,放入ai事先生成好的文章简介
1 2 3 4 5
| --- title: 文章添加AI摘要和推荐 ai: 本文介绍了如何通过手动生成GPT网页版摘要,再用JS模拟GPT打字生成效果,实现AI摘要。作者详细讲解了实现思路,包括停顿、延迟等策略,以及如何监听视口并控制生成。作者还提到了未来计划,即使用API生成更多摘要。此外,作者还给出了修改 post.pug 和 CSS 的代码。 ---
|
1.0版
才两个小时就沦为旧版的屑
修改post.pug
,将下面代码加在合适的位置,如 article#article-container.post-content 后,注意缩进
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
| if page.ai .post-ai .ai-title i.fa-brands.fa-airbnb .ai-title-text AI摘要 .ai-tag GPT-4 .ai-explanation #{page.ai} script. if (true) { let animationRunning = true; const explanation = document.querySelector('.ai-explanation'); const post_ai = document.querySelector('.post-ai'); const ai_str = explanation.innerHTML; const ai_str_length = ai_str.length; let delay_init = 600; let i = 0; explanation.innerHTML = 'AI摘要生成中. . .'; const animate = (timestamp) => { if (!animationRunning) { return; } if (!animate.start) animate.start = timestamp; const elapsed = timestamp - animate.start; if (elapsed >= 20) { animate.start = timestamp; if (i < ai_str_length-1) { let char = ai_str.charAt(i+1); let delay = /[,.,。!?!?]/.test(char) ? 160 : 20; if(explanation.firstElementChild){ explanation.removeChild(explanation.firstElementChild); } explanation.innerHTML += char; let div = document.createElement('div'); div.className = "ai-cursor"; explanation.appendChild(div); i++; if(delay === 160){ document.querySelector('.ai-explanation .ai-cursor').style.opacity = "0"; } if(i === ai_str_length-1){ observer.disconnect(); explanation.removeChild(explanation.firstElementChild); } setTimeout(() => { requestAnimationFrame(animate); }, delay); } } else { requestAnimationFrame(animate); } }; const observer = new IntersectionObserver((entries) => { let isVisible = entries[0].isIntersecting; animationRunning = isVisible; if(animationRunning){ delay_init = i===0 ? 600 : 20; setTimeout(() => { if(i===0){ explanation.innerHTML = ai_str.charAt(0); } requestAnimationFrame(animate); }, delay_init); } }, { threshold: 0 }); observer.observe(post_ai); }
|
添加CSS,颜色变量F12自取
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
| .post-ai{ background: var(--ai-post-bg); border-radius: 12px; padding: 12px 16px; line-height: 1.3; border: var(--ai-border); margin-top: 10px; margin-bottom: 6px; } .ai-title{ display: flex; color: var(--font-color); border-radius: 8px; align-items: center; padding: 0 5px; } .ai-title i{ font-weight: 800; } .ai-title-text{ font-weight: bold; margin-left: 8px; } .ai-tag{ font-size: 12px; background-color: var(--ai-tag-bg); color: rgba(255,255,255,0.9); border-radius: 4px; margin-left: auto; line-height: 1; padding: 4px 5px; } .ai-explanation{ margin-top: 11px; font-size: 15.5px; line-height: 1.4; } .ai-cursor{ display: inline-block; width: 7px; background: #333; height: 16px; margin-bottom: -2px; opacity: 0.95; margin-left: 3px; transition: all 0.3s; -webkit-transition: all 0.3s; -moz-transition: all 0.3s; -ms-transition: all 0.3s; -o-transition: all 0.3s; } [data-theme=dark] .ai-cursor{ background: rgb(255, 255, 255, 0.9); }
|