在人工智能技术日益成熟的今天,将先进的AI能力整合到Web应用中已成为提升用户体验的重要手段。本文将详细记录我基于DeepSeek API开发AI聊天助手前端的完整过程,包括技术选型、架构设计、核心功能实现以及开发过程中遇到的各种"坑"与解决方案。这个项目不仅实现了基本的聊天交互功能,还包含了许多优化细节,希望能为类似项目的开发者提供参考。
一、项目规划与技术选型
1.1 需求分析
在项目启动前,我明确了以下核心需求:
- 实时对话:实现用户与AI助手的自然语言交互
- 消息历史:完整记录并展示对话历史
- 响应式设计:适配不同尺寸的屏幕设备
- 良好的用户体验:包括加载状态、错误处理等
- 便捷的输入方式:支持回车发送、多行输入等
1.2 技术栈选择
经过调研对比,最终确定的技术栈如下:
- 框架:React 18 + TypeScript
- UI组件库:Material-UI (MUI) 5
- 状态管理:React Context API
- HTTP客户端:Axios
- 路由管理:React Router 6
- 构建工具:Create React App
选择这些技术的主要考虑因素包括:TypeScript的类型安全能减少运行时错误,MUI提供丰富的预制组件加速开发,React Router 6的新特性更适合现代应用架构。
二、项目初始化与架构设计
2.1 项目初始化
使用Create React App脚手架快速搭建项目基础:
bash
复制
lua
npx create-react-app ai-assistant-frontend --template typescript
遇到的第一个坑 :安装过程中出现You are running create-react-app 5.0.1, which is behind the latest release
警告。虽然不影响使用,但为了获得最新特性和安全补丁,我按照提示更新了工具:
bash
复制
sql
npm uninstall -g create-react-app
npm install -g create-react-app@latest
2.2 项目结构设计
经过多次迭代,最终形成的项目结构如下:
复制
csharp
src/
├── components/
│ ├── Message.tsx # 单条消息组件
│ ├── ChatInput.tsx # 输入框组件
│ ├── Header.tsx # 顶部导航
│ └── Loading.tsx # 加载动画
├── pages/
│ ├── ChatPage.tsx # 聊天主页面
│ └── HomePage.tsx # 欢迎页面
├── services/
│ └── api.ts # API服务封装
├── styles/
│ ├── global.css # 全局样式
│ └── theme.ts # MUI主题配置
├── contexts/ # 状态上下文
├── hooks/ # 自定义Hook
├── utils/ # 工具函数
├── App.tsx # 根组件
└── index.tsx # 入口文件
这种模块化结构使得代码更易于维护和扩展,特别是在后期添加新功能时优势明显。
三、核心功能实现
3.1 聊天界面实现
消息列表组件
Message.tsx
组件负责渲染单条消息,需要区分用户消息和AI回复的不同样式:
tsx
复制
typescript
interface MessageProps {
content: string;
isUser: boolean;
timestamp?: Date;
}
export const Message = ({ content, isUser, timestamp }: MessageProps) => {
return (
<Box display="flex" justifyContent={isUser ? 'flex-end' : 'flex-start'} mb={2}>
{!isUser && (
<Avatar sx={{ mr: 1, bgcolor: 'primary.main' }}>
<SmartToy />
</Avatar>
)}
<Box maxWidth="80%">
<Paper
elevation={1}
sx={{
p: 2,
bgcolor: isUser ? 'primary.main' : 'background.paper',
color: isUser ? 'primary.contrastText' : 'text.primary'
}}
>
<Typography variant="body1" whiteSpace="pre-wrap">
{content}
</Typography>
</Paper>
{timestamp && (
<Typography variant="caption" color="text.secondary" mt={0.5}>
{format(timestamp, 'HH:mm')}
</Typography>
)}
</Box>
</Box>
);
};
样式设计坑 :最初直接使用div布局导致消息气泡在不同屏幕尺寸下显示异常。通过引入MUI的Paper
组件并设置maxWidth
属性解决了这个问题。
聊天输入框
ChatInput.tsx
组件实现了带有多行输入和回车发送功能的输入框:
tsx
复制
ini
const ChatInput = ({ onSend, disabled }: ChatInputProps) => {
const [message, setMessage] = useState('');
const handleSubmit = (e?: React.FormEvent) => {
e?.preventDefault();
if (message.trim()) {
onSend(message);
setMessage('');
}
};
const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSubmit();
}
};
return (
<Box component="form" onSubmit={handleSubmit}>
<TextField
fullWidth
multiline
value={message}
onChange={(e) => setMessage(e.value)}
onKeyDown={handleKeyDown}
placeholder="输入消息...(回车发送)"
/>
</Box>
);
};
回车发送坑 :最初实现时发现按下回车会同时触发发送和换行。通过e.preventDefault()
和!e.shiftKey
条件判断解决了这个问题,同时保留了Shift+Enter换行的功能。
3.2 状态管理与API集成
API服务层
services/api.ts
封装了所有与后端的交互逻辑:
tsx
复制
vbnet
const api = axios.create({
baseURL: process.env.REACT_APP_API_URL,
timeout: 30000
});
export const sendMessage = async (messages: ChatMessage[]) => {
try {
const response = await api.post('/chat', { messages });
return response.data.choices[0].message.content;
} catch (error) {
if (axios.isAxiosError(error)) {
throw new Error(error.response?.data?.error || '请求失败');
}
throw error;
}
};
API设计坑:最初没有统一处理错误,导致前端难以显示友好的错误提示。通过添加try-catch和错误类型检查改善了这个问题。
聊天页面状态
ChatPage.tsx
管理着核心的聊天状态和逻辑:
tsx
复制
typescript
const ChatPage = () => {
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [isLoading, setIsLoading] = useState(false);
const messagesEndRef = useRef<HTMLDivElement>(null);
const handleSend = async (content: string) => {
const userMessage = { role: 'user', content, timestamp: new Date() };
setMessages(prev => [...prev, userMessage]);
setIsLoading(true);
try {
const aiResponse = await sendMessage([
...messages.map(m => ({ role: m.role, content: m.content })),
{ role: 'user', content }
]);
setMessages(prev => [...prev, {
role: 'assistant',
content: aiResponse,
timestamp: new Date()
}]);
} catch (error) {
setMessages(prev => [...prev, {
role: 'assistant',
content: '抱歉,发生错误: ' + error.message,
timestamp: new Date()
}]);
} finally {
setIsLoading(false);
}
};
// 自动滚动到底部
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
return (
<>
<MessageList messages={messages} />
{isLoading && <Loading />}
<ChatInput onSend={handleSend} disabled={isLoading} />
<div ref={messagesEndRef} />
</>
);
};
状态管理坑:最初直接将API返回的整个响应对象存入状态,导致渲染时报错"Objects are not valid as a React child"。通过只提取需要的content字段解决了这个问题。
四、深度优化与功能增强
4.1 性能优化
消息列表优化
使用React.memo避免不必要的重渲染:
tsx
复制
javascript
const MessageItem = React.memo(({ message }: { message: ChatMessage }) => {
return <Message {...message} />;
});
const MessageList = ({ messages }: { messages: ChatMessage[] }) => {
return (
<>
{messages.map((msg, i) => (
<MessageItem key={`${msg.timestamp}-${i}`} message={msg} />
))}
</>
);
};
API请求优化
添加请求取消功能,防止组件卸载后仍更新状态:
tsx
复制
ini
const ChatPage = () => {
const controllerRef = useRef<AbortController>();
const handleSend = async (content: string) => {
controllerRef.current?.abort();
controllerRef.current = new AbortController();
try {
const aiResponse = await sendMessage(/* ... */, {
signal: controllerRef.current.signal
});
// ...更新状态
} catch (error) {
if (!axios.isCancel(error)) {
// 处理真实错误
}
}
};
useEffect(() => {
return () => controllerRef.current?.abort();
}, []);
};
4.2 用户体验优化
加载状态反馈
添加了三种状态的视觉反馈:
- 发送中:输入框禁用
- AI思考中:显示加载指示器
- 错误状态:显示红色错误消息
tsx
复制
javascript
{
error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)
}
本地存储对话历史
使用localStorage持久化聊天记录:
tsx
复制
ini
const useChatHistory = () => {
const [messages, setMessages] = useState<ChatMessage[]>(() => {
const saved = localStorage.getItem('chatHistory');
return saved ? JSON.parse(saved) : [];
});
const updateMessages = useCallback((newMessages: ChatMessage[]) => {
setMessages(newMessages);
localStorage.setItem('chatHistory', JSON.stringify(newMessages));
}, []);
return [messages, updateMessages] as const;
};
五、遇到的典型问题与解决方案
5.1 CORS跨域问题
问题现象:前端请求被浏览器拦截,控制台显示CORS错误。
解决方案:
-
后端添加CORS中间件:
ts
复制
lessapp.use(cors({ origin: ['http://localhost:3000'], methods: ['GET', 'POST'] }));
-
前端配置代理(适用于开发环境):
json
复制
json// package.json "proxy": "http://localhost:3001"
5.2 大语言模型响应慢
问题现象:API请求经常超时。
解决方案:
-
增加超时时间:
ts
复制
bashaxios.create({ timeout: 30000 })
-
实现乐观更新:先显示用户消息,再等待AI响应
-
添加重试机制:
ts
复制
vbnetconst retry = async (fn, retries = 3) => { try { return await fn(); } catch (error) { return retries > 0 ? retry(fn, retries - 1) : throw error; } };
5.3 移动端适配问题
问题现象:在手机上输入框被键盘遮挡。
解决方案:
-
使用CSS视口单位:
css
复制
css.chat-container { height: 100vh; height: calc(var(--vh, 1vh) * 100); }
-
JavaScript动态调整:
ts
复制
javascriptuseEffect(() => { const setVh = () => { document.documentElement.style.setProperty('--vh', `${window.innerHeight * 0.01}px`); }; window.addEventListener('resize', setVh); return () => window.removeEventListener('resize', setVh); }, []);
六、项目总结与展望
通过这个项目的开发,我深刻体会到:
- 类型安全的重要性:TypeScript帮助捕获了大量潜在的类型错误
- 组件化设计的优势:使得功能扩展和维护变得简单
- 用户体验的细节:微小的交互改进能显著提升使用感受
未来可能的改进方向包括:
- 实现流式传输(SSE)以获得更实时的响应
- 添加消息编辑和重新生成功能
- 支持多模态输入(图片、语音等)
- 实现对话主题和场景切换
这个AI聊天助手前端的开发过程充满了挑战和学习机会,希望本文的记录能为其他开发者提供有价值的参考。在技术快速发展的今天,持续学习和实践是提升开发能力的最佳途径。