共计 4227 个字符,预计需要花费 11 分钟才能阅读完成。
ChatGPT 的 API 为开发者提供了强大的自然语言处理能力,通过简单的 HTTP 请求即可实现智能对话功能。对于前端开发者来说,将其集成到网页应用中可以大大提升用户体验。本文将带你从零开始构建一个完整的 ChatGPT 对话前端页面。

技术栈选择
在开始之前,我们先对比一下主流前端框架在这个场景下的表现:
- React:组件化开发模式非常适合构建交互式对话界面,丰富的生态系统和 hooks 特性简化了状态管理
- Vue:虽然也可以胜任,但在处理复杂状态流时略显不足
- 纯 JavaScript:开发效率低,维护成本高
综合考虑开发效率和项目可维护性,我们推荐使用 React + TypeScript 技术栈。TypeScript 的类型系统能帮助我们更好地管理 API 响应数据结构和组件状态。
核心实现
1. API 请求封装与鉴权处理
首先我们需要封装与 ChatGPT API 的通信逻辑。这里使用 Fetch API 来实现:
interface Message {
role: 'user' | 'assistant';
content: string;
}
async function fetchChatResponse(messages: Message[], apiKey: string) {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
model: 'gpt-3.5-turbo',
messages: messages,
stream: true // 启用流式响应
})
});
if (!response.ok) {throw new Error(`API 请求失败: ${response.status}`);
}
return response;
}
2. 对话流式接收与渲染
流式接收可以显著改善用户体验,让回复能够逐步显示而不是等待完整响应:
async function processStreamResponse(
response: Response,
onDataReceived: (chunk: string) => void
) {const reader = response.body?.getReader();
const decoder = new TextDecoder();
let result = '';
if (!reader) return;
while (true) {const { done, value} = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true});
const lines = chunk.split('\n');
for (const line of lines) {if (line.startsWith('data:') && !line.includes('[DONE]')) {const data = JSON.parse(line.replace('data:', ''));
const content = data.choices[0]?.delta?.content || '';
result += content;
onDataReceived(result);
}
}
}
}
3. 用户输入防抖与错误处理
为了防止频繁触发 API 请求,我们需要对用户输入进行防抖处理:
function debounce<F extends (...args: any[]) => void>(
func: F,
delay: number
): (...args: Parameters<F>) => void {
let timeoutId: ReturnType<typeof setTimeout>;
return function(...args: Parameters<F>) {clearTimeout(timeoutId);
timeoutId = setTimeout(() => func(...args), delay);
};
}
完整 React 组件实现
下面是一个完整的 ChatGPT 对话组件实现:
import React, {useState, useRef} from 'react';
type Message = {
role: 'user' | 'assistant';
content: string;
};
const ChatInterface: React.FC<{apiKey: string}> = ({apiKey}) => {const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);
const handleSubmit = async (e: React.FormEvent) => {e.preventDefault();
if (!input.trim() || isLoading) return;
const userMessage: Message = {role: 'user', content: input};
setMessages(prev => [...prev, userMessage]);
setInput('');
setIsLoading(true);
setError(null);
try {const response = await fetchChatResponse([...messages, userMessage], apiKey);
let assistantMessage = '';
setMessages(prev => [...prev, { role: 'assistant', content: ''}]);
await processStreamResponse(response, (chunk) => {
assistantMessage = chunk;
setMessages(prev => {const newMessages = [...prev];
newMessages[newMessages.length - 1].content = assistantMessage;
return newMessages;
});
scrollToBottom();});
} catch (err) {setError(err instanceof Error ? err.message : '未知错误');
} finally {setIsLoading(false);
}
};
const scrollToBottom = () => {messagesEndRef.current?.scrollIntoView({ behavior: 'smooth'});
};
return (
<div className="chat-container">
<div className="messages">
{messages.map((msg, i) => (<div key={i} className={`message ${msg.role}`}>
{msg.content}
</div>
))}
{isLoading && (
<div className="message assistant">
<div className="typing-indicator"> 思考中...</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
{error && <div className="error">{error}</div>}
<form onSubmit={handleSubmit} className="input-area">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="输入消息..."
disabled={isLoading}
/>
<button type="submit" disabled={isLoading || !input.trim()}>
发送
</button>
</form>
</div>
);
};
生产环境注意事项
API 调用频率限制处理
ChatGPT API 有调用频率限制,我们需要在客户端实现适当的节流:
// 在组件中使用
const throttledSubmit = useMemo(() => throttle(handleSubmit, 1000), []);
敏感内容过滤方案
可以在发送到 API 前对用户输入进行初步过滤:
function filterSensitiveContent(text: string): string {const sensitivePatterns = [/badword1/gi, /badword2/gi];
return sensitivePatterns.reduce((result, pattern) =>
result.replace(pattern, '***'), text);
}
移动端适配要点
确保在移动设备上有良好的体验:
.chat-container {
max-width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
}
.input-area {
padding: 10px;
position: sticky;
bottom: 0;
background: white;
}
进阶思考
- 对话历史本地存储 :如何使用 localStorage 或 IndexedDB 保存对话历史,实现页面刷新后恢复对话?
- 多轮对话上下文保持 :如何设计数据结构来维护对话上下文,确保 ChatGPT 能理解连续对话?
- 性能优化指标监控 :如何监控 API 响应时间、首字节时间等关键指标,并据此优化用户体验?
通过本文的学习,你已经掌握了构建 ChatGPT 对话前端页面的核心技能。建议从简单实现开始,逐步添加上述进阶功能,不断优化用户体验。
正文完
发表至: 前端开发
近两天内
