ChatGPT与React结合开发AI聊天应用:从零构建智能对话系统完整教程

SoftCloud
SoftCloud 2026-01-30T17:08:09+08:00
0 0 1

前言

在人工智能技术飞速发展的今天,ChatGPT作为OpenAI推出的革命性语言模型,正在改变我们与机器交互的方式。而React作为当下最流行的前端框架之一,为构建现代化Web应用提供了强大的支持。将这两者结合,我们可以创造出功能强大、用户体验优秀的AI聊天应用。

本文将从零开始,详细讲解如何使用React框架集成ChatGPT API,构建一个完整的智能对话系统。通过本教程,你将掌握API集成、实时通信、用户界面设计等核心技术,并能够独立开发出高质量的AI聊天应用。

项目概述

技术栈介绍

在开始具体实现之前,让我们先了解一下本项目涉及的主要技术:

  • React: 现代前端开发的核心框架,用于构建用户界面
  • ChatGPT API: OpenAI提供的语言模型API,支持自然语言交互
  • Axios: HTTP客户端库,用于发送API请求
  • React Hooks: React的函数式组件状态管理方案
  • CSS Modules/Styled Components: 样式管理方案
  • Node.js: 后端服务环境(可选)

项目功能特性

本聊天应用将具备以下核心功能:

  1. 实时与ChatGPT进行对话交流
  2. 用户友好的界面设计
  3. 消息历史记录保存
  4. 加载状态显示
  5. 错误处理机制
  6. 响应式布局设计

环境准备与项目初始化

1. 创建React项目

首先,我们需要创建一个新的React项目。推荐使用Create React App来快速搭建环境:

npx create-react-app ai-chat-app
cd ai-chat-app
npm start

2. 安装必要依赖

为了实现API集成和更好的用户体验,我们需要安装一些额外的依赖包:

npm install axios react-router-dom

3. 获取ChatGPT API密钥

访问OpenAI官网注册账号并获取API密钥。确保你的账户有足够的余额来使用API服务。

API集成与配置

1. 创建API服务文件

src目录下创建一个专门的API服务文件,用于管理所有与ChatGPT相关的请求:

// src/services/chatGptService.js
import axios from 'axios';

class ChatGptService {
  constructor() {
    this.apiKey = process.env.REACT_APP_OPENAI_API_KEY;
    this.baseUrl = 'https://api.openai.com/v1';
    this.headers = {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${this.apiKey}`
    };
  }

  async sendMessage(message) {
    try {
      const response = await axios.post(
        `${this.baseUrl}/chat/completions`,
        {
          model: "gpt-3.5-turbo",
          messages: [
            {
              role: "user",
              content: message
            }
          ],
          temperature: 0.7,
          max_tokens: 1000
        },
        {
          headers: this.headers
        }
      );

      return response.data.choices[0].message.content.trim();
    } catch (error) {
      console.error('API Error:', error);
      throw new Error('Failed to get response from ChatGPT');
    }
  }

  async sendMessageStream(message, onChunk) {
    try {
      const response = await axios.post(
        `${this.baseUrl}/chat/completions`,
        {
          model: "gpt-3-5-turbo",
          messages: [
            {
              role: "user",
              content: message
            }
          ],
          stream: true,
          temperature: 0.7
        },
        {
          headers: this.headers,
          responseType: 'stream'
        }
      );

      // 处理流式响应
      const reader = response.data.getReader();
      const decoder = new TextDecoder();
      
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        
        const chunk = decoder.decode(value);
        const lines = chunk.split('\n');
        
        for (const line of lines) {
          if (line.startsWith('data: ')) {
            const data = line.slice(6);
            if (data === '[DONE]') continue;
            
            try {
              const parsed = JSON.parse(data);
              const content = parsed.choices[0]?.delta?.content;
              if (content) {
                onChunk(content);
              }
            } catch (e) {
              console.error('Error parsing chunk:', e);
            }
          }
        }
      }
    } catch (error) {
      console.error('Streaming Error:', error);
      throw new Error('Failed to stream response from ChatGPT');
    }
  }
}

export default new ChatGptService();

2. 环境变量配置

在项目根目录创建.env文件,添加API密钥:

# .env
REACT_APP_OPENAI_API_KEY=your_api_key_here

核心组件开发

1. 消息组件设计

首先创建一个消息组件来展示用户和AI的消息:

// src/components/Message.jsx
import React from 'react';
import './Message.css';

const Message = ({ message, isUser }) => {
  return (
    <div className={`message ${isUser ? 'user-message' : 'ai-message'}`}>
      <div className="message-content">
        {message}
      </div>
    </div>
  );
};

export default Message;

对应的CSS样式:

/* src/components/Message.css */
.message {
  margin: 10px 0;
  padding: 12px 16px;
  border-radius: 8px;
  max-width: 80%;
  word-wrap: break-word;
}

.user-message {
  background-color: #007bff;
  color: white;
  margin-left: auto;
  text-align: right;
}

.ai-message {
  background-color: #f8f9fa;
  color: #333;
  margin-right: auto;
}

.message-content {
  font-size: 14px;
  line-height: 1.4;
}

2. 聊天窗口组件

创建主要的聊天窗口组件:

// src/components/ChatWindow.jsx
import React, { useState, useEffect, useRef } from 'react';
import Message from './Message';
import chatGptService from '../services/chatGptService';
import './ChatWindow.css';

const ChatWindow = () => {
  const [messages, setMessages] = useState([]);
  const [inputValue, setInputValue] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const messagesEndRef = useRef(null);

  // 自动滚动到底部
  const scrollToBottom = () => {
    messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
  };

  useEffect(() => {
    scrollToBottom();
  }, [messages]);

  // 处理消息发送
  const handleSendMessage = async (message) => {
    if (!message.trim() || isLoading) return;

    // 添加用户消息
    const userMessage = {
      id: Date.now(),
      text: message,
      isUser: true,
      timestamp: new Date()
    };

    setMessages(prev => [...prev, userMessage]);
    setInputValue('');
    setIsLoading(true);

    try {
      // 获取AI回复
      const aiResponse = await chatGptService.sendMessage(message);
      
      const aiMessage = {
        id: Date.now() + 1,
        text: aiResponse,
        isUser: false,
        timestamp: new Date()
      };

      setMessages(prev => [...prev, aiMessage]);
    } catch (error) {
      const errorMessage = {
        id: Date.now() + 1,
        text: '抱歉,我遇到了一些问题。请稍后再试。',
        isUser: false,
        timestamp: new Date()
      };
      setMessages(prev => [...prev, errorMessage]);
    } finally {
      setIsLoading(false);
    }
  };

  // 处理回车键发送消息
  const handleKeyPress = (e) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      handleSendMessage(inputValue);
    }
  };

  return (
    <div className="chat-window">
      <div className="chat-header">
        <h2>AI聊天助手</h2>
      </div>
      
      <div className="messages-container">
        {messages.map((message) => (
          <Message 
            key={message.id} 
            message={message.text} 
            isUser={message.isUser} 
          />
        ))}
        
        {isLoading && (
          <div className="loading-message">
            <div className="typing-indicator">
              <span></span>
              <span></span>
              <span></span>
            </div>
          </div>
        )}
        
        <div ref={messagesEndRef} />
      </div>

      <div className="input-container">
        <textarea
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          onKeyPress={handleKeyPress}
          placeholder="输入您的问题..."
          disabled={isLoading}
          rows="3"
        />
        <button 
          onClick={() => handleSendMessage(inputValue)}
          disabled={isLoading || !inputValue.trim()}
          className="send-button"
        >
          发送
        </button>
      </div>
    </div>
  );
};

export default ChatWindow;

对应的CSS样式:

/* src/components/ChatWindow.css */
.chat-window {
  display: flex;
  flex-direction: column;
  height: 100vh;
  max-width: 800px;
  margin: 0 auto;
  border: 1px solid #ddd;
  border-radius: 10px;
  overflow: hidden;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.chat-header {
  background-color: #007bff;
  color: white;
  padding: 15px 20px;
  text-align: center;
  border-bottom: 1px solid #ddd;
}

.messages-container {
  flex: 1;
  overflow-y: auto;
  padding: 20px;
  background-color: #f8f9fa;
}

.loading-message {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  margin-bottom: 10px;
}

.typing-indicator {
  display: flex;
  align-items: center;
  padding: 10px 15px;
  background-color: #f8f9fa;
  border-radius: 20px;
  border: 1px solid #ddd;
}

.typing-indicator span {
  height: 8px;
  width: 8px;
  border-radius: 50%;
  background-color: #007bff;
  display: inline-block;
  margin: 0 2px;
  animation: typing 1.4s infinite ease-in-out;
}

.typing-indicator span:nth-child(2) {
  animation-delay: 0.2s;
}

.typing-indicator span:nth-child(3) {
  animation-delay: 0.4s;
}

@keyframes typing {
  0%, 60%, 100% { transform: translateY(0); }
  30% { transform: translateY(-5px); }
}

.input-container {
  display: flex;
  padding: 15px;
  background-color: white;
  border-top: 1px solid #ddd;
  gap: 10px;
}

.input-container textarea {
  flex: 1;
  padding: 12px;
  border: 1px solid #ddd;
  border-radius: 8px;
  resize: none;
  font-size: 14px;
  outline: none;
  transition: border-color 0.3s;
}

.input-container textarea:focus {
  border-color: #007bff;
}

.send-button {
  padding: 12px 20px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  font-size: 14px;
  transition: background-color 0.3s;
}

.send-button:hover:not(:disabled) {
  background-color: #0056b3;
}

.send-button:disabled {
  background-color: #6c757d;
  cursor: not-allowed;
}

@media (max-width: 768px) {
  .chat-window {
    height: 100vh;
    border-radius: 0;
  }
  
  .messages-container {
    padding: 10px;
  }
  
  .input-container {
    padding: 10px;
  }
  
  .message {
    max-width: 90%;
  }
}

高级功能实现

1. 消息历史管理

为了提供更好的用户体验,我们需要实现消息历史记录功能:

// src/utils/messageHistory.js
class MessageHistory {
  static STORAGE_KEY = 'chat_messages';

  static saveMessages(messages) {
    try {
      const serialized = JSON.stringify(messages);
      localStorage.setItem(this.STORAGE_KEY, serialized);
    } catch (error) {
      console.error('Failed to save messages:', error);
    }
  }

  static loadMessages() {
    try {
      const serialized = localStorage.getItem(this.STORAGE_KEY);
      return serialized ? JSON.parse(serialized) : [];
    } catch (error) {
      console.error('Failed to load messages:', error);
      return [];
    }
  }

  static clearHistory() {
    localStorage.removeItem(this.STORAGE_KEY);
  }
}

export default MessageHistory;

2. 流式响应处理

为了让用户体验更加流畅,我们可以实现流式响应功能:

// src/components/StreamingChatWindow.jsx
import React, { useState, useEffect, useRef } from 'react';
import Message from './Message';
import chatGptService from '../services/chatGptService';
import MessageHistory from '../utils/messageHistory';
import './ChatWindow.css';

const StreamingChatWindow = () => {
  const [messages, setMessages] = useState([]);
  const [inputValue, setInputValue] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const [currentResponse, setCurrentResponse] = useState('');
  const messagesEndRef = useRef(null);

  useEffect(() => {
    // 加载历史消息
    const savedMessages = MessageHistory.loadMessages();
    setMessages(savedMessages);
  }, []);

  useEffect(() => {
    // 保存消息到本地存储
    MessageHistory.saveMessages(messages);
    scrollToBottom();
  }, [messages]);

  const scrollToBottom = () => {
    messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
  };

  const handleSendMessage = async (message) => {
    if (!message.trim() || isLoading) return;

    // 添加用户消息
    const userMessage = {
      id: Date.now(),
      text: message,
      isUser: true,
      timestamp: new Date()
    };

    setMessages(prev => [...prev, userMessage]);
    setInputValue('');
    setIsLoading(true);
    setCurrentResponse('');

    try {
      // 使用流式API获取响应
      const responseChunks = [];
      
      await chatGptService.sendMessageStream(message, (chunk) => {
        responseChunks.push(chunk);
        setCurrentResponse(responseChunks.join(''));
      });

      // 添加AI回复
      const aiMessage = {
        id: Date.now() + 1,
        text: responseChunks.join(''),
        isUser: false,
        timestamp: new Date()
      };

      setMessages(prev => [...prev, aiMessage]);
      setCurrentResponse('');
    } catch (error) {
      const errorMessage = {
        id: Date.now() + 1,
        text: '抱歉,我遇到了一些问题。请稍后再试。',
        isUser: false,
        timestamp: new Date()
      };
      setMessages(prev => [...prev, errorMessage]);
    } finally {
      setIsLoading(false);
    }
  };

  const handleKeyPress = (e) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      handleSendMessage(inputValue);
    }
  };

  return (
    <div className="chat-window">
      <div className="chat-header">
        <h2>AI聊天助手</h2>
      </div>
      
      <div className="messages-container">
        {messages.map((message) => (
          <Message 
            key={message.id} 
            message={message.text} 
            isUser={message.isUser} 
          />
        ))}
        
        {currentResponse && (
          <Message 
            message={currentResponse} 
            isUser={false} 
          />
        )}
        
        {isLoading && (
          <div className="loading-message">
            <div className="typing-indicator">
              <span></span>
              <span></span>
              <span></span>
            </div>
          </div>
        )}
        
        <div ref={messagesEndRef} />
      </div>

      <div className="input-container">
        <textarea
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          onKeyPress={handleKeyPress}
          placeholder="输入您的问题..."
          disabled={isLoading}
          rows="3"
        />
        <button 
          onClick={() => handleSendMessage(inputValue)}
          disabled={isLoading || !inputValue.trim()}
          className="send-button"
        >
          发送
        </button>
      </div>
    </div>
  );
};

export default StreamingChatWindow;

错误处理与优化

1. 完善的错误处理机制

// src/services/chatGptService.js (增强版)
import axios from 'axios';

class ChatGptService {
  constructor() {
    this.apiKey = process.env.REACT_APP_OPENAI_API_KEY;
    this.baseUrl = 'https://api.openai.com/v1';
    this.headers = {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${this.apiKey}`
    };
    this.retryAttempts = 3;
  }

  async sendMessage(message, retries = 0) {
    try {
      const response = await axios.post(
        `${this.baseUrl}/chat/completions`,
        {
          model: "gpt-3.5-turbo",
          messages: [
            {
              role: "user",
              content: message
            }
          ],
          temperature: 0.7,
          max_tokens: 1000
        },
        {
          headers: this.headers,
          timeout: 30000 // 30秒超时
        }
      );

      return response.data.choices[0].message.content.trim();
    } catch (error) {
      console.error('API Error:', error);
      
      // 处理不同类型的错误
      if (error.code === 'ECONNABORTED') {
        throw new Error('请求超时,请稍后再试');
      }
      
      if (error.response) {
        const { status, data } = error.response;
        switch (status) {
          case 401:
            throw new Error('API密钥无效,请检查您的配置');
          case 429:
            throw new Error('请求过于频繁,请稍后再试');
          case 500:
            throw new Error('服务器内部错误,请稍后再试');
          default:
            throw new Error(`API错误: ${data.error?.message || '未知错误'}`);
        }
      }
      
      if (retries < this.retryAttempts) {
        // 指数退避重试
        await new Promise(resolve => setTimeout(resolve, Math.pow(2, retries) * 1000));
        return this.sendMessage(message, retries + 1);
      }
      
      throw new Error('网络连接失败,请检查您的网络');
    }
  }
}

export default new ChatGptService();

2. 性能优化

// src/components/ChatWindow.jsx (优化版)
import React, { useState, useEffect, useRef, useCallback } from 'react';
import Message from './Message';
import chatGptService from '../services/chatGptService';
import MessageHistory from '../utils/messageHistory';
import './ChatWindow.css';

const ChatWindow = () => {
  const [messages, setMessages] = useState([]);
  const [inputValue, setInputValue] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const messagesEndRef = useRef(null);
  const inputRef = useRef(null);

  // 使用useCallback优化函数引用
  const scrollToBottom = useCallback(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
  }, []);

  const saveMessagesToStorage = useCallback((newMessages) => {
    MessageHistory.saveMessages(newMessages);
  }, []);

  useEffect(() => {
    const savedMessages = MessageHistory.loadMessages();
    setMessages(savedMessages);
  }, []);

  useEffect(() => {
    scrollToBottom();
    saveMessagesToStorage(messages);
  }, [messages, scrollToBottom, saveMessagesToStorage]);

  // 防抖处理
  const debouncedSendMessage = useCallback(
    debounce(async (message) => {
      if (!message.trim() || isLoading) return;

      const userMessage = {
        id: Date.now(),
        text: message,
        isUser: true,
        timestamp: new Date()
      };

      setMessages(prev => [...prev, userMessage]);
      setInputValue('');
      setIsLoading(true);

      try {
        const aiResponse = await chatGptService.sendMessage(message);
        
        const aiMessage = {
          id: Date.now() + 1,
          text: aiResponse,
          isUser: false,
          timestamp: new Date()
        };

        setMessages(prev => [...prev, aiMessage]);
      } catch (error) {
        const errorMessage = {
          id: Date.now() + 1,
          text: error.message || '抱歉,我遇到了一些问题。请稍后再试。',
          isUser: false,
          timestamp: new Date()
        };
        setMessages(prev => [...prev, errorMessage]);
      } finally {
        setIsLoading(false);
      }
    }, 300),
    [isLoading]
  );

  const handleSendMessage = (message) => {
    debouncedSendMessage(message);
  };

  const handleKeyPress = (e) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      handleSendMessage(inputValue);
    }
  };

  // 防抖函数
  function debounce(func, wait) {
    let timeout;
    return function executedFunction(...args) {
      const later = () => {
        clearTimeout(timeout);
        func(...args);
      };
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
    };
  }

  return (
    <div className="chat-window">
      <div className="chat-header">
        <h2>AI聊天助手</h2>
      </div>
      
      <div className="messages-container">
        {messages.map((message) => (
          <Message 
            key={message.id} 
            message={message.text} 
            isUser={message.isUser} 
          />
        ))}
        
        {isLoading && (
          <div className="loading-message">
            <div className="typing-indicator">
              <span></span>
              <span></span>
              <span></span>
            </div>
          </div>
        )}
        
        <div ref={messagesEndRef} />
      </div>

      <div className="input-container">
        <textarea
          ref={inputRef}
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          onKeyPress={handleKeyPress}
          placeholder="输入您的问题..."
          disabled={isLoading}
          rows="3"
        />
        <button 
          onClick={() => handleSendMessage(inputValue)}
          disabled={isLoading || !inputValue.trim()}
          className="send-button"
        >
          发送
        </button>
      </div>
    </div>
  );
};

export default ChatWindow;

完整的应用入口

1. 主应用组件

// src/App.jsx
import React from 'react';
import ChatWindow from './components/ChatWindow';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h1>AI聊天助手</h1>
      </header>
      <main>
        <ChatWindow />
      </main>
    </div>
  );
}

export default App;

2. 样式优化

/* src/App.css */
.App {
  text-align: center;
  min-height: 100vh;
  background-color: #f0f2f5;
}

.App-header {
  background-color: #282c34;
  padding: 20px;
  color: white;
  margin-bottom: 20px;
}

.App-header h1 {
  margin: 0;
  font-size: 2rem;
}

main {
  padding: 20px;
}

部署与生产环境优化

1. 环境变量管理

在生产环境中,确保正确配置环境变量:

# .env.production
REACT_APP_OPENAI_API_KEY=your_production_api_key_here
REACT_APP_API_BASE_URL=https://api.openai.com/v1

2. 构建优化

// src/services/chatGptService.js (生产版本)
import axios from 'axios';

class ChatGptService {
  constructor() {
    this.apiKey = process.env.REACT_APP_OPENAI_API_KEY;
    this.baseUrl = process.env.REACT_APP_API_BASE_URL || 'https://api.openai.com/v1';
    this.headers = {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${this.apiKey}`
    };
  }

  async sendMessage(message) {
    try {
      const response = await axios.post(
        `${this.baseUrl}/chat/completions`,
        {
          model: "gpt-3.5-turbo",
          messages: [
            {
              role: "user",
              content: message
            }
          ],
          temperature: 0.7,
          max_tokens: 1000
        },
        {
          headers: this.headers,
          timeout: 30000,
          // 生产环境配置
          validateStatus: function (status) {
            return status < 500; // 只对5xx错误进行reject
          }
        }
      );

      if (!response.data.choices || response.data.choices.length === 0) {
        throw new Error('No response from AI service');
      }

      return response.data.choices[0].message.content.trim();
    } catch (error) {
      console.error('API Error:', error);
      
      if (error.response?.status === 429) {
        throw new Error('请求过于频繁,请稍后再试');
      }
      
      if (error.response?.status === 401) {
        throw new Error('API密钥无效,请检查配置');
      }
      
      throw new Error('网络连接失败,请检查您的网络');
    }
  }
}

export default new ChatGptService();

3. 缓存机制

// src/utils/cache.js
class Cache {
  constructor(maxSize = 100) {
    this.cache = new Map();
    this.maxSize = maxSize;
  }

  get(key) {
    if (this.cache.has(key)) {
      const item = this.cache.get(key);
      // 更新访问时间
      this.cache.delete(key);
      this.cache.set(key, item);
     
相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000