mirror of https://github.com/langgenius/dify.git
chat workflow run
This commit is contained in:
parent
7655d7f662
commit
bd52937c88
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,41 @@
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,192 @@
|
|||
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
|
||||
|
|
|
|||
Loading…
Reference in New Issue