Skip to content

Commit

Permalink
feat: add ai summary component
Browse files Browse the repository at this point in the history
  • Loading branch information
3Alan committed Jan 14, 2024
1 parent 9a4267b commit ce04cf2
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 53 deletions.
115 changes: 115 additions & 0 deletions docs/blog-guides/docusaurus-ai-bot.md
@@ -0,0 +1,115 @@
---
slug: docusaurus-ai-bot
title: Docusaurus 自动化生成文章总结
tags:
- 博客
- Docusaurus
keywords:
- Docusaurus
- probot
- Gemini
- AI summarizer
date: 2024-01-14T00:00:00.000Z
description: 利用 Gemini AI 搭配 Github Bot 实现自动化生成文章总结
sidebar_position: 1
---

先看效果
![20240114223129](https://raw.githubusercontent.com/3Alan/images/master/img/20240114223129.png)
![20240114223229](https://raw.githubusercontent.com/3Alan/images/master/img/20240114223229.png)

最近谷歌的 AI 模型 Gemini 发布了,本着白嫖的原则,准备给我的博客加上 AI 总结的功能。由于我的博客是一个静态网站,没有服务端并且我也不想引入服务端,所有没有办法在上面写接口。于是在调研了一些方案后决定在 build 时对文章进行总结并将总结的内容插入到文档的 front matter 中,这样就可以在没有服务端的情况下直接读取到总结的内容了。

## 方案

我一共想到了几种方案

1. 通过 Github Action 监听 push 事件,然后在 Action 中调用 Gemini API 生成总结,最后将总结的内容插入到文档的 front matter 中。
2. 通过 Github Bot 监听 Github Webhook 事件,然后在 Bot 中调用 Gemini API 生成总结,最后将总结的内容插入到文档的 front matter 中。
3. 直接在页面上请求 Gemini API 生成总结然后展示在页面上。

首先第三种方案由于博客是静态页面,出于安全考虑没办法将 API KEY 直接发送给客户端,所以这种方案直接排除了。
第一种方案虽然可行但是没有第二种方案能实现的功能多,所以我最后选择了第二种方案。

于是我自己开发了一个 Github 机器人来完成自动化总结的功能, [项目地址](https://github.com/3Alan/docs-ai-bot)

## 部署机器人

本来想使用 Vercel 部署的,可是 Vercel 的免费版的 Serverless Function 有 10s 的超时时间,而总结的时间是远超过 10s 的,我总结 55 篇文章大概耗费了 3 分钟。在调研了一些服务后,我最后使用了 [Zeabur](https://zeabur.com?referralCode=3Alan) 的免费计划来部署,不过它的免费计划针对一些服务可能会将其无故关闭,后面看是否能找到一些能够白嫖的服务。
![20240114215619](https://raw.githubusercontent.com/3Alan/images/master/img/20240114215619.png)

### 部署到 Zeabur

直接使用下面我创建好的模版

[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/templates/EZOGJM?referralCode=3Alan)

![20240114220851](https://raw.githubusercontent.com/3Alan/images/master/img/20240114220851.png)

### 创建 Github App

首先将[项目代码](https://github.com/3Alan/docs-ai-bot)拉取下来,然后执行以下命令

```md
# Install dependencies

npm install

# Run the bot

npm start
```

打开 `http://localhost:3000`
![20240114221333](https://raw.githubusercontent.com/3Alan/images/master/img/20240114221333.png)
注册属于你自己的 bot
![20240114221511](https://raw.githubusercontent.com/3Alan/images/master/img/20240114221511.png)
![20240114221600](https://raw.githubusercontent.com/3Alan/images/master/img/20240114221600.png)

完成这些步骤后在你的项目中会出现一个 `.env` 文件

![20240114221743](https://raw.githubusercontent.com/3Alan/images/master/img/20240114221743.png)

将这个文件中的环境变量内容复制到 Zeabur 的环境变量中
![20240114221904](https://raw.githubusercontent.com/3Alan/images/master/img/20240114221904.png)

### 获取 Gemini API KEY

打开[地址](https://makersuite.google.com/app/prompts/new_freeform)

![20240114222123](https://raw.githubusercontent.com/3Alan/images/master/img/20240114222123.png)

将这个 API KEY 添加到 Zeabur 的环境变量 `GEMINI_API_KEY` 中,完成这些操作后 **重新部署一次**

![20240114222317](https://raw.githubusercontent.com/3Alan/images/master/img/20240114222317.png)

### 获取 Webhook 地址

![20240114222515](https://raw.githubusercontent.com/3Alan/images/master/img/20240114222515.png)

将该地址回填到 Github App 的 Webhook 地址中。

1. 访问 https://github.com/settings/apps
2. 找到你刚才创建的 Github App,点击 Edit
3. 填入你的 Webhook 地址
4. 点击保存

![20240114222809](https://raw.githubusercontent.com/3Alan/images/master/img/20240114222809.png)

到此所有安装工作全部完成

## 触发总结所有博文

首先新建一个 issue,然后给你这个 issue 添加一个 `summarizer` label,这样机器人就会开始总结你的博文了,具体完成的速度看你的仓库中有多少文章,我 55 篇文章大概花费了 3 分钟时间。

![20240114223129](https://raw.githubusercontent.com/3Alan/images/master/img/20240114223129.png)

## 添加总结组件

安装三方库

```
yarn add typed.js
```

首先确保你之前 Swizzling 过 `DocItem/Layout``BlogPostPage` 组件,具体步骤参考 [这篇文章](/posts/blog-guides/docusaurus-comment#swizzling-docusaurus-内部组件),这里我不在过多赘述。
39 changes: 39 additions & 0 deletions src/components/aiSummary/index.scss
@@ -0,0 +1,39 @@
$namespace: ai-summary;

.#{$namespace} {
margin-bottom: 20px;

&-button {
background-color: #242428;
outline: none;
border: none;
cursor: pointer;
padding: 6px 10px;
border-radius: 4px;
display: flex;
align-items: center;

span {
margin-left: 5px;
}

&:hover {
background-color: #353538;
}
}

&-title {
font-size: 15px;
margin-bottom: 8px;
display: flex;
align-items: center;
}

&-content {
padding: 10px;
font-size: 14px;
background-color: rgb(36, 36, 40);
border: 1px solid var(--ifm-surface-border-color);
border-radius: 6px;
}
}
55 changes: 55 additions & 0 deletions src/components/aiSummary/index.tsx
@@ -0,0 +1,55 @@
import React, { useEffect, useState } from 'react';
import { FaMagic } from 'react-icons/fa';
import './index.scss';
import Typed from 'typed.js';

const cls = 'ai-summary';

export function AiSummary({ content }) {
const [show, setShow] = useState(false);
const el = React.useRef(null);

useEffect(() => {
if (!show) {
return;
}

const typed = new Typed(el.current, {
strings: [content],
startDelay: 300,
typeSpeed: 15,
showCursor: false
});

return () => {
// Destroy Typed instance during cleanup to stop animation
typed.destroy();
};
}, [show]);

return (
<div className={cls}>
{!show && (
<button
className={`${cls}-button`}
onClick={() => {
setShow(true);
}}
>
<FaMagic color="#66adff" />
<span>AI 总结</span>
</button>
)}

{show && (
<div className={`${cls}-content`}>
<div className={`${cls}-title`}>
<FaMagic style={{ marginRight: 5 }} color="#66adff" /> AI 总结
</div>

<span ref={el}></span>
</div>
)}
</div>
);
}
7 changes: 0 additions & 7 deletions src/components/wave/index.scss

This file was deleted.

42 changes: 0 additions & 42 deletions src/components/wave/index.tsx

This file was deleted.

4 changes: 1 addition & 3 deletions src/pages/index.tsx
@@ -1,12 +1,10 @@
import React, { FC, ReactNode } from 'react';
import React from 'react';
import { VscGithubInverted, VscNotebook } from 'react-icons/vsc';
import './index.scss';
import Projects from '../components/homPageProjects';
import RecentBlogs, { RecentBlogItem } from '../components/homePageRecentBlogs';
import { useAllPluginInstancesData } from '@docusaurus/useGlobalData';
import clsx from 'clsx';
import Typed from 'typed.js';
import Wave from '../components/wave';
import Button from '../components/button';
import { FaLanguage } from 'react-icons/fa';
import { useLocation } from '@docusaurus/router';
Expand Down
3 changes: 3 additions & 0 deletions src/theme/BlogPostPage/index.tsx
Expand Up @@ -20,6 +20,7 @@ import License from '../../components/license';
import { OutDated } from '../../components/outdated';
import getFormatDate from '../../utils/getFormatDate';
import { Wip } from '@site/src/components/wip';
import { AiSummary } from '@site/src/components/aiSummary';

function BlogPostPageContent({
children
Expand All @@ -35,6 +36,7 @@ function BlogPostPageContent({
toc_max_heading_level: tocMaxHeadingLevel,
hide_comment: hideComment,
out_dated: outDated,
summary,
wip,
date,
updated
Expand All @@ -52,6 +54,7 @@ function BlogPostPageContent({
}
>
<BlogPostItem>
{summary && <AiSummary content={summary} />}
{outDated && <OutDated date={getFormatDate(updated || date)} />}
{wip && <Wip />}
{children}
Expand Down
11 changes: 10 additions & 1 deletion src/theme/DocItem/Layout/index.tsx
Expand Up @@ -18,6 +18,7 @@ import Comment from '../../../components/comment';
import { OutDated } from '../../../components/outdated';
import getFormatDate from '../../../utils/getFormatDate';
import { Wip } from '@site/src/components/wip';
import { AiSummary } from '@site/src/components/aiSummary';

/**
* Decide if the toc should be rendered, on mobile or desktop viewports
Expand Down Expand Up @@ -46,7 +47,14 @@ function useDocTOC() {
export default function DocItemLayout({ children }: Props): JSX.Element {
const docTOC = useDocTOC();
const { frontMatter } = useDoc();
const { hide_comment: hideComment, out_dated: outDated, wip, date, updated } = frontMatter;
const {
hide_comment: hideComment,
out_dated: outDated,
wip,
date,
updated,
summary
} = frontMatter;

return (
<div className="row">
Expand All @@ -59,6 +67,7 @@ export default function DocItemLayout({ children }: Props): JSX.Element {
{docTOC.mobile}
<DocItemContent>
<>
{summary && <AiSummary content={summary} />}
{outDated && <OutDated date={getFormatDate(updated || date)} />}
{wip && <Wip />}
{children}
Expand Down

1 comment on commit ce04cf2

@vercel
Copy link

@vercel vercel bot commented on ce04cf2 Jan 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

site – ./

site-git-main-alanwang.vercel.app
site-alanwang.vercel.app
alanwang.site
www.alanwang.site

Please sign in to comment.