import { useState, useRef, useEffect, useCallback } from 'react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; import { Send, Square, Bot, User, Copy, Check, Menu, Sparkles, RefreshCw, } from 'lucide-react'; import type { Conversation, Message } from '@/types'; interface ChatAreaProps { conversation: Conversation | null; isStreaming: boolean; onSendMessage: (content: string) => void; onRegenerateMessage: (messageId: string) => void; onStopStreaming: () => void; sidebarOpen: boolean; onToggleSidebar: () => void; } function CodeBlock({ language, value }: { language: string; value: string }) { const [copied, setCopied] = useState(false); const handleCopy = async () => { await navigator.clipboard.writeText(value); setCopied(true); setTimeout(() => setCopied(false), 2000); }; return (
{language || 'text'}
{value}
); } function MessageBubble({ message, isStreaming, onRegenerate }: { message: Message; isStreaming: boolean; onRegenerate?: () => void }) { const [copied, setCopied] = useState(false); const [thinkingOpen, setThinkingOpen] = useState(false); const handleCopy = async () => { await navigator.clipboard.writeText(message.content); setCopied(true); setTimeout(() => setCopied(false), 2000); }; if (message.role === 'user') { return (
{new Date(message.timestamp).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}

{message.content}

); } return (
{new Date(message.timestamp).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}
{/* 思考过程(可折叠) */} {message.thinking && (
{thinkingOpen && (
{message.thinking}
)}
)} {message.content || isStreaming ? (
; } return ( {children} ); }, p({ children }) { return

{children}

; }, ul({ children }) { return
    {children}
; }, ol({ children }) { return
    {children}
; }, li({ children }) { return
  • {children}
  • ; }, h1({ children }) { return

    {children}

    ; }, h2({ children }) { return

    {children}

    ; }, h3({ children }) { return

    {children}

    ; }, blockquote({ children }) { return (
    {children}
    ); }, table({ children }) { return (
    {children}
    ); }, thead({ children }) { return {children}; }, th({ children }) { return {children}; }, td({ children }) { return {children}; }, hr() { return
    ; }, a({ children, href }) { return ( {children} ); }, }} > {message.content || (isStreaming ? '' : '')}
    {/* 流式输出光标 */} {isStreaming && !message.content && ( )}
    ) : (
    )} {/* Token 用量 */} {(message.promptTokens || message.completionTokens) && (
    {message.promptTokens ? 输入: {message.promptTokens} tokens : null} {message.completionTokens ? 输出: {message.completionTokens} tokens : null}
    )} {/* 操作按钮:复制 + 重新生成 */}
    {message.content && ( )} {onRegenerate && message.content && !isStreaming && ( )}
    ); } export default function ChatArea({ conversation, isStreaming, onSendMessage, onRegenerateMessage, onStopStreaming, sidebarOpen, onToggleSidebar, }: ChatAreaProps) { const [input, setInput] = useState(''); const textareaRef = useRef(null); const messagesEndRef = useRef(null); // 输入框自动调整高度 useEffect(() => { const textarea = textareaRef.current; if (textarea) { textarea.style.height = 'auto'; textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px`; } }, [input]); // 自动滚动到底部 useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [conversation?.messages]); const handleSubmit = useCallback(() => { const trimmed = input.trim(); if (!trimmed || isStreaming) return; onSendMessage(trimmed); setInput(''); if (textareaRef.current) { textareaRef.current.style.height = 'auto'; } }, [input, isStreaming, onSendMessage]); const handleKeyDown = useCallback((e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSubmit(); } }, [handleSubmit]); return (
    {/* 顶部标题栏 */}
    {!sidebarOpen && ( )}

    {conversation?.title || '新对话'}

    {conversation && (

    {conversation.modelConfig.model} · temp: {conversation.modelConfig.temperature}

    )}
    {conversation && (
    {conversation.modelConfig.baseUrl.replace(/^https?:\/\//, '').split('/')[0]}
    )}
    {/* 消息列表区域 */}
    {!conversation || conversation.messages.length === 0 ? (

    开始一段新的对话

    在下方输入框中输入你的问题

    {[ '解释一下React的useEffect原理', '写一段Python快速排序算法', '分析一下Linux内存管理机制', '帮我写一个Dockerfile模板', ].map((prompt, i) => ( ))}
    ) : (
    {conversation.messages.map((message, idx) => ( { // 找到该助手消息对应的用户消息 const userMsg = conversation.messages[idx - 1]; if (userMsg && userMsg.role === 'user') { onRegenerateMessage(userMsg.id); } }} /> ))}
    )}
    {/* 输入区域 */}