【AskAI系列课程】:P4.将AI助手集成到Astro网站前端
这是【AskAI系列课程】的第4课:将前面构建的智能AI助手真正集成到网站前端,让访客能够直接与AI对话。
在前面的课程中,我们已经完成了整个后端服务的搭建:
- P1课:接入Agno框架,创建智能Assistant
- P2课:通过脚本批量上传文档到阿里云百炼知识库
- P3课:实现从百炼知识库检索并回答用户问题
现在到了最激动人心的时刻——让AI助手真正"现身"在我们的网站上,为访客提供智能问答服务!
你可以在我的主页右下角看到这个可爱的AI助手图标,点击即可体验:https://fastcar.fun
完整源码:https://github.com/Match-Yang/fastcar-web
整体架构设计
在开始具体实现之前,让我先展示整个系统的架构设计。这个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,
)
API接口
后端服务提供了标准的对话接口:
- 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集成// 其他集成...],// 其他配置...
})
{"dependencies": {"@astrojs/react": "4.3.1","@ant-design/x": "1.6.1","react": "19.1.1","react-dom": "19.1.1"}
}
这里需要注意的是,我选择了最新的 React 19 版本,配合 AntDesign X 1.6.1,这个组合提供了最佳的开发体验和性能。
组件架构设计
前端组件采用三层架构:
Astro组件包装器
首先是最外层的Astro组件,它的作用是将React组件嵌入到Astro页面中:
---
import FloatingAskAIComponent from './FloatingAskAI.tsx'
---<!-- 悬浮 AskAI 按钮 -->
<FloatingAskAIComponent client:load />
这里的 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 (<><buttonclassName="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>)}</>)
}
这个组件有几个巧妙的设计:
- 懒加载:使用
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 && (<Welcomeicon="✨"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"><Senderplaceholder="请输入你的问题…"onSubmit={(text) => {if (text.trim()) {onRequest(text)}}}/></div></div></div></XProvider>)
}
为什么选择AntDesign X?
在选择聊天界面框架时,我考虑了多个方案,最终选择AntDesign X有以下原因:
- 专业的聊天体验:AntDesign X是专门为AI对话场景设计的,提供了流式输出、加载状态、错误处理等开箱即用的功能
- React 19兼容性:与最新的React版本有良好的兼容性
- 主题适配:支持深色/浅色主题自动切换,与我的网站主题系统无缝集成
- Markdown支持:内置支持Markdown渲染,AI回答可以包含格式化文本
- TypeScript支持:完整的类型定义,开发体验很好
API通信:实现流式对话
流式请求处理
为了提供流畅的对话体验,我实现了基于Server-Sent Events(SSE)的流式通信:
export const sendStreamRequest = async (message: string,callbacks: StreamCallbacks
): Promise<void> => {const { onMessage, onError, onComplete } = callbacksconst 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)}
}
这个实现的关键在于正确处理SSE数据流的分包问题,确保每个JSON事件都能被正确解析和处理。
环境配置管理
在Astro项目中,环境变量的处理需要特别注意:
const getApiBaseUrl = (): string => {// 在Astro中使用 import.meta.env 访问环境变量const serverUrl = import.meta.env.PUBLIC_AGNO_SERVERif (serverUrl) {return serverUrl}// 开发环境默认值return 'http://localhost:7777'
}
注意这里的环境变量必须以 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>)
}
这样当用户切换网站主题时,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%);
}
这些样式类会根据主题状态动态应用,确保界面元素在不同主题下都有良好的可读性。
数据流全链路分析
让我通过一个完整的交互流程来展示整个系统的数据流:
这个流程有几个关键特点:
- 实时性:从用户提问到看到回答开始出现,通常在1-2秒内
- 渐进式:回答是逐字显示的,用户可以实时看到AI的思考过程
- 准确性:通过知识库检索,确保回答基于真实的网站内容
- 体验性:整个交互过程流畅自然,就像在和真人对话
开发环境配置
后端服务启动
确保你的后端服务正在运行:
# 进入后端目录
cd ask-ai-server# 启动开发服务器
python fastcar_os.py
服务默认运行在 http://localhost:7777
,提供API接口给前端调用。
环境变量配置
在项目根目录的 .env
文件中配置前端环境变量:
# 后端API服务地址
PUBLIC_AGNO_SERVER=http://localhost:7777
注意:在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=["*"],
)
这样前端就能正常与后端API通信了。
总结与思考
通过这次完整的AI助手集成项目,我深刻体会到了现代Web开发中前后端分离架构的强大和灵活性。这个项目不仅让我的网站变得更加智能和互动,也让我对以下几个技术方向有了更深的理解:
技术架构方面:
- Astro的客户端渲染指令让我们能够精确控制哪些组件需要客户端交互
- React 19与AntDesign X的结合提供了出色的开发体验
- 流式API设计大大提升了用户体验
用户体验方面:
- 可拖拽的悬浮按钮让用户可以自定义界面布局
- 渐进式回答显示增加了对话的真实感
- 主题适配确保了视觉一致性
部署运维方面:
- 容器化部署简化了生产环境管理
- 环境变量配置让不同环境的切换变得简单
- 健康检查和日志管理提升了系统可靠性
这个完整的AskAI系列到此告一段落,我们从零开始构建了一个完整的智能问答系统:
P1课 - 搭建了基于Agno的智能助手框架
P2课 - 实现了文档自动同步到知识库的脚本
P3课 - 完成了知识库检索功能的集成
P4课 - 将AI助手真正部署到了网站前端
通过这个系列,我们不仅实现了一个实用的功能,更重要的是掌握了现代AI应用开发的完整技术栈。
接下来,我计划继续完善这个系统,比如增加对话历史保存、多轮对话优化、语音输入支持等功能。同时也会探索更多AI应用场景,如会议助手、客户信息收集、日志分析等。
如果你对这个系列感兴趣,或者有任何问题想要交流,欢迎通过右下角的AI助手直接与我对话,或者在评论区留言讨论。让我们一起探索AI时代Web开发的无限可能!