import { useStore } from '@nanostores/react'; import classNames from 'classnames'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { ClientOnly } from 'remix-utils/client-only'; import { useAuth, usePromptEnhancer } from '~/.client/hooks'; import { aiState } from '~/.client/stores/ai-state'; import { IconButton } from '../ui/IconButton'; import { SendButton } from './SendButton.client'; interface ChatTextareaProps { uploadFiles: File[]; setUploadFiles: (files: File[]) => void; onSendMessage: (message: string) => void; onStopMessage: () => void; } const TEXTAREA_MIN_HEIGHT = 76; export const ChatTextarea = ({ uploadFiles, setUploadFiles, onSendMessage, onStopMessage }: ChatTextareaProps) => { const { isAuthenticated, signIn } = useAuth(); const { chatStarted, isStreaming } = useStore(aiState); const { enhancedInput, isLoading, enhancePrompt, resetEnhancer } = usePromptEnhancer(); const [input, setInput] = useState(''); const textareaRef = useRef(null); // 检测当前 URL 是否包含登录回调参数 useEffect(() => { if (typeof window !== 'undefined') { const savedMessage = localStorage.getItem('pendingChatMessage'); // 如果是从登录页面回调回来的,检查 localStorage 中是否有待发送的消息 if (savedMessage && isAuthenticated) { try { const msgData = JSON.parse(savedMessage); requestAnimationFrame(() => { if (msgData.messageInput) { setInput(msgData.messageInput); sendMessage(); } }); } catch (e) { console.error('Error parsing saved message:', e); } finally { localStorage.removeItem('pendingChatMessage'); } } } }, [isAuthenticated]); useEffect(() => { setInput(enhancedInput); scrollTextArea(); }, [enhancedInput]); const TEXTAREA_MAX_HEIGHT = useMemo(() => { return chatStarted ? 400 : 200; }, [chatStarted]); const scrollTextArea = useCallback(() => { const textarea = textareaRef.current; if (textarea) { textarea.scrollTop = textarea.scrollHeight; } }, [textareaRef]); const handleEnhancePrompt = useCallback(async () => { try { await enhancePrompt(input); } catch (error) { console.error('Error enhancing prompt:', error); } }, [input]); const sendMessage = async () => { if (!input?.trim()) { return; } onSendMessage(input); setInput(''); setUploadFiles([]); resetEnhancer(); textareaRef.current?.blur(); }; useEffect(() => { const textarea = textareaRef.current; if (textarea) { textarea.style.height = 'auto'; const scrollHeight = textarea.scrollHeight; textarea.style.height = `${Math.min(scrollHeight, TEXTAREA_MAX_HEIGHT)}px`; textarea.style.overflowY = scrollHeight > TEXTAREA_MAX_HEIGHT ? 'auto' : 'hidden'; } }, [input, textareaRef]); const handleSendMessage = () => { if (!isAuthenticated) { if (input) { const savedMsg = { messageInput: input, timestamp: new Date().getTime(), }; localStorage.setItem('pendingChatMessage', JSON.stringify(savedMsg)); signIn(); return; } } if (sendMessage) { sendMessage(); } }; const handlePaste = async (e: React.ClipboardEvent) => { const items = e.clipboardData?.items; if (!items) { return; } const files: File[] = []; for (const item of items) { if (item.type.startsWith('image/')) { e.preventDefault(); const file = item.getAsFile(); if (file) { files.push(file); } } } handleFileReader(files); }; const handleFileUpload = useCallback(() => { const input = document.createElement('input'); input.type = 'file'; input.accept = 'image/*'; input.onchange = async (e) => { const files = (e.target as HTMLInputElement).files; handleFileReader(files ? Array.from(files) : []); }; input.click(); }, [uploadFiles]); const handleFileReader = (files: File[]) => { files.forEach((file) => { setUploadFiles?.([...uploadFiles, file]); }); }; return (