'use client' import type { FC } from 'react' import React, { useEffect, useRef } from 'react' import cn from 'classnames' import { useTranslation } from 'react-i18next' import Textarea from 'rc-textarea' import s from './style.module.css' import Answer from './answer' import Question from './question' import type { FeedbackFunc } from './type' import type { ChatItem, VisionFile, VisionSettings } from '@/types/app' import { TransferMethod } from '@/types/app' import Tooltip from '@/app/components/base/tooltip' import Toast from '@/app/components/base/toast' import ChatImageUploader from '@/app/components/base/image-uploader/chat-image-uploader' import ImageList from '@/app/components/base/image-uploader/image-list' import { useImageFiles } from '@/app/components/base/image-uploader/hooks' export type IChatProps = { chatList: ChatItem[] /** * Whether to display the editing area and rating status */ feedbackDisabled?: boolean /** * Whether to display the input area */ isHideSendInput?: boolean onFeedback?: FeedbackFunc checkCanSend?: () => boolean onSend?: (message: string, files: VisionFile[]) => void useCurrentUserAvatar?: boolean isResponsing?: boolean controlClearQuery?: number visionConfig?: VisionSettings } const Chat: FC = ({ chatList, feedbackDisabled = false, isHideSendInput = false, onFeedback, checkCanSend, onSend = () => { }, useCurrentUserAvatar, isResponsing, controlClearQuery, visionConfig, }) => { const { t } = useTranslation() const { notify } = Toast const isUseInputMethod = useRef(false) const [query, setQuery] = React.useState('') const handleContentChange = (e: any) => { const value = e.target.value setQuery(value) } const logError = (message: string) => { notify({ type: 'error', message, duration: 3000 }) } const valid = () => { if (!query || query.trim() === '') { logError('Message cannot be empty') return false } return true } useEffect(() => { if (controlClearQuery) setQuery('') }, [controlClearQuery]) const { files, onUpload, onRemove, onReUpload, onImageLinkLoadError, onImageLinkLoadSuccess, onClear, } = useImageFiles() const handleSend = () => { if (!valid() || (checkCanSend && !checkCanSend())) return onSend(query, files.filter(file => file.progress !== -1).map(fileItem => ({ type: 'image', transfer_method: fileItem.type, url: fileItem.url, upload_file_id: fileItem.fileId, }))) if (!files.find(item => item.type === TransferMethod.local_file && !item.fileId)) { if (files.length) onClear() if (!isResponsing) setQuery('') } } const handleKeyUp = (e: any) => { if (e.code === 'Enter') { e.preventDefault() // prevent send message when using input method enter if (!e.shiftKey && !isUseInputMethod.current) handleSend() } } const handleKeyDown = (e: any) => { isUseInputMethod.current = e.nativeEvent.isComposing if (e.code === 'Enter' && !e.shiftKey) { setQuery(query.replace(/\n$/, '')) e.preventDefault() } } return (
{/* Chat List */}
{chatList.map((item) => { if (item.isAnswer) { const isLast = item.id === chatList[chatList.length - 1].id return } return ( 0) ? item.message_files.map(item => item.url) : []} /> ) })}
{ !isHideSendInput && (
{ visionConfig?.enabled && ( <>
= visionConfig.number_limits} />
) }