Feat/attachments (#9526)
Co-authored-by: Joel <iamjoel007@gmail.com> Co-authored-by: JzoNg <jzongcode@gmail.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import {
|
||||
forwardRef,
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
@@ -19,7 +20,7 @@ import { useChat } from './hooks'
|
||||
import type { ChatWrapperRefType } from './index'
|
||||
import Chat from '@/app/components/base/chat/chat'
|
||||
import type { ChatItem, OnSend } from '@/app/components/base/chat/types'
|
||||
import { useFeaturesStore } from '@/app/components/base/features/hooks'
|
||||
import { useFeatures } from '@/app/components/base/features/hooks'
|
||||
import {
|
||||
fetchSuggestedQuestions,
|
||||
stopChatMessageResponding,
|
||||
@@ -31,22 +32,26 @@ type ChatWrapperProps = {
|
||||
showConversationVariableModal: boolean
|
||||
onConversationModalHide: () => void
|
||||
showInputsFieldsPanel: boolean
|
||||
onHide: () => void
|
||||
}
|
||||
|
||||
const ChatWrapper = forwardRef<ChatWrapperRefType, ChatWrapperProps>(({ showConversationVariableModal, onConversationModalHide, showInputsFieldsPanel }, ref) => {
|
||||
const ChatWrapper = forwardRef<ChatWrapperRefType, ChatWrapperProps>(({
|
||||
showConversationVariableModal,
|
||||
onConversationModalHide,
|
||||
showInputsFieldsPanel,
|
||||
onHide,
|
||||
}, ref) => {
|
||||
const nodes = useNodes<StartNodeType>()
|
||||
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
|
||||
const startVariables = startNode?.data.variables
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
const workflowStore = useWorkflowStore()
|
||||
const featuresStore = useFeaturesStore()
|
||||
const inputs = useStore(s => s.inputs)
|
||||
const features = featuresStore!.getState().features
|
||||
|
||||
const features = useFeatures(s => s.features)
|
||||
const config = useMemo(() => {
|
||||
return {
|
||||
opening_statement: features.opening?.opening_statement || '',
|
||||
suggested_questions: features.opening?.suggested_questions || [],
|
||||
opening_statement: features.opening?.enabled ? (features.opening?.opening_statement || '') : '',
|
||||
suggested_questions: features.opening?.enabled ? (features.opening?.suggested_questions || []) : [],
|
||||
suggested_questions_after_answer: features.suggested,
|
||||
text_to_speech: features.text2speech,
|
||||
speech_to_text: features.speech2text,
|
||||
@@ -54,7 +59,8 @@ const ChatWrapper = forwardRef<ChatWrapperRefType, ChatWrapperProps>(({ showConv
|
||||
sensitive_word_avoidance: features.moderation,
|
||||
file_upload: features.file,
|
||||
}
|
||||
}, [features])
|
||||
}, [features.opening, features.suggested, features.text2speech, features.speech2text, features.citation, features.moderation, features.file])
|
||||
const setShowFeaturesPanel = useStore(s => s.setShowFeaturesPanel)
|
||||
|
||||
const {
|
||||
conversationId,
|
||||
@@ -70,7 +76,7 @@ const ChatWrapper = forwardRef<ChatWrapperRefType, ChatWrapperProps>(({ showConv
|
||||
config,
|
||||
{
|
||||
inputs,
|
||||
promptVariables: (startVariables as any) || [],
|
||||
inputsForm: (startVariables || []) as any,
|
||||
},
|
||||
[],
|
||||
taskId => stopChatMessageResponding(appDetail!.id, taskId),
|
||||
@@ -113,6 +119,11 @@ const ChatWrapper = forwardRef<ChatWrapperRefType, ChatWrapperProps>(({ showConv
|
||||
}
|
||||
}, [handleRestart])
|
||||
|
||||
useEffect(() => {
|
||||
if (isResponding)
|
||||
onHide()
|
||||
}, [isResponding, onHide])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Chat
|
||||
@@ -125,8 +136,13 @@ const ChatWrapper = forwardRef<ChatWrapperRefType, ChatWrapperProps>(({ showConv
|
||||
chatContainerClassName='px-3'
|
||||
chatContainerInnerClassName='pt-6 w-full max-w-full mx-auto'
|
||||
chatFooterClassName='px-4 rounded-bl-2xl'
|
||||
chatFooterInnerClassName='pb-4 w-full max-w-full mx-auto'
|
||||
chatFooterInnerClassName='pb-0'
|
||||
showFileUpload
|
||||
showFeatureBar
|
||||
onFeatureBarClick={setShowFeaturesPanel}
|
||||
onSend={doSend}
|
||||
inputs={inputs}
|
||||
inputsForm={(startVariables || []) as any}
|
||||
onRegenerate={doRegenerate}
|
||||
onStopResponding={handleStop}
|
||||
chatNode={(
|
||||
|
||||
@@ -96,14 +96,14 @@ const ConversationVariableModal = ({
|
||||
</div>
|
||||
</div>
|
||||
{/* RIGHT */}
|
||||
<div className='grow flex flex-col h-full bg-components-panel-bg'>
|
||||
<div className='grow flex flex-col w-0 h-full bg-components-panel-bg'>
|
||||
<div className='shrink-0 p-4 pb-2'>
|
||||
<div className='flex items-center gap-1 py-1'>
|
||||
<div className='text-text-primary system-xl-semibold'>{currentVar.name}</div>
|
||||
<div className='text-text-tertiary system-xs-medium'>{capitalize(currentVar.value_type)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='grow p-4 pt-2 flex flex-col'>
|
||||
<div className='grow p-4 pt-2 flex flex-col h-0'>
|
||||
<div className='shrink-0 mb-2 flex items-center gap-2'>
|
||||
<div className='shrink-0 text-text-tertiary system-xs-medium-uppercase'>{t('workflow.chatVariable.storedContent').toLocaleUpperCase()}</div>
|
||||
<div className='grow h-[1px]' style={{
|
||||
@@ -113,7 +113,7 @@ const ConversationVariableModal = ({
|
||||
<div className='shrink-0 text-text-tertiary system-xs-regular'>{t('workflow.chatVariable.updatedAt')}{formatTime(latestValueTimestampMap[currentVar.id], t('appLog.dateTimeFormat') as string)}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='grow'>
|
||||
<div className='grow overflow-y-auto'>
|
||||
{currentVar.value_type !== ChatVarType.Number && currentVar.value_type !== ChatVarType.String && (
|
||||
<div className='h-full flex flex-col bg-components-input-bg-normal rounded-lg px-2 pb-2'>
|
||||
<div className='shrink-0 flex justify-between items-center h-7 pt-1 pl-3 pr-2'>
|
||||
@@ -142,7 +142,7 @@ const ConversationVariableModal = ({
|
||||
</div>
|
||||
)}
|
||||
{(currentVar.value_type === ChatVarType.Number || currentVar.value_type === ChatVarType.String) && (
|
||||
<div className='h-full px-4 py-3 rounded-lg bg-components-input-bg-normal text-components-input-text-filled system-md-regular overflow-y-auto'>{latestValueMap[currentVar.id] || ''}</div>
|
||||
<div className='h-full px-4 py-3 rounded-lg bg-components-input-bg-normal text-components-input-text-filled system-md-regular overflow-y-auto overflow-x-hidden'>{latestValueMap[currentVar.id] || ''}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,17 +6,25 @@ import {
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { produce, setAutoFreeze } from 'immer'
|
||||
import { uniqBy } from 'lodash-es'
|
||||
import { useWorkflowRun } from '../../hooks'
|
||||
import { NodeRunningStatus, WorkflowRunningStatus } from '../../types'
|
||||
import type {
|
||||
ChatItem,
|
||||
Inputs,
|
||||
PromptVariable,
|
||||
} from '@/app/components/base/chat/types'
|
||||
import type { InputForm } from '@/app/components/base/chat/chat/type'
|
||||
import {
|
||||
getProcessedInputs,
|
||||
processOpeningStatement,
|
||||
} from '@/app/components/base/chat/chat/utils'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import type { VisionFile } from '@/types/app'
|
||||
import { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel'
|
||||
import {
|
||||
getProcessedFiles,
|
||||
getProcessedFilesFromResponse,
|
||||
} from '@/app/components/base/file-uploader/utils'
|
||||
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||
|
||||
type GetAbortController = (abortController: AbortController) => void
|
||||
type SendCallback = {
|
||||
@@ -24,9 +32,9 @@ type SendCallback = {
|
||||
}
|
||||
export const useChat = (
|
||||
config: any,
|
||||
promptVariablesConfig?: {
|
||||
formSettings?: {
|
||||
inputs: Inputs
|
||||
promptVariables: PromptVariable[]
|
||||
inputsForm: InputForm[]
|
||||
},
|
||||
prevChatList?: ChatItem[],
|
||||
stopChat?: (taskId: string) => void,
|
||||
@@ -62,8 +70,8 @@ export const useChat = (
|
||||
}, [])
|
||||
|
||||
const getIntroduction = useCallback((str: string) => {
|
||||
return replaceStringWithValues(str, promptVariablesConfig?.promptVariables || [], promptVariablesConfig?.inputs || {})
|
||||
}, [promptVariablesConfig?.inputs, promptVariablesConfig?.promptVariables])
|
||||
return processOpeningStatement(str, formSettings?.inputs || {}, formSettings?.inputsForm || [])
|
||||
}, [formSettings?.inputs, formSettings?.inputsForm])
|
||||
useEffect(() => {
|
||||
if (config?.opening_statement) {
|
||||
handleUpdateChatList(produce(chatListRef.current, (draft) => {
|
||||
@@ -143,7 +151,11 @@ export const useChat = (
|
||||
}, [handleUpdateChatList])
|
||||
|
||||
const handleSend = useCallback((
|
||||
params: any,
|
||||
params: {
|
||||
query: string
|
||||
files?: FileEntity[]
|
||||
[key: string]: any
|
||||
},
|
||||
{
|
||||
onGetSuggestedQuestions,
|
||||
}: SendCallback,
|
||||
@@ -182,12 +194,14 @@ export const useChat = (
|
||||
|
||||
handleResponding(true)
|
||||
|
||||
const { files, inputs, ...restParams } = params
|
||||
const bodyParams = {
|
||||
conversation_id: conversationId.current,
|
||||
...params,
|
||||
files: getProcessedFiles(files || []),
|
||||
inputs: getProcessedInputs(inputs || {}, formSettings?.inputsForm || []),
|
||||
...restParams,
|
||||
}
|
||||
if (bodyParams?.files?.length) {
|
||||
bodyParams.files = bodyParams.files.map((item: VisionFile) => {
|
||||
bodyParams.files = bodyParams.files.map((item) => {
|
||||
if (item.transfer_method === TransferMethod.local_file) {
|
||||
return {
|
||||
...item,
|
||||
@@ -201,7 +215,7 @@ export const useChat = (
|
||||
let hasSetResponseId = false
|
||||
|
||||
handleRun(
|
||||
params,
|
||||
bodyParams,
|
||||
{
|
||||
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
|
||||
responseItem.content = responseItem.content + message
|
||||
@@ -260,6 +274,8 @@ export const useChat = (
|
||||
},
|
||||
onMessageEnd: (messageEnd) => {
|
||||
responseItem.citation = messageEnd.metadata?.retriever_resources || []
|
||||
const processedFilesFromResponse = getProcessedFilesFromResponse(messageEnd.files || [])
|
||||
responseItem.allFiles = uniqBy([...(responseItem.allFiles || []), ...(processedFilesFromResponse || [])], 'id')
|
||||
|
||||
const newListWithAnswer = produce(
|
||||
chatListRef.current.filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
|
||||
@@ -382,7 +398,7 @@ export const useChat = (
|
||||
},
|
||||
},
|
||||
)
|
||||
}, [handleRun, handleResponding, handleUpdateChatList, notify, t, updateCurrentQA, config.suggested_questions_after_answer?.enabled])
|
||||
}, [handleRun, handleResponding, handleUpdateChatList, notify, t, updateCurrentQA, config.suggested_questions_after_answer?.enabled, formSettings])
|
||||
|
||||
return {
|
||||
conversationId: conversationId.current,
|
||||
|
||||
@@ -54,11 +54,8 @@ const DebugAndPreview = () => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col w-[420px] rounded-l-2xl h-full border border-black/2',
|
||||
'flex flex-col w-[420px] bg-chatbot-bg rounded-l-2xl h-full border border-components-panel-border border-r-0 shadow-xl',
|
||||
)}
|
||||
style={{
|
||||
background: 'linear-gradient(156deg, rgba(242, 244, 247, 0.80) 0%, rgba(242, 244, 247, 0.00) 99.43%), var(--white, #FFF)',
|
||||
}}
|
||||
>
|
||||
<div className='shrink-0 flex items-center justify-between px-4 pt-3 pb-2 text-text-primary system-xl-semibold'>
|
||||
<div className='h-8'>{t('workflow.common.debugAndPreview').toLocaleUpperCase()}</div>
|
||||
@@ -88,6 +85,7 @@ const DebugAndPreview = () => {
|
||||
<RiEqualizer2Line className='w-4 h-4' />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
{expanded && <div className='absolute z-10 bottom-[-17px] right-[5px] w-3 h-3 bg-components-panel-on-panel-item-bg border-l-[0.5px] border-t-[0.5px] border-components-panel-border-subtle rotate-45'/>}
|
||||
</div>
|
||||
)}
|
||||
<div className='mx-3 w-[1px] h-3.5 bg-gray-200'></div>
|
||||
@@ -105,6 +103,7 @@ const DebugAndPreview = () => {
|
||||
showConversationVariableModal={showConversationVariableModal}
|
||||
onConversationModalHide={() => setShowConversationVariableModal(false)}
|
||||
showInputsFieldsPanel={expanded}
|
||||
onHide={() => setExpanded(false)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,11 @@ const UserInput = () => {
|
||||
const variables = startNode?.data.variables || []
|
||||
|
||||
const handleValueChange = (variable: string, v: string) => {
|
||||
workflowStore.getState().setInputs({
|
||||
const {
|
||||
inputs,
|
||||
setInputs,
|
||||
} = workflowStore.getState()
|
||||
setInputs({
|
||||
...inputs,
|
||||
[variable]: v,
|
||||
})
|
||||
@@ -29,7 +33,7 @@ const UserInput = () => {
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className={cn('relative bg-components-panel-on-panel-item-bg rounded-xl border-[0.5px] border-components-panel-border-subtle shadow-xs z-[1]')}>
|
||||
<div className={cn('sticky top-0 bg-components-panel-on-panel-item-bg rounded-xl border-[0.5px] border-components-panel-border-subtle shadow-xs z-[1]')}>
|
||||
<div className='px-4 pt-3 pb-4'>
|
||||
{variables.map((variable, index) => (
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user