| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516 | 
							- import {
 -   useCallback,
 -   useEffect,
 -   useMemo,
 -   useRef,
 -   useState,
 - } from 'react'
 - import { useTranslation } from 'react-i18next'
 - import { produce, setAutoFreeze } from 'immer'
 - import { uniqBy } from 'lodash-es'
 - import {
 -   useSetWorkflowVarsWithValue,
 -   useWorkflowRun,
 - } from '../../hooks'
 - import { NodeRunningStatus, WorkflowRunningStatus } from '../../types'
 - import { useWorkflowStore } from '../../store'
 - import { DEFAULT_ITER_TIMES, DEFAULT_LOOP_TIMES } from '../../constants'
 - import type {
 -   ChatItem,
 -   ChatItemInTree,
 -   Inputs,
 - } 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 {
 -   getProcessedFiles,
 -   getProcessedFilesFromResponse,
 - } from '@/app/components/base/file-uploader/utils'
 - import type { FileEntity } from '@/app/components/base/file-uploader/types'
 - import { getThreadMessages } from '@/app/components/base/chat/utils'
 - import { useInvalidAllLastRun } from '@/service/use-workflow'
 - import { useParams } from 'next/navigation'
 - 
 - type GetAbortController = (abortController: AbortController) => void
 - type SendCallback = {
 -   onGetSuggestedQuestions?: (responseItemId: string, getAbortController: GetAbortController) => Promise<any>
 - }
 - export const useChat = (
 -   config: any,
 -   formSettings?: {
 -     inputs: Inputs
 -     inputsForm: InputForm[]
 -   },
 -   prevChatTree?: ChatItemInTree[],
 -   stopChat?: (taskId: string) => void,
 - ) => {
 -   const { t } = useTranslation()
 -   const { notify } = useToastContext()
 -   const { handleRun } = useWorkflowRun()
 -   const hasStopResponded = useRef(false)
 -   const workflowStore = useWorkflowStore()
 -   const conversationId = useRef('')
 -   const taskIdRef = useRef('')
 -   const [isResponding, setIsResponding] = useState(false)
 -   const isRespondingRef = useRef(false)
 -   const { appId } = useParams()
 -   const invalidAllLastRun = useInvalidAllLastRun(appId as string)
 -   const { fetchInspectVars } = useSetWorkflowVarsWithValue()
 -   const [suggestedQuestions, setSuggestQuestions] = useState<string[]>([])
 -   const suggestedQuestionsAbortControllerRef = useRef<AbortController | null>(null)
 -   const {
 -     setIterTimes,
 -     setLoopTimes,
 -   } = workflowStore.getState()
 - 
 -   const handleResponding = useCallback((isResponding: boolean) => {
 -     setIsResponding(isResponding)
 -     isRespondingRef.current = isResponding
 -   }, [])
 - 
 -   const [chatTree, setChatTree] = useState<ChatItemInTree[]>(prevChatTree || [])
 -   const chatTreeRef = useRef<ChatItemInTree[]>(chatTree)
 -   const [targetMessageId, setTargetMessageId] = useState<string>()
 -   const threadMessages = useMemo(() => getThreadMessages(chatTree, targetMessageId), [chatTree, targetMessageId])
 - 
 -   const getIntroduction = useCallback((str: string) => {
 -     return processOpeningStatement(str, formSettings?.inputs || {}, formSettings?.inputsForm || [])
 -   }, [formSettings?.inputs, formSettings?.inputsForm])
 - 
 -   /** Final chat list that will be rendered */
 -   const chatList = useMemo(() => {
 -     const ret = [...threadMessages]
 -     if (config?.opening_statement) {
 -       const index = threadMessages.findIndex(item => item.isOpeningStatement)
 - 
 -       if (index > -1) {
 -         ret[index] = {
 -           ...ret[index],
 -           content: getIntroduction(config.opening_statement),
 -           suggestedQuestions: config.suggested_questions?.map((item: string) => getIntroduction(item)),
 -         }
 -       }
 -       else {
 -         ret.unshift({
 -           id: `${Date.now()}`,
 -           content: getIntroduction(config.opening_statement),
 -           isAnswer: true,
 -           isOpeningStatement: true,
 -           suggestedQuestions: config.suggested_questions?.map((item: string) => getIntroduction(item)),
 -         })
 -       }
 -     }
 -     return ret
 -   }, [threadMessages, config?.opening_statement, getIntroduction, config?.suggested_questions])
 - 
 -   useEffect(() => {
 -     setAutoFreeze(false)
 -     return () => {
 -       setAutoFreeze(true)
 -     }
 -   }, [])
 - 
 -   /** Find the target node by bfs and then operate on it */
 -   const produceChatTreeNode = useCallback((targetId: string, operation: (node: ChatItemInTree) => void) => {
 -     return produce(chatTreeRef.current, (draft) => {
 -       const queue: ChatItemInTree[] = [...draft]
 -       while (queue.length > 0) {
 -         const current = queue.shift()!
 -         if (current.id === targetId) {
 -           operation(current)
 -           break
 -         }
 -         if (current.children)
 -           queue.push(...current.children)
 -       }
 -     })
 -   }, [])
 - 
 -   const handleStop = useCallback(() => {
 -     hasStopResponded.current = true
 -     handleResponding(false)
 -     if (stopChat && taskIdRef.current)
 -       stopChat(taskIdRef.current)
 -     setIterTimes(DEFAULT_ITER_TIMES)
 -     setLoopTimes(DEFAULT_LOOP_TIMES)
 -     if (suggestedQuestionsAbortControllerRef.current)
 -       suggestedQuestionsAbortControllerRef.current.abort()
 -   }, [handleResponding, setIterTimes, setLoopTimes, stopChat])
 - 
 -   const handleRestart = useCallback(() => {
 -     conversationId.current = ''
 -     taskIdRef.current = ''
 -     handleStop()
 -     setIterTimes(DEFAULT_ITER_TIMES)
 -     setLoopTimes(DEFAULT_LOOP_TIMES)
 -     setChatTree([])
 -     setSuggestQuestions([])
 -   }, [
 -     handleStop,
 -     setIterTimes,
 -     setLoopTimes,
 -   ])
 - 
 -   const updateCurrentQAOnTree = useCallback(({
 -     parentId,
 -     responseItem,
 -     placeholderQuestionId,
 -     questionItem,
 -   }: {
 -     parentId?: string
 -     responseItem: ChatItem
 -     placeholderQuestionId: string
 -     questionItem: ChatItem
 -   }) => {
 -     let nextState: ChatItemInTree[]
 -     const currentQA = { ...questionItem, children: [{ ...responseItem, children: [] }] }
 -     if (!parentId && !chatTree.some(item => [placeholderQuestionId, questionItem.id].includes(item.id))) {
 -       // QA whose parent is not provided is considered as a first message of the conversation,
 -       // and it should be a root node of the chat tree
 -       nextState = produce(chatTree, (draft) => {
 -         draft.push(currentQA)
 -       })
 -     }
 -     else {
 -       // find the target QA in the tree and update it; if not found, insert it to its parent node
 -       nextState = produceChatTreeNode(parentId!, (parentNode) => {
 -         const questionNodeIndex = parentNode.children!.findIndex(item => [placeholderQuestionId, questionItem.id].includes(item.id))
 -         if (questionNodeIndex === -1)
 -           parentNode.children!.push(currentQA)
 -         else
 -           parentNode.children![questionNodeIndex] = currentQA
 -       })
 -     }
 -     setChatTree(nextState)
 -     chatTreeRef.current = nextState
 -   }, [chatTree, produceChatTreeNode])
 - 
 -   const handleSend = useCallback((
 -     params: {
 -       query: string
 -       files?: FileEntity[]
 -       parent_message_id?: string
 -       [key: string]: any
 -     },
 -     {
 -       onGetSuggestedQuestions,
 -     }: SendCallback,
 -   ) => {
 -     if (isRespondingRef.current) {
 -       notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') })
 -       return false
 -     }
 - 
 -     const parentMessage = threadMessages.find(item => item.id === params.parent_message_id)
 - 
 -     const placeholderQuestionId = `question-${Date.now()}`
 -     const questionItem = {
 -       id: placeholderQuestionId,
 -       content: params.query,
 -       isAnswer: false,
 -       message_files: params.files,
 -       parentMessageId: params.parent_message_id,
 -     }
 - 
 -     const placeholderAnswerId = `answer-placeholder-${Date.now()}`
 -     const placeholderAnswerItem = {
 -       id: placeholderAnswerId,
 -       content: '',
 -       isAnswer: true,
 -       parentMessageId: questionItem.id,
 -       siblingIndex: parentMessage?.children?.length ?? chatTree.length,
 -     }
 - 
 -     setTargetMessageId(parentMessage?.id)
 -     updateCurrentQAOnTree({
 -       parentId: params.parent_message_id,
 -       responseItem: placeholderAnswerItem,
 -       placeholderQuestionId,
 -       questionItem,
 -     })
 - 
 -     // answer
 -     const responseItem: ChatItem = {
 -       id: placeholderAnswerId,
 -       content: '',
 -       agent_thoughts: [],
 -       message_files: [],
 -       isAnswer: true,
 -       parentMessageId: questionItem.id,
 -       siblingIndex: parentMessage?.children?.length ?? chatTree.length,
 -     }
 - 
 -     handleResponding(true)
 - 
 -     const { files, inputs, ...restParams } = params
 -     const bodyParams = {
 -       files: getProcessedFiles(files || []),
 -       inputs: getProcessedInputs(inputs || {}, formSettings?.inputsForm || []),
 -       ...restParams,
 -     }
 -     if (bodyParams?.files?.length) {
 -       bodyParams.files = bodyParams.files.map((item) => {
 -         if (item.transfer_method === TransferMethod.local_file) {
 -           return {
 -             ...item,
 -             url: '',
 -           }
 -         }
 -         return item
 -       })
 -     }
 - 
 -     let hasSetResponseId = false
 - 
 -     handleRun(
 -       bodyParams,
 -       {
 -         onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
 -           responseItem.content = responseItem.content + message
 - 
 -           if (messageId && !hasSetResponseId) {
 -             questionItem.id = `question-${messageId}`
 -             responseItem.id = messageId
 -             responseItem.parentMessageId = questionItem.id
 -             hasSetResponseId = true
 -           }
 - 
 -           if (isFirstMessage && newConversationId)
 -             conversationId.current = newConversationId
 - 
 -           taskIdRef.current = taskId
 -           if (messageId)
 -             responseItem.id = messageId
 - 
 -           updateCurrentQAOnTree({
 -             placeholderQuestionId,
 -             questionItem,
 -             responseItem,
 -             parentId: params.parent_message_id,
 -           })
 -         },
 -         async onCompleted(hasError?: boolean, errorMessage?: string) {
 -           handleResponding(false)
 -           fetchInspectVars()
 -           invalidAllLastRun()
 - 
 -           if (hasError) {
 -             if (errorMessage) {
 -               responseItem.content = errorMessage
 -               responseItem.isError = true
 -               updateCurrentQAOnTree({
 -                 placeholderQuestionId,
 -                 questionItem,
 -                 responseItem,
 -                 parentId: params.parent_message_id,
 -               })
 -             }
 -             return
 -           }
 - 
 -           if (config?.suggested_questions_after_answer?.enabled && !hasStopResponded.current && onGetSuggestedQuestions) {
 -             try {
 -               const { data }: any = await onGetSuggestedQuestions(
 -                 responseItem.id,
 -                 newAbortController => suggestedQuestionsAbortControllerRef.current = newAbortController,
 -               )
 -               setSuggestQuestions(data)
 -             }
 -             // eslint-disable-next-line unused-imports/no-unused-vars
 -             catch (error) {
 -               setSuggestQuestions([])
 -             }
 -           }
 -         },
 -         onMessageEnd: (messageEnd) => {
 -           responseItem.citation = messageEnd.metadata?.retriever_resources || []
 -           const processedFilesFromResponse = getProcessedFilesFromResponse(messageEnd.files || [])
 -           responseItem.allFiles = uniqBy([...(responseItem.allFiles || []), ...(processedFilesFromResponse || [])], 'id')
 - 
 -           updateCurrentQAOnTree({
 -             placeholderQuestionId,
 -             questionItem,
 -             responseItem,
 -             parentId: params.parent_message_id,
 -           })
 -         },
 -         onMessageReplace: (messageReplace) => {
 -           responseItem.content = messageReplace.answer
 -         },
 -         onError() {
 -           handleResponding(false)
 -         },
 -         onWorkflowStarted: ({ workflow_run_id, task_id }) => {
 -           taskIdRef.current = task_id
 -           responseItem.workflow_run_id = workflow_run_id
 -           responseItem.workflowProcess = {
 -             status: WorkflowRunningStatus.Running,
 -             tracing: [],
 -           }
 -           updateCurrentQAOnTree({
 -             placeholderQuestionId,
 -             questionItem,
 -             responseItem,
 -             parentId: params.parent_message_id,
 -           })
 -         },
 -         onWorkflowFinished: ({ data }) => {
 -           responseItem.workflowProcess!.status = data.status as WorkflowRunningStatus
 -           updateCurrentQAOnTree({
 -             placeholderQuestionId,
 -             questionItem,
 -             responseItem,
 -             parentId: params.parent_message_id,
 -           })
 -         },
 -         onIterationStart: ({ data }) => {
 -           responseItem.workflowProcess!.tracing!.push({
 -             ...data,
 -             status: NodeRunningStatus.Running,
 -           })
 -           updateCurrentQAOnTree({
 -             placeholderQuestionId,
 -             questionItem,
 -             responseItem,
 -             parentId: params.parent_message_id,
 -           })
 -         },
 -         onIterationFinish: ({ data }) => {
 -           const currentTracingIndex = responseItem.workflowProcess!.tracing!.findIndex(item => item.id === data.id)
 -           if (currentTracingIndex > -1) {
 -             responseItem.workflowProcess!.tracing[currentTracingIndex] = {
 -               ...responseItem.workflowProcess!.tracing[currentTracingIndex],
 -               ...data,
 -             }
 -             updateCurrentQAOnTree({
 -               placeholderQuestionId,
 -               questionItem,
 -               responseItem,
 -               parentId: params.parent_message_id,
 -             })
 -           }
 -         },
 -         onLoopStart: ({ data }) => {
 -           responseItem.workflowProcess!.tracing!.push({
 -             ...data,
 -             status: NodeRunningStatus.Running,
 -           })
 -           updateCurrentQAOnTree({
 -             placeholderQuestionId,
 -             questionItem,
 -             responseItem,
 -             parentId: params.parent_message_id,
 -           })
 -         },
 -         onLoopFinish: ({ data }) => {
 -           const currentTracingIndex = responseItem.workflowProcess!.tracing!.findIndex(item => item.id === data.id)
 -           if (currentTracingIndex > -1) {
 -             responseItem.workflowProcess!.tracing[currentTracingIndex] = {
 -               ...responseItem.workflowProcess!.tracing[currentTracingIndex],
 -               ...data,
 -             }
 -             updateCurrentQAOnTree({
 -               placeholderQuestionId,
 -               questionItem,
 -               responseItem,
 -               parentId: params.parent_message_id,
 -             })
 -           }
 -         },
 -         onNodeStarted: ({ data }) => {
 -           responseItem.workflowProcess!.tracing!.push({
 -             ...data,
 -             status: NodeRunningStatus.Running,
 -           } as any)
 -           updateCurrentQAOnTree({
 -             placeholderQuestionId,
 -             questionItem,
 -             responseItem,
 -             parentId: params.parent_message_id,
 -           })
 -         },
 -         onNodeRetry: ({ data }) => {
 -           responseItem.workflowProcess!.tracing!.push(data)
 - 
 -           updateCurrentQAOnTree({
 -             placeholderQuestionId,
 -             questionItem,
 -             responseItem,
 -             parentId: params.parent_message_id,
 -           })
 -         },
 -         onNodeFinished: ({ data }) => {
 -           const currentTracingIndex = responseItem.workflowProcess!.tracing!.findIndex(item => item.id === data.id)
 -           if (currentTracingIndex > -1) {
 -             responseItem.workflowProcess!.tracing[currentTracingIndex] = {
 -               ...responseItem.workflowProcess!.tracing[currentTracingIndex],
 -               ...data,
 -             }
 -             updateCurrentQAOnTree({
 -               placeholderQuestionId,
 -               questionItem,
 -               responseItem,
 -               parentId: params.parent_message_id,
 -             })
 -           }
 -         },
 -         onAgentLog: ({ data }) => {
 -           const currentNodeIndex = responseItem.workflowProcess!.tracing!.findIndex(item => item.node_id === data.node_id)
 -           if (currentNodeIndex > -1) {
 -             const current = responseItem.workflowProcess!.tracing![currentNodeIndex]
 - 
 -             if (current.execution_metadata) {
 -               if (current.execution_metadata.agent_log) {
 -                 const currentLogIndex = current.execution_metadata.agent_log.findIndex(log => log.id === data.id)
 -                 if (currentLogIndex > -1) {
 -                   current.execution_metadata.agent_log[currentLogIndex] = {
 -                     ...current.execution_metadata.agent_log[currentLogIndex],
 -                     ...data,
 -                   }
 -                 }
 -                 else {
 -                   current.execution_metadata.agent_log.push(data)
 -                 }
 -               }
 -               else {
 -                 current.execution_metadata.agent_log = [data]
 -               }
 -             }
 -             else {
 -               current.execution_metadata = {
 -                 agent_log: [data],
 -               } as any
 -             }
 - 
 -             responseItem.workflowProcess!.tracing[currentNodeIndex] = {
 -               ...current,
 -             }
 - 
 -             updateCurrentQAOnTree({
 -               placeholderQuestionId,
 -               questionItem,
 -               responseItem,
 -               parentId: params.parent_message_id,
 -             })
 -           }
 -         },
 -       },
 -     )
 -   }, [threadMessages, chatTree.length, updateCurrentQAOnTree, handleResponding, formSettings?.inputsForm, handleRun, notify, t, config?.suggested_questions_after_answer?.enabled, fetchInspectVars, invalidAllLastRun])
 - 
 -   return {
 -     conversationId: conversationId.current,
 -     chatList,
 -     setTargetMessageId,
 -     handleSend,
 -     handleStop,
 -     handleRestart,
 -     isResponding,
 -     suggestedQuestions,
 -   }
 - }
 
 
  |