file in chat question

This commit is contained in:
StyleZhang 2024-09-19 16:54:34 +08:00
parent 396a240e68
commit 2b0c39ed3f
10 changed files with 90 additions and 43 deletions

View File

@ -71,12 +71,7 @@ const ChatInputArea = ({
notify({ type: 'info', message: t('appAnnotation.errorMessage.queryRequired') }) notify({ type: 'info', message: t('appAnnotation.errorMessage.queryRequired') })
return return
} }
onSend(query, files.filter(file => file.progress !== -1).map(fileItem => ({ onSend(query, files)
type: fileItem.fileType,
transfer_method: fileItem.type,
url: fileItem.url || '',
upload_file_id: fileItem.fileStorageId || '',
})))
setQuery('') setQuery('')
setFiles([]) setFiles([])
} }

View File

@ -13,7 +13,6 @@ import type {
ChatItem, ChatItem,
Inputs, Inputs,
PromptVariable, PromptVariable,
VisionFile,
} from '../types' } from '../types'
import { TransferMethod } from '@/types/app' import { TransferMethod } from '@/types/app'
import { useToastContext } from '@/app/components/base/toast' import { useToastContext } from '@/app/components/base/toast'
@ -23,6 +22,8 @@ import type { Annotation } from '@/models/log'
import { WorkflowRunningStatus } from '@/app/components/workflow/types' import { WorkflowRunningStatus } from '@/app/components/workflow/types'
import useTimestamp from '@/hooks/use-timestamp' import useTimestamp from '@/hooks/use-timestamp'
import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player.manager' import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player.manager'
import type { FileEntity } from '@/app/components/base/file-uploader/types'
import { getProcessedFiles } from '@/app/components/base/file-uploader/utils'
type GetAbortController = (abortController: AbortController) => void type GetAbortController = (abortController: AbortController) => void
type SendCallback = { type SendCallback = {
@ -196,7 +197,11 @@ export const useChat = (
const handleSend = useCallback(async ( const handleSend = useCallback(async (
url: string, url: string,
data: any, data: {
query: string
files?: FileEntity[]
[key: string]: any
},
{ {
onGetConversationMessages, onGetConversationMessages,
onGetSuggestedQuestions, onGetSuggestedQuestions,
@ -244,13 +249,16 @@ export const useChat = (
handleResponding(true) handleResponding(true)
hasStopResponded.current = false hasStopResponded.current = false
const { query, files, ...restData } = data
const bodyParams = { const bodyParams = {
response_mode: 'streaming', response_mode: 'streaming',
conversation_id: conversationId.current, conversation_id: conversationId.current,
...data, files: getProcessedFiles(files || []),
query,
...restData,
} }
if (bodyParams?.files?.length) { if (bodyParams?.files?.length) {
bodyParams.files = bodyParams.files.map((item: VisionFile) => { bodyParams.files = bodyParams.files.map((item) => {
if (item.transfer_method === TransferMethod.local_file) { if (item.transfer_method === TransferMethod.local_file) {
return { return {
...item, ...item,

View File

@ -10,7 +10,7 @@ import type { Theme } from '../embedded-chatbot/theme/theme-context'
import { CssTransform } from '../embedded-chatbot/theme/utils' import { CssTransform } from '../embedded-chatbot/theme/utils'
import { User } from '@/app/components/base/icons/src/public/avatar' import { User } from '@/app/components/base/icons/src/public/avatar'
import { Markdown } from '@/app/components/base/markdown' import { Markdown } from '@/app/components/base/markdown'
import ImageGallery from '@/app/components/base/image-gallery' import { FileList } from '@/app/components/base/file-uploader'
type QuestionProps = { type QuestionProps = {
item: ChatItem item: ChatItem
@ -27,7 +27,6 @@ const Question: FC<QuestionProps> = ({
message_files, message_files,
} = item } = item
const imgSrcs = message_files?.length ? message_files.map(item => item.url) : []
return ( return (
<div className='flex justify-end mb-2 last:mb-0 pl-10'> <div className='flex justify-end mb-2 last:mb-0 pl-10'>
<div className='group relative mr-4'> <div className='group relative mr-4'>
@ -36,8 +35,12 @@ const Question: FC<QuestionProps> = ({
style={theme?.chatBubbleColorStyle ? CssTransform(theme.chatBubbleColorStyle) : {}} style={theme?.chatBubbleColorStyle ? CssTransform(theme.chatBubbleColorStyle) : {}}
> >
{ {
!!imgSrcs.length && ( !!message_files?.length && (
<ImageGallery srcs={imgSrcs} /> <FileList
files={message_files}
showDeleteAction={false}
showDownloadAction={false}
/>
) )
} }
<Markdown content={content} /> <Markdown content={content} />

View File

@ -1,6 +1,7 @@
import type { TypeWithI18N } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { TypeWithI18N } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { Annotation, MessageRating } from '@/models/log' import type { Annotation, MessageRating } from '@/models/log'
import type { VisionFile } from '@/types/app' import type { VisionFile } from '@/types/app'
import type { FileEntity } from '@/app/components/base/file-uploader/types'
export type MessageMore = { export type MessageMore = {
time: string time: string
@ -90,7 +91,7 @@ export type IChatItem = {
suggestedQuestions?: string[] suggestedQuestions?: string[]
log?: { role: string; text: string; files?: VisionFile[] }[] log?: { role: string; text: string; files?: VisionFile[] }[]
agent_thoughts?: ThoughtItem[] agent_thoughts?: ThoughtItem[]
message_files?: VisionFile[] message_files?: FileEntity[]
workflow_run_id?: string workflow_run_id?: string
// for agent log // for agent log
conversationId?: string conversationId?: string

View File

@ -1,11 +1,11 @@
import type { import type {
ModelConfig, ModelConfig,
VisionFile,
VisionSettings, VisionSettings,
} from '@/types/app' } from '@/types/app'
import type { IChatItem } from '@/app/components/base/chat/chat/type' import type { IChatItem } from '@/app/components/base/chat/chat/type'
import type { NodeTracing } from '@/types/workflow' import type { NodeTracing } from '@/types/workflow'
import type { WorkflowRunningStatus } from '@/app/components/workflow/types' import type { WorkflowRunningStatus } from '@/app/components/workflow/types'
import type { FileEntity } from '@/app/components/base/file-uploader/types'
export type { VisionFile } from '@/types/app' export type { VisionFile } from '@/types/app'
export { TransferMethod } from '@/types/app' export { TransferMethod } from '@/types/app'
@ -63,7 +63,7 @@ export type ChatItem = IChatItem & {
conversationId?: string conversationId?: string
} }
export type OnSend = (message: string, files?: VisionFile[]) => void export type OnSend = (message: string, files?: FileEntity[]) => void
export type Callback = { export type Callback = {
onSuccess: () => void onSuccess: () => void

View File

@ -1,22 +1,25 @@
import { isImage } from '../utils' import { isImage } from '../utils'
import { useFile } from '../hooks' import { useFile } from '../hooks'
import { useStore } from '../store' import { useStore } from '../store'
import type { FileEntity } from '../types'
import FileImageItem from './file-image-item' import FileImageItem from './file-image-item'
import FileItem from './file-item' import FileItem from './file-item'
import type { FileUpload } from '@/app/components/base/features/types' import type { FileUpload } from '@/app/components/base/features/types'
type FileListProps = { type FileListProps = {
fileConfig: FileUpload files: FileEntity[]
onRemove?: (fileId: string) => void
onReUpload?: (fileId: string) => void
showDeleteAction?: boolean
showDownloadAction?: boolean
} }
const FileList = ({ export const FileList = ({
fileConfig, files,
onReUpload,
onRemove,
showDeleteAction = true,
showDownloadAction = false,
}: FileListProps) => { }: FileListProps) => {
const files = useStore(s => s.files)
const {
handleRemoveFile,
handleReUploadFile,
} = useFile(fileConfig)
return ( return (
<div className='flex flex-wrap gap-2'> <div className='flex flex-wrap gap-2'>
{ {
@ -28,9 +31,9 @@ const FileList = ({
fileId={file.fileId} fileId={file.fileId}
imageUrl={file.base64Url} imageUrl={file.base64Url}
progress={file.progress} progress={file.progress}
showDeleteAction showDeleteAction={showDeleteAction}
onRemove={handleRemoveFile} onRemove={onRemove}
onReUpload={handleReUploadFile} onReUpload={onReUpload}
/> />
) )
} }
@ -41,10 +44,10 @@ const FileList = ({
fileId={file.fileId} fileId={file.fileId}
file={file.file} file={file.file}
progress={file.progress} progress={file.progress}
showDeleteAction showDeleteAction={showDeleteAction}
showDownloadAction={false} showDownloadAction={showDownloadAction}
onRemove={handleRemoveFile} onRemove={onRemove}
onReUpload={handleReUploadFile} onReUpload={onReUpload}
/> />
) )
}) })
@ -53,4 +56,23 @@ const FileList = ({
) )
} }
export default FileList type FileListInChatInputProps = {
fileConfig: FileUpload
}
export const FileListInChatInput = ({
fileConfig,
}: FileListInChatInputProps) => {
const files = useStore(s => s.files)
const {
handleRemoveFile,
handleReUploadFile,
} = useFile(fileConfig)
return (
<FileList
files={files}
onReUpload={handleReUploadFile}
onRemove={handleRemoveFile}
/>
)
}

View File

@ -2,5 +2,6 @@ export { default as FileUploaderInAttachmentWrapper } from './file-uploader-in-a
export { default as FileItemInAttachment } from './file-uploader-in-attachment/file-item' export { default as FileItemInAttachment } from './file-uploader-in-attachment/file-item'
export { default as FileUploaderInChatInput } from './file-uploader-in-chat-input' export { default as FileUploaderInChatInput } from './file-uploader-in-chat-input'
export { default as FileTypeIcon } from './file-type-icon' export { default as FileTypeIcon } from './file-type-icon'
export { default as FileListInChatInput } from './file-uploader-in-chat-input/file-list' export { FileListInChatInput } from './file-uploader-in-chat-input/file-list'
export { default as FileItemInChatInput } from './file-uploader-in-chat-input/file-list' export { FileList } from './file-uploader-in-chat-input/file-list'
export { default as FileItem } from './file-uploader-in-chat-input/file-item'

View File

@ -1,4 +1,5 @@
import { FileAppearanceTypeEnum } from './types' import { FileAppearanceTypeEnum } from './types'
import type { FileEntity } from './types'
import { upload } from '@/service/base' import { upload } from '@/service/base'
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
@ -83,3 +84,12 @@ export const getFileType = (file?: File) => {
return '' return ''
} }
export const getProcessedFiles = (files: FileEntity[]) => {
return files.filter(file => file.progress !== -1).map(fileItem => ({
type: fileItem.fileType,
transfer_method: fileItem.type,
url: fileItem.url || '',
upload_file_id: fileItem.fileStorageId || '',
}))
}

View File

@ -407,9 +407,9 @@ const WorkflowWrap = memo(() => {
const initialFeatures: FeaturesData = { const initialFeatures: FeaturesData = {
file: { file: {
image: { image: {
enabled: !!features.file_upload?.image.enabled, enabled: !!features.file_upload?.image?.enabled,
number_limits: features.file_upload?.image.number_limits || 3, number_limits: features.file_upload?.image?.number_limits || 3,
transfer_methods: features.file_upload?.image.transfer_methods || ['local_file', 'remote_url'], transfer_methods: features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
}, },
enabled: !!(features.file_upload?.enabled || features.file_upload?.image?.enabled), enabled: !!(features.file_upload?.enabled || features.file_upload?.image?.enabled),
allowed_file_types: features.file_upload?.allowed_file_types || [SupportUploadFileTypes.image], allowed_file_types: features.file_upload?.allowed_file_types || [SupportUploadFileTypes.image],

View File

@ -15,8 +15,9 @@ import type {
} from '@/app/components/base/chat/types' } from '@/app/components/base/chat/types'
import { useToastContext } from '@/app/components/base/toast' import { useToastContext } from '@/app/components/base/toast'
import { TransferMethod } from '@/types/app' import { TransferMethod } from '@/types/app'
import type { VisionFile } from '@/types/app'
import { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel/utils' import { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel/utils'
import { getProcessedFiles } from '@/app/components/base/file-uploader/utils'
import type { FileEntity } from '@/app/components/base/file-uploader/types'
type GetAbortController = (abortController: AbortController) => void type GetAbortController = (abortController: AbortController) => void
type SendCallback = { type SendCallback = {
@ -143,7 +144,11 @@ export const useChat = (
}, [handleUpdateChatList]) }, [handleUpdateChatList])
const handleSend = useCallback(( const handleSend = useCallback((
params: any, params: {
query: string
files?: FileEntity[]
[key: string]: any
},
{ {
onGetSuggestedQuestions, onGetSuggestedQuestions,
}: SendCallback, }: SendCallback,
@ -182,12 +187,14 @@ export const useChat = (
handleResponding(true) handleResponding(true)
const { files, ...restParams } = params
const bodyParams = { const bodyParams = {
conversation_id: conversationId.current, conversation_id: conversationId.current,
...params, files: getProcessedFiles(files || []),
...restParams,
} }
if (bodyParams?.files?.length) { if (bodyParams?.files?.length) {
bodyParams.files = bodyParams.files.map((item: VisionFile) => { bodyParams.files = bodyParams.files.map((item) => {
if (item.transfer_method === TransferMethod.local_file) { if (item.transfer_method === TransferMethod.local_file) {
return { return {
...item, ...item,