Commit bd52937c authored by StyleZhang's avatar StyleZhang

chat workflow run

parent 7655d7f6
......@@ -56,7 +56,6 @@ const PreviewMode = memo(() => {
const { t } = useTranslation()
const { handleRunInit } = useWorkflow()
const runningStatus = useStore(s => s.runningStatus)
const isRunning = runningStatus === WorkflowRunningStatus.Running
const handleClick = () => {
handleRunInit()
......@@ -67,12 +66,12 @@ const PreviewMode = memo(() => {
className={`
flex items-center px-1.5 h-7 rounded-md text-[13px] font-medium text-primary-600
hover:bg-primary-50 cursor-pointer
${isRunning && 'bg-primary-50 opacity-50 !cursor-not-allowed'}
${runningStatus && 'bg-primary-50 opacity-50 !cursor-not-allowed'}
`}
onClick={() => !isRunning && handleClick()}
onClick={() => !runningStatus && handleClick()}
>
{
isRunning
runningStatus
? (
<>
{t('workflow.common.inPreview')}
......
......@@ -36,6 +36,7 @@ import { syncWorkflowDraft } from '@/service/workflow'
import { useFeaturesStore } from '@/app/components/base/features/hooks'
import { useStore as useAppStore } from '@/app/components/app/store'
import { ssePost } from '@/service/base'
import type { IOtherOptions } from '@/service/base'
export const useIsChatMode = () => {
const appDetail = useAppStore(s => s.appDetail)
......@@ -671,7 +672,7 @@ export const useWorkflow = () => {
export const useWorkflowRun = () => {
const store = useStoreApi()
return (params: any) => {
const run = useCallback((params: any, callback?: IOtherOptions) => {
const {
getNodes,
setNodes,
......@@ -721,7 +722,10 @@ export const useWorkflowRun = () => {
})
setNodes(newNodes)
},
...callback,
},
)
}
}, [store])
return run
}
import {
memo,
useCallback,
} from 'react'
import { useStore } from '../../store'
import UserInput from './user-input'
import { useChat } from './hooks'
import Chat from '@/app/components/base/chat/chat'
import { useChat } from '@/app/components/base/chat/chat/hooks'
import type { OnSend } from '@/app/components/base/chat/types'
const ChatWrapper = () => {
const {
conversationId,
chatList,
handleStop,
isResponding,
suggestedQuestions,
handleSend,
} = useChat()
const doSend = useCallback<OnSend>((query, files) => {
handleSend({
query,
files,
inputs: useStore.getState().inputs,
conversationId,
})
}, [conversationId, handleSend])
return (
<Chat
chatList={[]}
chatList={chatList}
isResponding={isResponding}
chatContainerclassName='px-4'
chatContainerInnerClassName='px-4'
chatFooterClassName='pb-4'
chatFooterInnerClassName='px-4'
onSend={() => {}}
chatContainerInnerClassName='pt-6'
chatFooterClassName='px-4'
chatFooterInnerClassName='pb-4'
onSend={doSend}
onStopResponding={handleStop}
chatNode={<UserInput />}
allToolIcons={{}}
......@@ -26,4 +44,4 @@ const ChatWrapper = () => {
)
}
export default ChatWrapper
export default memo(ChatWrapper)
import {
useCallback,
useEffect,
useRef,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import { produce, setAutoFreeze } from 'immer'
import { useWorkflowRun } from '../../hooks'
import type { ChatItem } from '@/app/components/base/chat/types'
import { useToastContext } from '@/app/components/base/toast'
import { TransferMethod } from '@/types/app'
import type { VisionFile } from '@/types/app'
export const useChat = (
prevChatList?: ChatItem[],
) => {
const { t } = useTranslation()
const { notify } = useToastContext()
const run = useWorkflowRun()
const hasStopResponded = useRef(false)
const connversationId = useRef('')
const taskIdRef = useRef('')
const [chatList, setChatList] = useState<ChatItem[]>(prevChatList || [])
const chatListRef = useRef<ChatItem[]>(prevChatList || [])
const [isResponding, setIsResponding] = useState(false)
const isRespondingRef = useRef(false)
const [suggestedQuestions, setSuggestQuestions] = useState<string[]>([])
const stopAbortControllerRef = useRef<AbortController | null>(null)
const suggestedQuestionsAbortControllerRef = useRef<AbortController | null>(null)
useEffect(() => {
setAutoFreeze(false)
return () => {
setAutoFreeze(true)
}
}, [])
const handleUpdateChatList = useCallback((newChatList: ChatItem[]) => {
setChatList(newChatList)
chatListRef.current = newChatList
}, [])
const handleResponding = useCallback((isResponding: boolean) => {
setIsResponding(isResponding)
isRespondingRef.current = isResponding
}, [])
const handleStop = useCallback(() => {
hasStopResponded.current = true
handleResponding(false)
}, [handleResponding])
const updateCurrentQA = useCallback(({
responseItem,
questionId,
placeholderAnswerId,
questionItem,
}: {
responseItem: ChatItem
questionId: string
placeholderAnswerId: string
questionItem: ChatItem
}) => {
const newListWithAnswer = produce(
chatListRef.current.filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
(draft) => {
if (!draft.find(item => item.id === questionId))
draft.push({ ...questionItem })
draft.push({ ...responseItem })
})
handleUpdateChatList(newListWithAnswer)
}, [handleUpdateChatList])
const handleSend = useCallback((params: any) => {
if (isRespondingRef.current) {
notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') })
return false
}
const questionId = `question-${Date.now()}`
const questionItem = {
id: questionId,
content: params.query,
isAnswer: false,
message_files: params.files,
}
const placeholderAnswerId = `answer-placeholder-${Date.now()}`
const placeholderAnswerItem = {
id: placeholderAnswerId,
content: '',
isAnswer: true,
}
const newList = [...chatListRef.current, questionItem, placeholderAnswerItem]
handleUpdateChatList(newList)
// answer
const responseItem: ChatItem = {
id: `${Date.now()}`,
content: '',
agent_thoughts: [],
message_files: [],
isAnswer: true,
}
handleResponding(true)
const bodyParams = {
conversation_id: connversationId.current,
...params,
}
if (bodyParams?.files?.length) {
bodyParams.files = bodyParams.files.map((item: VisionFile) => {
if (item.transfer_method === TransferMethod.local_file) {
return {
...item,
url: '',
}
}
return item
})
}
let hasSetResponseId = false
run(
params,
{
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
responseItem.content = responseItem.content + message
if (messageId && !hasSetResponseId) {
responseItem.id = messageId
hasSetResponseId = true
}
if (isFirstMessage && newConversationId)
connversationId.current = newConversationId
taskIdRef.current = taskId
if (messageId)
responseItem.id = messageId
updateCurrentQA({
responseItem,
questionId,
placeholderAnswerId,
questionItem,
})
},
async onCompleted(hasError?: boolean) {
handleResponding(false)
},
onMessageEnd: (messageEnd) => {
responseItem.citation = messageEnd.metadata?.retriever_resources || []
const newListWithAnswer = produce(
chatListRef.current.filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
(draft) => {
if (!draft.find(item => item.id === questionId))
draft.push({ ...questionItem })
draft.push({ ...responseItem })
})
handleUpdateChatList(newListWithAnswer)
},
onMessageReplace: (messageReplace) => {
responseItem.content = messageReplace.answer
},
onError() {
handleResponding(false)
const newChatList = produce(chatListRef.current, (draft) => {
draft.splice(draft.findIndex(item => item.id === placeholderAnswerId), 1)
})
handleUpdateChatList(newChatList)
},
},
)
}, [run, handleResponding, handleUpdateChatList, notify, t, updateCurrentQA])
return {
conversationId: connversationId.current,
chatList,
handleSend,
handleStop,
isResponding,
suggestedQuestions,
}
}
......@@ -3,11 +3,27 @@ import {
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import { useNodes } from 'reactflow'
import FormItem from '../../nodes/_base/components/before-run-form/form-item'
import { BlockEnum } from '../../types'
import { useStore } from '../../store'
import type { StartNodeType } from '../../nodes/start/types'
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
const UserInput = () => {
const { t } = useTranslation()
const [expanded, setExpanded] = useState(true)
const inputs = useStore(s => s.inputs)
const nodes = useNodes<StartNodeType>()
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
const variables = startNode?.data.variables || []
const handleValueChange = (variable: string, v: string) => {
useStore.getState().setInputs({
...inputs,
[variable]: v,
})
}
return (
<div
......@@ -32,10 +48,20 @@ const UserInput = () => {
{
expanded && (
<div className='py-2 text-[13px] text-gray-900'>
<div className='flex px-4 py-1'>
<div className='shrink-0 mr-4 leading-8'>Service Name</div>
<input className='grow px-3 h-8 appearance-none outline-none rounded-lg bg-gray-100' />
</div>
{
variables.map(variable => (
<div
key={variable.variable}
className='mb-2 last-of-type:mb-0'
>
<FormItem
payload={variable}
value={inputs[variable.variable]}
onChange={v => handleValueChange(variable.variable, v)}
/>
</div>
))
}
</div>
)
}
......
......@@ -54,7 +54,7 @@ export type IOnNodeFinished = (nodeFinished: NodeFinishedResponse) => void
export type IOnTextChunk = (textChunk: TextChunkResponse) => void
export type IOnTextReplace = (textReplace: TextReplaceResponse) => void
type IOtherOptions = {
export type IOtherOptions = {
isPublicAPI?: boolean
bodyStringify?: boolean
needAllResponseContent?: boolean
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment