'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' import FileUploaderInAttachmentWrapper from '@/app/components/base/file-uploader-in-attachment' import type { FileEntity, FileUpload } from '@/app/components/base/file-uploader-in-attachment/types' import { getProcessedFiles } from '@/app/components/base/file-uploader-in-attachment/utils' export interface 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 isResponding?: boolean controlClearQuery?: number visionConfig?: VisionSettings fileConfig?: FileUpload } const Chat: FC = ({ chatList, feedbackDisabled = false, isHideSendInput = false, onFeedback, checkCanSend, onSend = () => { }, useCurrentUserAvatar, isResponding, controlClearQuery, visionConfig, fileConfig, }) => { const { t } = useTranslation() const { notify } = Toast const isUseInputMethod = useRef(false) const [query, setQuery] = React.useState('') const queryRef = useRef('') const handleContentChange = (e: any) => { const value = e.target.value setQuery(value) queryRef.current = value } const logError = (message: string) => { notify({ type: 'error', message, duration: 3000 }) } const valid = () => { const query = queryRef.current if (!query || query.trim() === '') { logError(t('app.errorMessage.valueOfVarRequired')) return false } return true } useEffect(() => { if (controlClearQuery) { setQuery('') queryRef.current = '' } }, [controlClearQuery]) const { files, onUpload, onRemove, onReUpload, onImageLinkLoadError, onImageLinkLoadSuccess, onClear, } = useImageFiles() const [attachmentFiles, setAttachmentFiles] = React.useState([]) const handleSend = () => { if (!valid() || (checkCanSend && !checkCanSend())) { return } const imageFiles: VisionFile[] = files.filter(file => file.progress !== -1).map(fileItem => ({ type: 'image', transfer_method: fileItem.type, url: fileItem.url, upload_file_id: fileItem.fileId, })) const docAndOtherFiles: VisionFile[] = getProcessedFiles(attachmentFiles) const combinedFiles: VisionFile[] = [...imageFiles, ...docAndOtherFiles] onSend(queryRef.current, combinedFiles) if (!files.find(item => item.type === TransferMethod.local_file && !item.fileId)) { if (files.length) { onClear() } if (!isResponding) { setQuery('') queryRef.current = '' } } if (!attachmentFiles.find(item => item.transferMethod === TransferMethod.local_file && !item.uploadedId)) { setAttachmentFiles([]) } } 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) { const result = query.replace(/\n$/, '') setQuery(result) queryRef.current = result e.preventDefault() } } const suggestionClick = (suggestion: string) => { setQuery(suggestion) queryRef.current = suggestion handleSend() } 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} />
) } { fileConfig?.enabled && (
) }