【AskAI系列课程】:P4.将AI助手集成到Astro网站前端
详细分享如何在Astro项目中集成AI问答功能,包括React组件集成、AntDesign X框架应用、流式对话实现等完整前端方案。
这是【AskAI系列课程】的第4课:将前面构建的智能AI助手真正集成到网站前端,让访客能够直接与AI对话。

在前面的课程中,我们已经完成了整个后端服务的搭建:
- P1课:接入Agno框架,创建智能Assistant
- P2课:通过脚本批量上传文档到阿里云百炼知识库
- P3课:实现从百炼知识库检索并回答用户问题
现在到了最激动人心的时刻——让AI助手真正”现身”在我们的网站上,为访客提供智能问答服务!
你可以在我的网站右下角看到这个可爱的AI助手图标,点击即可体验。
整体架构设计#
在开始具体实现之前,让我先展示整个系统的架构设计。这个AI助手系统主要由三个部分组成:
这个架构的核心思路是将AI能力封装为后端服务,前端通过HTTP流式请求获取实时回答,同时利用知识库提供准确的上下文信息。
后端架构回顾#
在开始前端集成之前,让我们快速回顾一下后端架构。在前面的课程中,我们已经构建了一个完整的智能助手后端:
核心组件#
- Agno Agent:基于P1课搭建的智能助手框架
- 知识库检索器:基于P3课实现的百炼知识库集成
- FastAPI服务:通过AgentOS提供的Web API接口
# 核心Agent配置(来自前面的课程)
assistant = Agent(
name="Assistant",
id="fastcar-assistant",
model=OpenAILike(
id=os.getenv("ARK_BIG_THINKING_MODEL"),
api_key=os.getenv("ARK_API_KEY"),
base_url=os.getenv("ARK_BASE_URL"),
),
instructions=[
"你是fastcar.fun网站的AI助手,请根据用户的问题给出回答。",
"你的回答应该简洁且有礼貌。",
],
markdown=True,
db=db,
knowledge_retriever=smart_bailian_retriever, # P3课实现的检索器
search_knowledge=True,
)pythonAPI接口#
后端服务提供了标准的对话接口:
- POST
/agents/fastcar-assistant/runs- 发起对话 - 支持流式输出 - 实时返回AI回答
- 自动知识检索 - 根据问题自动调用知识库
现在我们的任务是在前端实现一个用户友好的界面来调用这些API。
前端集成:在Astro中使用React组件#
Astro + React 集成配置#
要在Astro项目中使用React组件,首先需要正确配置:
// astro.config.ts
import react from '@astrojs/react'
import { defineConfig } from 'astro/config'
export default defineConfig({
integrations: [
react(), // 启用React集成
// 其他集成...
],
// 其他配置...
})typescript{
"dependencies": {
"@astrojs/react": "4.3.1",
"@ant-design/x": "1.6.1",
"react": "19.1.1",
"react-dom": "19.1.1"
}
}json这里需要注意的是,我选择了最新的 React 19 版本,配合 AntDesign X 1.6.1,这个组合提供了最佳的开发体验和性能。
组件架构设计#
前端组件采用三层架构:
Astro组件层] --> B[FloatingAskAI.tsx
React容器组件] B --> C[XChatUI.tsx
聊天界面组件] C --> D[AntDesign X组件
UI组件库] B --> E[拖拽逻辑] B --> F[位置存储] C --> G[聊天状态管理] C --> H[主题适配] C --> I[API通信] style A fill:#e3f2fd style B fill:#fff3e0 style C fill:#f3e5f5 style D fill:#e8f5e8
Astro组件包装器#
首先是最外层的Astro组件,它的作用是将React组件嵌入到Astro页面中:
---
import FloatingAskAIComponent from './FloatingAskAI.tsx'
---
<!-- 悬浮 AskAI 按钮 -->
<FloatingAskAIComponent client:load />astro这里的 client:load 指令告诉Astro在页面加载时立即在客户端渲染这个React组件。这对于交互性强的组件是必需的。
React容器组件实现#
接下来是核心的React容器组件,它管理着悬浮按钮的所有行为:
import React, { useState, useRef, useEffect, Suspense, lazy } from 'react'
const XChatUI = lazy(() => import('./XChatUI'))
const FloatingAskAI: React.FC = () => {
const [open, setOpen] = useState(false)
const [position, setPosition] = useState<Position>({ x: 0, y: 0 })
const [isDragging, setIsDragging] = useState(false)
// 位置恢复和保存逻辑
useEffect(() => {
const savedPosition = localStorage.getItem('askAI-position')
if (savedPosition) {
const parsed = JSON.parse(savedPosition)
// 验证位置是否在视窗范围内
if (parsed.x >= 0 && parsed.x <= window.innerWidth - 68) {
setPosition(parsed)
}
} else {
// 默认位置:右下角
setPosition({
x: window.innerWidth - 68,
y: window.innerHeight - 68
})
}
}, [])
// 拖拽处理逻辑...
return (
<>
<button
className="fixed z-50 flex h-12 w-12 items-center justify-center rounded-full bg-white shadow-lg border border-gray-200"
style={{
left: `${position.x}px`,
top: `${position.y}px`,
cursor: isDragging ? 'grabbing' : 'grab',
}}
onClick={() => setOpen(true)}
>
<img src="https://img.icons8.com/fluency/96/bard.png" alt="AskAI" />
</button>
{open && (
<Suspense fallback={null}>
<XChatUI onClose={() => setOpen(false)} />
</Suspense>
)}
</>
)
}typescript这个组件有几个巧妙的设计:
- 懒加载:使用
lazy()和Suspense来懒加载聊天界面,避免首屏加载时间过长 - 位置持久化:将按钮位置保存到 localStorage,用户下次访问时会记住位置
- 拖拽功能:支持用户自由拖拽按钮到合适的位置
- 响应式设计:自动处理不同屏幕尺寸下的位置限制
AntDesign X 聊天界面#
聊天界面是用户体验的核心,我选择了蚂蚁集团开源的 AntDesign X 框架:
import { Bubble, Sender, useXAgent, useXChat, Welcome, XProvider } from '@ant-design/x'
import type { BubbleProps } from '@ant-design/x'
const XChatUI: React.FC<{ onClose: () => void }> = ({ onClose }) => {
const agent = useRealAgent()
const { messages, onRequest } = useXChat({ agent })
// Markdown渲染配置
const renderMarkdown: BubbleProps['messageRender'] = (content) => {
return (
<Typography>
<div dangerouslySetInnerHTML={{ __html: md.render(content) }} />
</Typography>
)
}
return (
<XProvider theme={themeConfig}>
<div className="fixed inset-0 z-[60] flex items-center justify-center">
<div className="absolute inset-0 bg-black/40" onClick={onClose} />
<div className="relative mx-auto h-[100svh] w-[100svw] md:h-[80vh] md:max-w-4xl md:w-full md:rounded-2xl bg-background">
{/* 聊天内容区域 */}
<div className="min-h-0 flex-1 overflow-y-auto p-4">
{messages.length === 0 && (
<Welcome
icon="✨"
title="欢迎来到我的网站!"
description="我是您的智能助手,可以帮您解答各种关于我的网站的问题。"
/>
)}
{messages.length > 0 && (
<Bubble.List
items={messages.map(msg => ({
content: msg.message,
placement: msg.status === 'local' ? 'end' : 'start',
messageRender: msg.status === 'local' ? undefined : renderMarkdown
}))}
/>
)}
</div>
{/* 输入区域 */}
<div className="border-t p-3">
<Sender
placeholder="请输入你的问题…"
onSubmit={(text) => {
if (text.trim()) {
onRequest(text)
}
}}
/>
</div>
</div>
</div>
</XProvider>
)
}typescript为什么选择AntDesign X?#
在选择聊天界面框架时,我考虑了多个方案,最终选择AntDesign X有以下原因:
- 专业的聊天体验:AntDesign X是专门为AI对话场景设计的,提供了流式输出、加载状态、错误处理等开箱即用的功能
- React 19兼容性:与最新的React版本有良好的兼容性
- 主题适配:支持深色/浅色主题自动切换,与我的网站主题系统无缝集成
- Markdown支持:内置支持Markdown渲染,AI回答可以包含格式化文本
- TypeScript支持:完整的类型定义,开发体验很好
API通信:实现流式对话#
流式请求处理#
为了提供流畅的对话体验,我实现了基于Server-Sent Events的流式通信:
export const sendStreamRequest = async (
message: string,
callbacks: StreamCallbacks
): Promise<void> => {
const { onMessage, onError, onComplete } = callbacks
const formData = new URLSearchParams()
formData.set('message', message)
formData.set('stream', 'true')
try {
const response = await fetch(apiEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
},
body: formData.toString(),
})
const reader = response.body?.getReader()
const decoder = new TextDecoder()
let buffer = ''
while (true) {
const { done, value } = await reader.read()
if (done) {
onComplete?.()
break
}
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split('\n')
buffer = lines.pop() || ''
for (const line of lines) {
if (line.startsWith('data:')) {
const jsonText = line.slice(5).trim()
try {
const parsed = JSON.parse(jsonText)
if (parsed.event === 'RunContent' && parsed.content) {
onMessage?.(parsed.content)
}
} catch (e) {
console.warn('Failed to parse SSE data:', e)
}
}
}
}
} catch (error) {
onError?.(error as Error)
}
}typescript这个实现的关键在于正确处理SSE数据流的分包问题,确保每个JSON事件都能被正确解析和处理。
环境配置管理#
在Astro项目中,环境变量的处理需要特别注意:
const getApiBaseUrl = (): string => {
// 在Astro中使用 import.meta.env 访问环境变量
const serverUrl = import.meta.env.PUBLIC_AGNO_SERVER
if (serverUrl) {
return serverUrl
}
// 开发环境默认值
return 'http://localhost:7777'
}typescript注意这里的环境变量必须以 PUBLIC_ 开头,才能在客户端代码中访问。
主题集成:无缝融入网站设计#
动态主题适配#
我的网站支持深色和浅色主题切换,AI助手界面也需要跟随主题变化:
const XChatUI: React.FC = ({ onClose }) => {
const [isDark, setIsDark] = useState(false)
// 主题检测
useEffect(() => {
const detectTheme = () => {
const isDarkMode = document.documentElement.classList.contains('dark')
setIsDark(isDarkMode)
}
detectTheme()
// 监听主题变化
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
detectTheme()
}
})
})
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class']
})
return () => observer.disconnect()
}, [])
// 动态主题配置
const themeConfig = useMemo(() => ({
token: {
colorBgContainer: isDark ? 'hsl(var(--card))' : 'hsl(var(--background))',
colorText: isDark ? 'hsl(var(--card-foreground))' : 'hsl(var(--foreground))',
colorPrimary: isDark ? 'hsl(var(--primary))' : 'hsl(var(--primary))',
// 更多主题配置...
}
}), [isDark])
return (
<XProvider theme={themeConfig}>
{/* 聊天界面内容 */}
</XProvider>
)
}typescript这样当用户切换网站主题时,AI助手界面也会自动跟随变化,保持视觉一致性。
CSS变量系统#
为了更好地集成主题,我利用了CSS变量系统:
.dark-input {
background-color: hsl(240 10% 3.9%);
color: hsl(0 0% 98%);
border-color: hsl(240 3.7% 19.9%);
}
.light-input {
background-color: hsl(210 33% 99%);
color: hsl(240 10% 3.9%);
border-color: hsl(240 5.9% 88%);
}css这些样式类会根据主题状态动态应用,确保界面元素在不同主题下都有良好的可读性。
数据流全链路分析#
让我通过一个完整的交互流程来展示整个系统的数据流:
这个流程有几个关键特点:
- 实时性:从用户提问到看到回答开始出现,通常在1-2秒内
- 渐进式:回答是逐字显示的,用户可以实时看到AI的思考过程
- 准确性:通过知识库检索,确保回答基于真实的网站内容
- 体验性:整个交互过程流畅自然,就像在和真人对话
开发环境配置#
后端服务启动#
确保你的后端服务正在运行:
# 进入后端目录
cd ask-ai-server
# 启动开发服务器
python fastcar_os.pybash服务默认运行在 http://localhost:7777,提供API接口给前端调用。
环境变量配置#
在项目根目录的 .env 文件中配置前端环境变量:
# 后端API服务地址
PUBLIC_AGNO_SERVER=http://localhost:7777bash注意:在Astro中,客户端可访问的环境变量必须以 PUBLIC_ 开头。
跨域配置#
确保后端服务已正确配置CORS:
app.add_middleware(
CORSMiddleware,
allow_origins=["https://fastcar.fun", "http://localhost:4321"],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["*"],
)python这样前端就能正常与后端API通信了。
总结与思考#
通过这次完整的AI助手集成项目,我深刻体会到了现代Web开发中前后端分离架构的强大和灵活性。这个项目不仅让我的网站变得更加智能和互动,也让我对以下几个技术方向有了更深的理解:
技术架构方面:
- Astro的客户端渲染指令让我们能够精确控制哪些组件需要客户端交互
- React 19与AntDesign X的结合提供了出色的开发体验
- 流式API设计大大提升了用户体验
用户体验方面:
- 可拖拽的悬浮按钮让用户可以自定义界面布局
- 渐进式回答显示增加了对话的真实感
- 主题适配确保了视觉一致性
部署运维方面:
- 容器化部署简化了生产环境管理
- 环境变量配置让不同环境的切换变得简单
- 健康检查和日志管理提升了系统可靠性
这个完整的AskAI系列到此告一段落,我们从零开始构建了一个完整的智能问答系统:
P1课 - 搭建了基于Agno的智能助手框架
P2课 - 实现了文档自动同步到知识库的脚本
P3课 - 完成了知识库检索功能的集成
P4课 - 将AI助手真正部署到了网站前端
通过这个系列,我们不仅实现了一个实用的功能,更重要的是掌握了现代AI应用开发的完整技术栈。
接下来,我计划继续完善这个系统,比如增加对话历史保存、多轮对话优化、语音输入支持等功能。同时也会探索更多AI应用场景,如会议助手、客户信息收集、日志分析等。
如果你对这个系列感兴趣,或者有任何问题想要交流,欢迎通过右下角的AI助手直接与我对话,或者在评论区留言讨论。让我们一起探索AI时代Web开发的无限可能!