首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >react 实现AI对话窗口

react 实现AI对话窗口

原创
作者头像
小焱写作
发布2025-10-09 14:41:23
发布2025-10-09 14:41:23
2800
代码可运行
举报
文章被收录于专栏:javajava
运行总次数:0
代码可运行
代码语言:javascript
代码运行次数:0
运行
复制
import React, { useState, useEffect, useRef } from 'react';
import { Send, Paperclip, Mic, MoreVert, Loader2, X, Smile } from 'lucide-react';

// 消息类型定义
interface Message {
  id: string;
  content: string;
  sender: 'user' | 'ai';
  timestamp: Date;
  status: 'sent' | 'receiving' | 'received';
}

const AIChatWindow: React.FC = () => {
  // 状态管理
  const [messages, setMessages] = useState<Message[]>([
    {
      id: '1',
      content: '您好!我是AI助手,有什么可以帮助您的吗?',
      sender: 'ai',
      timestamp: new Date(Date.now() - 86400000),
      status: 'received'
    }
  ]);
  const [input, setInput] = useState('');
  const [isTyping, setIsTyping] = useState(false);
  const [showEmojiPicker, setShowEmojiPicker] = useState(false);
  const messagesEndRef = useRef<HTMLDivElement>(null);

  // 自动滚动到最新消息
  useEffect(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [messages]);

  // 发送消息处理函数
  const handleSendMessage = () => {
    if (!input.trim()) return;

    // 添加用户消息
    const userMessage: Message = {
      id: Date.now().toString(),
      content: input.trim(),
      sender: 'user',
      timestamp: new Date(),
      status: 'sent'
    };

    setMessages(prev => [...prev, userMessage]);
    setInput('');
    setIsTyping(true);

    // 模拟AI回复延迟
    setTimeout(() => {
      // 模拟AI回复
      const aiResponses = [
        '感谢您的提问!关于这个问题,我的理解是...',
        '您提出的这个问题很有深度。让我为您详细解答一下...',
        '根据您提供的信息,我认为最佳方案是...',
        '这个问题需要从多个角度来分析。首先...',
        '我明白了您的需求,我会尽力提供帮助。'
      ];
      
      const randomResponse = aiResponses[Math.floor(Math.random() * aiResponses.length)];
      
      const aiMessage: Message = {
        id: (Date.now() + 1).toString(),
        content: randomResponse,
        sender: 'ai',
        timestamp: new Date(),
        status: 'received'
      };

      setMessages(prev => [...prev, aiMessage]);
      setIsTyping(false);
    }, 1500);
  };

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

  // 添加表情
  const addEmoji = (emoji: string) => {
    setInput(prev => prev + emoji);
    setShowEmojiPicker(false);
  };

  // 格式化时间显示
  const formatTime = (date: Date) => {
    return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
  };

  return (
    <div className="flex flex-col h-full max-h-[800px] w-full max-w-md mx-auto bg-gray-50 rounded-xl shadow-lg overflow-hidden border border-gray-200">
      {/* 聊天窗口头部 */}
      <div className="bg-gradient-to-r from-blue-600 to-indigo-600 text-white p-4 shadow-md">
        <div className="flex items-center justify-between">
          <div className="flex items-center space-x-3">
            <div className="w-10 h-10 rounded-full bg-white/20 flex items-center justify-center">
              <div className="w-6 h-6 rounded-full bg-blue-200 flex items-center justify-center">
                <span className="text-blue-700 font-bold text-sm">AI</span>
              </div>
            </div>
            <div>
              <h2 className="font-semibold text-lg">AI 助手</h2>
              <p className="text-blue-100 text-xs flex items-center">
                <span className="w-2 h-2 rounded-full bg-green-400 mr-1"></span>
                在线
              </p>
            </div>
          </div>
          <button className="p-2 rounded-full hover:bg-white/10 transition-colors">
            <MoreVert size={20} />
          </button>
        </div>
      </div>

      {/* 聊天消息区域 */}
      <div className="flex-1 overflow-y-auto p-4 space-y-6 bg-gray-50">
        {/* 日期分隔线 */}
        <div className="flex items-center justify-center">
          <span className="text-xs text-gray-400 bg-gray-100 px-3 py-1 rounded-full">
            今天
          </span>
        </div>

        {/* 消息列表 */}
        {messages.map((message) => (
          <div 
            key={message.id} 
            className={`flex ${message.sender === 'user' ? 'justify-end' : 'justify-start'}`}
          >
            <div 
              className={`max-w-[80%] px-4 py-3 rounded-2xl shadow-sm ${
                message.sender === 'user' 
                  ? 'bg-blue-600 text-white rounded-tr-none' 
                  : 'bg-white text-gray-800 rounded-tl-none border border-gray-100'
              }`}
            >
              <p className="whitespace-pre-wrap">{message.content}</p>
              <p className={`text-xs mt-1 ${message.sender === 'user' ? 'text-blue-100' : 'text-gray-400'}`}>
                {formatTime(message.timestamp)}
              </p>
            </div>
          </div>
        ))}

        {/* 正在输入提示 */}
        {isTyping && (
          <div className="flex justify-start">
            <div className="bg-white px-4 py-3 rounded-2xl rounded-tl-none border border-gray-100 shadow-sm max-w-[80%]">
              <div className="flex space-x-1">
                <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce [animation-delay:-0.3s]"></div>
                <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce [animation-delay:-0.15s]"></div>
                <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
              </div>
            </div>
          </div>
        )}

        {/* 用于自动滚动的参考点 */}
        <div ref={messagesEndRef} />
      </div>

      {/* 输入区域 */}
      <div className="border-t border-gray-200 p-3 bg-white">
        <div className="relative">
          <textarea
            value={input}
            onChange={(e) => setInput(e.target.value)}
            onKeyPress={handleKeyPress}
            placeholder="输入消息..."
            className="w-full p-3 pr-20 pl-10 rounded-full border border-gray-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none transition-all duration-200"
            rows={1}
          />
          
          {/* 表情按钮 */}
          <button 
            onClick={() => setShowEmojiPicker(!showEmojiPicker)}
            className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700 transition-colors"
          >
            <Smile size={20} />
          </button>
          
          {/* 表情选择器 */}
          {showEmojiPicker && (
            <div className="absolute bottom-full left-0 mb-2 bg-white rounded-lg shadow-lg p-2 border border-gray-200 z-10">
              <div className="grid grid-cols-7 gap-2">
                {['😀', '😂', '😍', '🤔', '👍', '🙌', '👏', '🙏', '🎉', '😊', '🤗', '😢', '😮', '😴', '🤯', '🤩', '🤪', '😎', '🥳', '🤓'].map((emoji) => (
                  <button 
                    key={emoji} 
                    onClick={() => addEmoji(emoji)}
                    className="p-2 rounded-full hover:bg-gray-100 transition-colors"
                  >
                    {emoji}
                  </button>
                ))}
              </div>
            </div>
          )}
          
          {/* 附件按钮 */}
          <button className="absolute right-10 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700 transition-colors">
            <Paperclip size={20} />
          </button>
          
          {/* 语音按钮 */}
          <button className="absolute right-18 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700 transition-colors">
            <Mic size={20} />
          </button>
          
          {/* 发送按钮 */}
          <button
            onClick={handleSendMessage}
            disabled={!input.trim() || isTyping}
            className={`absolute right-3 top-1/2 transform -translate-y-1/2 w-8 h-8 rounded-full flex items-center justify-center transition-all ${
              input.trim() && !isTyping 
                ? 'bg-blue-600 text-white hover:bg-blue-700' 
                : 'bg-gray-100 text-gray-400'
            }`}
          >
            {isTyping ? <Loader2 size={16} className="animate-spin" /> : <Send size={16} />}
          </button>
        </div>
      </div>
    </div>
  );
};

export default AIChatWindow;

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档