diff --git a/web/app/components/app/chat/index.tsx b/web/app/components/app/chat/index.tsx index fe8ff5fa43..94eeb955c4 100644 --- a/web/app/components/app/chat/index.tsx +++ b/web/app/components/app/chat/index.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC, ReactNode } from 'react' import React, { useEffect, useLayoutEffect, useRef, useState } from 'react' +import Textarea from 'rc-textarea' import { useContext } from 'use-context-selector' import cn from 'classnames' import Recorder from 'js-audio-recorder' @@ -10,9 +11,8 @@ import type { DisplayScene, FeedbackFunc, IChatItem, SubmitAnnotationFunc } from import { TryToAskIcon, stopIcon } from './icon-component' import Answer from './answer' import Question from './question' -import Tooltip from '@/app/components/base/tooltip' +import TooltipPlus from '@/app/components/base/tooltip-plus' import { ToastContext } from '@/app/components/base/toast' -import AutoHeightTextarea from '@/app/components/base/auto-height-textarea' import Button from '@/app/components/base/button' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import VoiceInput from '@/app/components/base/voice-input' @@ -20,6 +20,10 @@ import { Microphone01 } from '@/app/components/base/icons/src/vender/line/mediaA import { Microphone01 as Microphone01Solid } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' import { XCircle } from '@/app/components/base/icons/src/vender/solid/general' import type { DataSet } from '@/models/datasets' +import ChatImageUploader from '@/app/components/base/image-uploader/chat-image-uploader' +import ImageList from '@/app/components/base/image-uploader/image-list' +import { TransferMethod, type VisionFile, type VisionSettings } from '@/types/app' +import { useImageFiles } from '@/app/components/base/image-uploader/hooks' export type IChatProps = { configElem?: React.ReactNode @@ -37,7 +41,7 @@ export type IChatProps = { onFeedback?: FeedbackFunc onSubmitAnnotation?: SubmitAnnotationFunc checkCanSend?: () => boolean - onSend?: (message: string) => void + onSend?: (message: string, files: VisionFile[]) => void displayScene?: DisplayScene useCurrentUserAvatar?: boolean isResponsing?: boolean @@ -54,6 +58,7 @@ export type IChatProps = { dataSets?: DataSet[] isShowCitationHitInfo?: boolean isShowPromptLog?: boolean + visionConfig?: VisionSettings } const Chat: FC = ({ @@ -83,9 +88,19 @@ const Chat: FC = ({ dataSets, isShowCitationHitInfo, isShowPromptLog, + visionConfig, }) => { const { t } = useTranslation() const { notify } = useContext(ToastContext) + const { + files, + onUpload, + onRemove, + onReUpload, + onImageLinkLoadError, + onImageLinkLoadSuccess, + onClear, + } = useImageFiles() const isUseInputMethod = useRef(false) const [query, setQuery] = React.useState('') @@ -114,9 +129,18 @@ const Chat: FC = ({ const handleSend = () => { if (!valid() || (checkCanSend && !checkCanSend())) return - onSend(query) - if (!isResponsing) - setQuery('') + 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: React.KeyboardEvent) => { @@ -198,6 +222,8 @@ const Chat: FC = ({ item={item} isShowPromptLog={isShowPromptLog} isResponsing={isResponsing} + // ['https://placekitten.com/360/360', 'https://placekitten.com/360/640'] + imgSrcs={(item.message_files && item.message_files?.length > 0) ? item.message_files.map(item => item.url) : []} /> ) })} @@ -246,18 +272,42 @@ const Chat: FC = ({ ) } -
- + { + visionConfig?.enabled && ( + <> +
+ = visionConfig.number_limits} + /> +
+
+
+ +
+ + ) + } +