起因#
在之前的NotionNext架构博客使用过一两周 TianliGPT ↗ 的AI摘要服务,虽然只需10¥就有足够的TOKEN余额,对于我这种小博客肯定是绰绰有余,但感觉还是不够自定义 (想完全自建使用) 详见《讨论NotionNext如何配置AI摘要》 ↗
之后呢给博客迁移到了 Astro 架构,看到了 konoXIN的文章 ↗ ,大佬的AI摘要实现思路也很完美,试了一周感觉不错,不过就是有点小小的不足,因为他的摘要是存储在 Index DB 里的,虽然占用的空间小,并且也足够存储生成的摘要,但每个人在访问文章时都会请求一次API还是太那啥了 (即使TOKEN无限用)
思考了几天,也试了试,还是想本地生成文章摘要再放置在文章的 Frontmatter 块末尾,于是结合 @Liushen ↗ 和 @konoXIN ↗ 的思路,捣鼓了几天 实则是摸鱼~ ,初步实现了我的需求 应该不会有啥大毛病吧
前置#
API这部分就直接采用 konoXIN文章 ↗ 里的代码了(如有侵权可以与我联系)
申请星火Spark-Lite#
- 访问 星火大模型API ↗
- 下滑页面到如下位置,选择
Spark-Lite,点击立即调用

- 点击创建新应用,之后侧边栏点击
Spark Lite,选择领取无限量即可

- 之后右侧会出现你的
APPID、APISecret、APIKey, 需要记住保存好

搭建 Vercel API#
- 一键部署到Vercel:
Deploy with Vercel
- Fork后自行导入:
Fork Github Project
-
点击上方任一按钮导入项目成功后,进入该项目在 Vercel 上的仪表盘 Dashboard 选项
-
在项目仪表盘中找到 Settings 选项
-
在 Settings 下找到 Environment Variables 部分
-
在这里添加你的
SPARK_APPID,SPARK_API_KEY和SPARK_API_SECRET,对应如下表:Spark Lite Websocket VercelServer APPIDSPARK_APPIDAPIKeySPARK_API_KEYAPISecretSPARK_API_SECRET -
配置完环境变量后需重新部署一次项目
项目部署后,Vercel 会提供一个类似 https://[your-project-name].vercel.app 的域名。代理函数可以通过 /api/spark-proxy 接口访问。
如果需要想改为自己的子域名,需要在域名控制台解析一下,让其可以在国内访问
引入#
- 代码片段Gist开源地址:Static-AiSummary for Astro-Theme-Pure ↗
- GitHub源码下载:static-aisummary ↗
-
在
src/plugins路径下添加脚本文件aisummary.js↗ -
在
src/assets/styles路径下添加样式文件aisummary.css↗ -
在
scripts/路径下添加脚本文件generateSummary.ts↗ -
在
src/plugins路径下添加配置文件aisummary.config.js↗ -
在
src/components/BaseHead.astro和src/layouts/BlogPost.astro中添加初始化代码BaseHead.astro
astro--- import aisummaryConfig from '../plugins/aisummary.config.js?url'; import aisummary from '../plugins/aisummary.js?url'; import aisummaryCss from '../assets/styles/aisummary.css?url'; --- {/* AISummary 工具 */} <script src={aisummaryConfig} is:inline></script> {Astro.url.pathname.startsWith('/blog/') && !/^\/blog\/\d+\/?$/.test(Astro.url.pathname) && <script src={aisummary} is:inline></script>} {/* AISummary 样式 */} <link rel='stylesheet' href={aisummaryCss} />BlogPost.astro
astro<PageLayout meta={{ articleDate, description, ogImage: socialImage, title }} highlightColor={primaryColor} back='/blog' > {/* AISummary AI摘要 */} {data.summary && ( <div class='aisummary-container'> <div class='aisummary-title'> <i class='aisummary-title-icon'> <svg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 0 48 48'> <title>机器人</title> <g stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'> <path d='M34.717885,5.03561087 C36.12744,5.27055371 37.079755,6.60373651 36.84481,8.0132786 L35.7944,14.3153359 L38.375,14.3153359 C43.138415,14.3153359 47,18.1768855 47,22.9402569 L47,34.4401516 C47,39.203523 43.138415,43.0650727 38.375,43.0650727 L9.625,43.0650727 C4.861585,43.0650727 1,39.203523 1,34.4401516 L1,22.9402569 C1,18.1768855 4.861585,14.3153359 9.625,14.3153359 L12.2056,14.3153359 L11.15519,8.0132786 C10.920245,6.60373651 11.87256,5.27055371 13.282115,5.03561087 C14.69167,4.80066802 16.024865,5.7529743 16.25981,7.16251639 L17.40981,14.0624532 C17.423955,14.1470924 17.43373,14.2315017 17.43948,14.3153359 L30.56052,14.3153359 C30.56627,14.2313867 30.576045,14.1470924 30.59019,14.0624532 L31.74019,7.16251639 C31.975135,5.7529743 33.30833,4.80066802 34.717885,5.03561087 Z M38.375,19.4902885 L9.625,19.4902885 C7.719565,19.4902885 6.175,21.0348394 6.175,22.9402569 L6.175,34.4401516 C6.175,36.3455692 7.719565,37.89012 9.625,37.89012 L38.375,37.89012 C40.280435,37.89012 41.825,36.3455692 41.825,34.4401516 L41.825,22.9402569 C41.825,21.0348394 40.280435,19.4902885 38.375,19.4902885 Z M14.8575,23.802749 C16.28649,23.802749 17.445,24.9612484 17.445,26.3902253 L17.445,28.6902043 C17.445,30.1191812 16.28649,31.2776806 14.8575,31.2776806 C13.42851,31.2776806 12.27,30.1191812 12.27,28.6902043 L12.27,26.3902253 C12.27,24.9612484 13.42851,23.802749 14.8575,23.802749 Z M33.1425,23.802749 C34.57149,23.802749 35.73,24.9612484 35.73,26.3902253 L35.73,28.6902043 C35.73,30.1191812 34.57149,31.2776806 33.1425,31.2776806 C31.71351,31.2776806 30.555,30.1191812 30.555,28.6902043 L30.555,26.3902253 C30.555,24.9612484 31.71351,23.802749 33.1425,23.802749 Z' fill='#444444' fill-rule='nonzero'></path> </g> </svg> </i> <div class='aisummary-title-text'>Li's AI Summary</div> <div class='aisummary-tag' id='aisummary-tag'>Li's AI</div> </div> <div class='aisummary-explanation' data-ai-summary={data.summary}>{data.summary}</div> <div class='aisummary-disclaimer'>本摘要由AI生成,仅供参考,内容准确性请以原文为准。</div> </div> )} </PageLayout>
配置#
主要集中在 aisummary.config.js 和 .env 文件进行配置和修改变量,列表如下(文件中也有详细注释说明):
供离线脚本读取的配置项#
| 配置项 | 示例值 | 说明 |
|---|---|---|
| AI_SUMMARY_API | https://your-api-server/api/spark-proxy | 摘要服务地址 |
| AI_SUMMARY_KEY | your-api-key | 摘要服务密钥 |
| AI_SUMMARY_MODEL | lite | 模型名称 |
| AISUMMARY_CONCURRENCY | 2 | 并发执行摘要生成任务数 |
| AISUMMARY_COVER_ALL | false | 是否重新生成所有摘要 |
| AISUMMARY_MAX_TOKEN | 5000 | 用于截取文章内容 |
| AISUMMARY_MIN_CONTENT_LENGTH | 50 | 用于判断是否跳过 |
说明:使用前文 Spark-Lite 模型搭建Vercel代理服务器不需配置 AI_SUMMARY_KEY 变量,可保持空值
供博客页面使用的配置项#
| 配置项 | 示例值 | 说明 |
|---|---|---|
| aisummaryTypingAnimate | true | 是否开启打字机动画 |
| aisummaryPostSelector | #content | 文章内容容器选择器 |
如何知道 aisummaryPostSelector ?
进入任一博客文章页面,按下 F12 进入开发者控制台,再按下 Ctrl + Shift + C ,并选取 正文部分 进行如下步骤复制后即为 aisummaryPostSelector 的值

使用#
- 补全缺失库
npm install -D xxx - 确保
aisummary.config.js和.env文件里变量配置正确 - 在项目主目录运行
npx tsx scripts/generate-summary.ts即可
后记#
Vercel代理服务器部分的代码基本来自于 @konoXIN ↗ ,主要修改的本地脚本的部分,感觉代码有点乱,还需要再整理一下
