import { CommentOutlined, PictureOutlined, StopOutlined } from '@ant-design/icons'; import { message as antdMessage, Button, Input, Tooltip, Upload } from 'antd'; import React, { useRef, useState } from 'react'; import type { VisionFile } from '~/api/dify-chat'; import '../../styles/components/chat-with-llm/chat-input.css'; const { TextArea } = Input; interface ChatInputProps { onSendMessage: (message: string, files?: VisionFile[]) => void; disabled?: boolean; placeholder?: string; onStop?: () => void; isResponding?: boolean; visionConfig?: { enabled: boolean; number_limits?: number; image_file_size_limit?: number; transfer_methods?: string[]; }; } /** * 聊天输入组件 */ export default function ChatInput({ onSendMessage, disabled = false, placeholder = '输入消息...', onStop, isResponding = false, visionConfig, }: ChatInputProps) { const [message, setMessage] = useState(''); const [files, setFiles] = useState([]); const textareaRef = useRef(null); const isComposing = useRef(false); /** * 提交消息 */ const handleSubmit = () => { if (!message.trim() || disabled) return; onSendMessage(message, files.length > 0 ? files : undefined); setMessage(''); setFiles([]); // 聚焦回输入框 setTimeout(() => { textareaRef.current?.focus(); }, 10); }; /** * 处理键盘事件 */ const handleKeyDown = (e: React.KeyboardEvent) => { // 处理输入法状态 if (e.nativeEvent.isComposing) { isComposing.current = true; return; } // Enter发送,Shift+Enter换行 if (e.key === 'Enter' && !e.shiftKey && !isComposing.current) { e.preventDefault(); handleSubmit(); } }; /** * 处理输入法结束 */ const handleCompositionEnd = () => { isComposing.current = false; }; /** * 停止响应 */ const handleStop = () => { onStop?.(); }; /** * 处理文件上传 */ const handleFileUpload = (file: File) => { // 检查文件数量限制 if (visionConfig?.number_limits && files.length >= visionConfig.number_limits) { antdMessage.error(`最多只能上传 ${visionConfig.number_limits} 个文件`); return false; } // 检查文件大小限制 if (visionConfig?.image_file_size_limit) { const limitMB = visionConfig.image_file_size_limit; const fileSizeMB = file.size / (1024 * 1024); if (fileSizeMB > limitMB) { antdMessage.error(`文件大小不能超过 ${limitMB}MB`); return false; } } // 检查文件类型 if (!file.type.startsWith('image/')) { antdMessage.error('只支持图片文件'); return false; } // 创建文件对象 const reader = new FileReader(); reader.onload = (e) => { const newFile: VisionFile = { id: `file-${Date.now()}-${Math.random()}`, type: 'image', transfer_method: 'local_file' as any, url: e.target?.result as string, upload_file_id: '', }; setFiles(prev => [...prev, newFile]); }; reader.readAsDataURL(file); return false; // 阻止默认上传行为 }; /** * 移除文件 */ const handleRemoveFile = (fileId: string) => { setFiles(prev => prev.filter(file => file.id !== fileId)); }; /** * 渲染文件预览 */ const renderFilePreview = () => { if (files.length === 0) return null; return (
{files.map((file) => (
预览
))}
); }; /** * 渲染上传按钮 */ const renderUploadButton = () => { if (!visionConfig?.enabled) return null; const isDisabled = disabled || (visionConfig.number_limits ? files.length >= visionConfig.number_limits : false); return (