为 Astro 静态博客集成 AI 助手:从想法到实现

发表于 2026-01-28 15:00 3356 字 17 min read

吕小布 avatar

吕小布

The first step is to establish that something is possible; then probability will occur.

暂无目录
记录如何为纯前端静态博客集成 AI 助手功能,实现一键摘要、选中解释、自由提问等功能,提升文章阅读体验。

本文记录了我为 Astro 静态博客集成 AI 助手的完整过程,从最初的想法萌生,到最终实现一个不影响阅读体验的侧边栏 AI 助手。

为什么想做这个功能

说实话,最开始想做这个功能,纯粹是因为觉得很酷

看到各行各业都在拥抱 AI,从办公软件到代码编辑器,从搜索引擎到社交媒体,AI 正在渗透到我们生活的方方面面。作为一个技术博客,如果能有一个 AI 助手帮助读者更好地理解文章内容,岂不是很有意思?

除了「酷」之外,我认为这个功能确实能提升阅读体验

  • 一键摘要:长文章可以快速了解核心要点
  • 选中解释:遇到不懂的术语,选中就能获得解释
  • 自由提问:针对文章内容提出任何问题

这不就是每个读者都想要的「私人助教」吗?

最初的担忧:静态博客能做吗?

在动手之前,我最大的担忧是:这是一个纯前端静态博客,没有后端服务器,怎么调用 AI API?

众所周知,调用 AI API 需要 API Key,而 API Key 是不能暴露在前端代码中的。传统的做法是通过后端服务器中转请求,但我的博客是用 Astro 构建的静态站点,部署在 Vercel 上,根本没有传统意义上的「后端」。

带着这个疑问,我让 Claude 分析了我的项目结构。结果发现,Astro 支持 API Routes,配合 Vercel 的 Serverless Functions,完全可以实现服务端逻辑!

用户请求 → Vercel Edge/Serverless → DeepSeek API → 流式响应返回

API Key 安全地存储在 Vercel 的环境变量中,前端只需要调用我们自己的 API 端点,完美解决了安全问题。

技术方案设计

确定可行性后,我开始设计具体的技术方案。

核心功能

功能说明
一键摘要点击按钮,AI 生成文章核心要点
选中解释选中文本后出现按钮,解释选中内容
自由提问输入框,针对文章内容提问

技术选型

  • AI 服务:DeepSeek API(性价比高,支持中文,32K 上下文)
  • 状态管理:Nanostores(轻量级,适合 Astro)
  • 动画效果:Motion(流畅的滑入滑出动画)
  • Markdown 渲染:marked(AI 回答支持 Markdown 格式)

交互设计

这是我花了最多心思的部分。最初的设计是点击按钮后弹出一个全屏模态框,但实际使用后发现体验很差——你没法一边看文章一边问 AI,每次都要关闭面板才能看原文。

最终方案是:

  • 桌面端:右侧固定侧边栏(384px 宽度),不遮挡文章内容
  • 移动端:全屏模态框(屏幕太小,没办法)

这样读者可以一边阅读文章,一边和 AI 对话,体验流畅多了。

实现过程

第 1 步:创建 API 路由

首先在 src/pages/api/ai/chat.ts 创建 API 端点:

import type { APIRoute } from 'astro';
import { callDeepSeekStream } from '@/lib/ai/deepseek';

export const prerender = false;

export const POST: APIRoute = async ({ request }) => {
  const apiKey = import.meta.env.DEEPSEEK_API_KEY;

  if (!apiKey) {
    return new Response(JSON.stringify({ error: 'API key not configured' }), {
      status: 500,
    });
  }

  const { action, content, title, query } = await request.json();
  const response = await callDeepSeekStream(apiKey, action, content, title, query);

  // 返回流式响应
  return new Response(response.body, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
    },
  });
};

关键点:

  • export const prerender = false 告诉 Astro 这是一个动态路由
  • 使用流式响应(SSE),实现打字机效果
  • API Key 从环境变量读取,不会暴露给前端

第 2 步:封装 DeepSeek 客户端

src/lib/ai/deepseek.ts 中封装 API 调用逻辑:

export function buildSystemPrompt(action: AIActionType): string {
  switch (action) {
    case 'summary':
      return `你是一个专业的文章摘要助手。请阅读文章内容,提取 3-5 个核心要点,用简洁的中文总结。
要求:
- 每个要点一句话
- 突出文章的核心观点和关键信息
- 使用 bullet points 格式`;

    case 'explain':
      return `你是一个知识解释助手。用户在阅读文章时选中了一段文字想要了解更多。
要求:
- 解释选中文字的含义
- 如果涉及专业术语,用通俗易懂的语言解释
- 结合文章上下文进行解释`;

    case 'ask':
      return `你是一个文章阅读助手。用户正在阅读一篇文章,并有一些问题。
要求:
- 基于文章内容回答用户的问题
- 如果问题超出文章范围,请说明并尽可能提供有用的信息`;
  }
}

针对不同的操作类型,设计了不同的 System Prompt,让 AI 的回答更加精准。

第 3 步:状态管理

使用 Nanostores 管理 AI 助手的状态:

import { atom } from 'nanostores';

// 面板开关状态
export const $aiPanelOpen = atom<boolean>(false);

// 消息历史
export const $aiMessages = atom<AIMessage[]>([]);

// 加载状态
export const $aiLoading = atom<boolean>(false);

// 当前文章内容
export const $currentArticleContent = atom<string>('');

Nanostores 的好处是可以在 Astro 组件和 React 组件之间共享状态,非常适合这种混合架构。

第 4 步:实现选中文本弹窗

这是一个有趣的交互:用户选中文章中的文本后,在选中位置上方出现一个「AI 解释」按钮。

const handleMouseUp = useCallback(() => {
  const selection = window.getSelection();
  if (!selection || selection.isCollapsed) {
    clearSelection();
    return;
  }

  const text = selection.toString().trim();
  if (!text || text.length < 2) {
    clearSelection();
    return;
  }

  // 检查选中是否在文章容器内
  const container = document.querySelector('article');
  if (!container?.contains(selection.anchorNode)) {
    clearSelection();
    return;
  }

  // 获取选中位置,显示弹窗
  const rect = selection.getRangeAt(0).getBoundingClientRect();
  setSelectionPosition({ x: rect.left + rect.width / 2, y: rect.top - 10 });
  setSelectedText(text);
}, []);

第 5 步:Markdown 渲染

AI 的回答通常包含 Markdown 格式(列表、粗体、代码等),需要正确渲染:

import { marked } from 'marked';

function MarkdownContent({ content }: { content: string }) {
  const html = useMemo(() => {
    return marked.parse(content, { async: false }) as string;
  }, [content]);

  return (
    <div
      className="prose prose-sm dark:prose-invert"
      dangerouslySetInnerHTML={{ __html: html }}
    />
  );
}

遇到的问题和解决方案

问题 1:面板全屏遮挡文章

现象:最初实现的 AI 面板是全屏的,无法同时查看文章和 AI 回答。

优化前的全屏效果

解决方案:修改为桌面端侧边栏模式,固定宽度 384px,从导航栏下方开始。

className={cn(
  'fixed right-0 z-[70] flex flex-col bg-background shadow-xl',
  'top-16 h-[calc(100vh-4rem)] w-96 border-l border-border',
  'md:top-0 md:h-full md:w-full md:border-l-0', // 移动端全屏
)}

这里有个坑:这个项目的 Tailwind 断点配置是反向的md: 表示小于等于 768px,而不是标准的大于等于。所以默认样式是桌面端的,md: 前缀用于移动端覆盖。

问题 2:AI 回答显示原始 Markdown 符号

现象:AI 返回的内容包含 **粗体** 等 Markdown 语法,但直接显示为纯文本。

解决方案:使用 marked 库将 Markdown 转换为 HTML,配合 Tailwind 的 prose 类进行样式美化。

问题 3:组件没有渲染

现象:点击 AI 按钮没有任何反应。

原因:把组件放在了一个不存在的 slot 里(slot="right-sider"),导致组件根本没有被渲染。

解决方案:移除 slot 属性,让组件作为固定定位元素独立渲染。

最终效果

经过多次优化,最终实现了一个体验良好的 AI 助手:

最终效果

功能特点

功能说明
一键摘要快速生成文章核心要点
选中解释选中文本即可获得解释
自由提问针对文章内容自由提问
流式输出打字机效果,体验流畅
Markdown 渲染支持列表、粗体、代码等格式
侧边栏模式不遮挡文章,可同时阅读

部署配置

环境变量

在 Vercel 项目设置中添加:

NameValue
DEEPSEEK_API_KEY你的 DeepSeek API Key

获取 API Key

  1. 访问 DeepSeek 开放平台
  2. 注册账号并登录
  3. 在 API Keys 页面创建新的 Key
  4. 复制 Key 到 Vercel 环境变量

DeepSeek 的定价非常友好,对于个人博客来说,成本几乎可以忽略不计。

成本分析:个人博客用得起吗?

在决定集成 AI 功能之前,很多人可能会担心:调用 AI API 会不会很贵?

这是一个非常实际的问题。毕竟个人博客不是商业项目,没有收入来源,如果每个月要花几十上百块在 API 调用上,那确实不太划算。

实际花费数据

我在开发和测试过程中,进行了大量的 API 调用来验证功能。来看看实际的花费:

API 调用次数

在开发测试阶段,我一共调用了 11 次 API,包括:

  • 多次测试一键摘要功能
  • 测试选中解释功能
  • 测试自由提问功能
  • 调试流式响应

这 11 次调用的总花费是多少呢?

API 调用花费

0.03 元。没错,三分钱。

为什么这么便宜?

DeepSeek 的定价策略非常亲民:

模型输入价格输出价格
deepseek-chat¥1 / 百万 tokens¥2 / 百万 tokens

一篇 3000 字的中文文章,大约是 2000-3000 tokens。加上 System Prompt 和 AI 的回答,一次完整的对话大约消耗 4000-6000 tokens。

按这个计算:

  • 单次调用成本:约 0.003 元(不到一分钱)
  • 每天 10 次调用:约 0.03 元
  • 每月 300 次调用:约 1 元

对于一个个人博客来说,每月 300 次 AI 调用已经是非常高的使用量了。即使你的博客流量很大,每月的 API 成本也很难超过 10 块钱。

成本控制建议

虽然成本已经很低,但还是有一些优化空间:

  1. 限制文章长度:对于超长文章,可以只发送前 N 个字符
  2. 缓存常见问题:对于「一键摘要」,可以考虑缓存结果
  3. 设置调用频率限制:防止恶意刷接口

目前我没有做这些优化,因为成本实在太低了,优化的收益不大。

结论

经济上完全可行。 对于个人博客来说,DeepSeek API 的成本几乎可以忽略不计。与其担心 API 费用,不如把精力放在如何提升用户体验上。

写在最后

从最初的「这能做吗?」到最终实现一个完整的 AI 助手,整个过程让我深刻体会到了 AI 时代编程的不同。

1 小时完成整个功能

说出来你可能不信,从零开始到功能完全可用,我只花了 1 个多小时

这在以前是不可想象的。如果让我自己从头写这个功能,光是研究 DeepSeek API 文档、设计组件结构、处理流式响应、调试各种边界情况,少说也要一两天。

但有了 Claude Code,一切都变得不一样了:

  1. 需求描述 → Claude 分析项目结构,确认技术可行性
  2. 方案设计 → Claude 给出完整的文件结构和实现思路
  3. 代码实现 → Claude 逐个文件编写代码
  4. 问题修复 → 遇到 bug 直接描述现象,Claude 定位并修复

整个过程我几乎没有手写代码,更多的是在「指挥」和「验收」。

提示词的重要性

在这个过程中,我深刻体会到:提示词的质量决定了 AI 的输出质量

好的提示词应该:

  • 描述清晰:不要说「帮我加个 AI 功能」,而是说「我想在文章页面添加一个 AI 助手,支持一键摘要、选中解释、自由提问三个功能」
  • 提供上下文:告诉 AI 你的项目用了什么技术栈、有什么限制条件
  • 分步骤推进:复杂功能拆成小步骤,一步一步来
  • 及时反馈:遇到问题立刻描述现象,让 AI 快速定位

比如当 AI 面板全屏遮挡文章时,我没有说「这个不对」,而是说「现在点击 AI 按钮后面板是全屏的,我希望桌面端是右侧侧边栏,不遮挡文章内容」。清晰的描述让 Claude 一次就改对了。

AI 时代的编程方式

以前遇到不会的技术,可能需要花几天时间查文档、看教程、踩坑。现在有了 AI 助手,我可以直接描述需求,让 AI 帮我分析可行性、设计方案、编写代码。我的角色从「写代码的人」变成了「指挥 AI 写代码的人」。

这并不意味着不需要学习了。相反,你需要:

  • 理解技术原理:才能判断 AI 的方案是否合理
  • 具备审美能力:才能设计出好的交互体验
  • 掌握调试技巧:AI 写的代码也会有 bug
  • 学会提问:好的提示词是高效协作的关键

但毫无疑问,AI 大大降低了技术实现的门槛。以前觉得「太难了做不了」的功能,现在可以大胆尝试。

我喜欢 AI,我喜欢代码,我喜欢用 AI 提升我的项目。 即使不会,也要用 AI 做出来——这或许就是 AI 时代编程的新姿势吧。

如果你也想给自己的博客加上 AI 助手,希望这篇文章能给你一些参考。有问题欢迎在评论区交流!

© 2024 - 2026 吕小布 @insist
Powered by theme astro-koharu · Inspired by Shoka