190 lines
6.3 KiB
TypeScript
190 lines
6.3 KiB
TypeScript
import React, { useState, useRef, useEffect } from 'react';
|
||
import { Send, Bot, User } from 'lucide-react';
|
||
|
||
interface Message {
|
||
id: string;
|
||
content: string;
|
||
role: 'user' | 'assistant';
|
||
timestamp: Date;
|
||
}
|
||
|
||
export const ChatInterface: React.FC = () => {
|
||
const [messages, setMessages] = useState<Message[]>([
|
||
{
|
||
id: '1',
|
||
content: '你好!我是AI助手,有什么可以帮助您的吗?',
|
||
role: 'assistant',
|
||
timestamp: new Date()
|
||
}
|
||
]);
|
||
const [inputValue, setInputValue] = useState('');
|
||
const [isLoading, setIsLoading] = useState(false);
|
||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||
|
||
// 自动滚动到最新消息
|
||
const scrollToBottom = () => {
|
||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||
};
|
||
|
||
useEffect(() => {
|
||
scrollToBottom();
|
||
}, [messages]);
|
||
|
||
// 发送消息
|
||
const handleSend = async () => {
|
||
if (!inputValue.trim() || isLoading) return;
|
||
|
||
const userMessage: Message = {
|
||
id: Date.now().toString(),
|
||
content: inputValue.trim(),
|
||
role: 'user',
|
||
timestamp: new Date()
|
||
};
|
||
|
||
setMessages(prev => [...prev, userMessage]);
|
||
setInputValue('');
|
||
setIsLoading(true);
|
||
|
||
// 模拟AI回复
|
||
setTimeout(() => {
|
||
const aiMessage: Message = {
|
||
id: (Date.now() + 1).toString(),
|
||
content: `我收到了您的消息:"${userMessage.content}"。这里是我的回复,您还有其他问题吗?`,
|
||
role: 'assistant',
|
||
timestamp: new Date()
|
||
};
|
||
setMessages(prev => [...prev, aiMessage]);
|
||
setIsLoading(false);
|
||
}, 1000);
|
||
};
|
||
|
||
// 处理键盘事件
|
||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||
if (e.key === 'Enter' && !e.shiftKey) {
|
||
e.preventDefault();
|
||
handleSend();
|
||
}
|
||
};
|
||
|
||
// 格式化时间
|
||
const formatTime = (date: Date) => {
|
||
return date.toLocaleTimeString('zh-CN', {
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
});
|
||
};
|
||
|
||
return (
|
||
<div className="h-full flex flex-col bg-gray-50">
|
||
{/* 头部 */}
|
||
<div className="bg-white border-b border-gray-200 p-4 shadow-sm">
|
||
<div className="flex items-center gap-3">
|
||
<div className="w-10 h-10 bg-blue-500 rounded-full flex items-center justify-center">
|
||
<Bot className="w-6 h-6 text-white" />
|
||
</div>
|
||
<div>
|
||
<h1 className="text-xl font-semibold text-gray-900">智能工作台</h1>
|
||
<p className="text-sm text-gray-500">在线 · 随时为您服务</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 对话列表区域 */}
|
||
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
||
{messages.map((message) => (
|
||
<div
|
||
key={message.id}
|
||
className={`flex gap-3 ${
|
||
message.role === 'user' ? 'justify-end' : 'justify-start'
|
||
}`}
|
||
>
|
||
{message.role === 'assistant' && (
|
||
<div className="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center flex-shrink-0">
|
||
<Bot className="w-5 h-5 text-white" />
|
||
</div>
|
||
)}
|
||
|
||
<div
|
||
className={`max-w-[70%] rounded-lg px-4 py-2 ${
|
||
message.role === 'user'
|
||
? 'bg-blue-500 text-white'
|
||
: 'bg-white text-gray-900 shadow-sm border border-gray-200'
|
||
}`}
|
||
>
|
||
<div className="text-sm leading-relaxed whitespace-pre-wrap">
|
||
{message.content}
|
||
</div>
|
||
<div
|
||
className={`text-xs mt-1 ${
|
||
message.role === 'user' ? 'text-blue-100' : 'text-gray-500'
|
||
}`}
|
||
>
|
||
{formatTime(message.timestamp)}
|
||
</div>
|
||
</div>
|
||
|
||
{message.role === 'user' && (
|
||
<div className="w-8 h-8 bg-gray-600 rounded-full flex items-center justify-center flex-shrink-0">
|
||
<User className="w-5 h-5 text-white" />
|
||
</div>
|
||
)}
|
||
</div>
|
||
))}
|
||
|
||
{/* 加载状态 */}
|
||
{isLoading && (
|
||
<div className="flex gap-3 justify-start">
|
||
<div className="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center flex-shrink-0">
|
||
<Bot className="w-5 h-5 text-white" />
|
||
</div>
|
||
<div className="bg-white rounded-lg px-4 py-2 shadow-sm border border-gray-200">
|
||
<div className="flex space-x-1">
|
||
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
|
||
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.1s' }}></div>
|
||
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
<div ref={messagesEndRef} />
|
||
</div>
|
||
|
||
{/* 输入框区域 */}
|
||
<div className="bg-white border-t border-gray-200 p-4">
|
||
<div className="flex gap-3 items-end">
|
||
<div className="flex-1 relative">
|
||
<textarea
|
||
ref={inputRef}
|
||
value={inputValue}
|
||
onChange={(e) => setInputValue(e.target.value)}
|
||
onKeyDown={handleKeyDown}
|
||
placeholder="输入您的消息... (按 Enter 发送,Shift+Enter 换行)"
|
||
className="w-full px-4 py-3 border border-gray-300 rounded-lg resize-none focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||
rows={1}
|
||
style={{ minHeight: '96px', maxHeight: '180px' }}
|
||
disabled={isLoading}
|
||
/>
|
||
</div>
|
||
<button
|
||
onClick={handleSend}
|
||
disabled={!inputValue.trim() || isLoading}
|
||
className={`p-3 rounded-lg flex items-center justify-center transition-colors ${
|
||
!inputValue.trim() || isLoading
|
||
? 'bg-gray-300 cursor-not-allowed'
|
||
: 'bg-blue-500 hover:bg-blue-600 text-white'
|
||
}`}
|
||
>
|
||
<Send className="w-5 h-5" />
|
||
</button>
|
||
</div>
|
||
|
||
{/* 提示文本 */}
|
||
<div className="mt-2 text-xs text-gray-500 text-center">
|
||
AI助手会根据您的输入生成回复,请文明使用
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}; |