mirror of https://github.com/langgenius/dify.git
feat: enhance chat functionality with workflow resumption and support regeneration (#31281)
This commit is contained in:
parent
1014852ebd
commit
f3ec6ad53c
|
|
@ -5,7 +5,7 @@ import type {
|
|||
ChatItemInTree,
|
||||
OnSend,
|
||||
} from '../types'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import AnswerIcon from '@/app/components/base/answer-icon'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import InputsForm from '@/app/components/base/chat/chat-with-history/inputs-form'
|
||||
|
|
@ -70,10 +70,10 @@ const ChatWrapper = () => {
|
|||
}, [appParams, currentConversationItem?.introduction])
|
||||
const {
|
||||
chatList,
|
||||
setTargetMessageId,
|
||||
handleSend,
|
||||
handleStop,
|
||||
handleResume,
|
||||
handleSwitchSibling,
|
||||
isResponding: respondingState,
|
||||
suggestedQuestions,
|
||||
} = useChat(
|
||||
|
|
@ -136,33 +136,38 @@ const ChatWrapper = () => {
|
|||
}, [respondingState, setIsResponding])
|
||||
|
||||
// Resume paused workflows when chat history is loaded
|
||||
const resumedWorkflowsRef = useRef<Set<string>>(new Set())
|
||||
useEffect(() => {
|
||||
if (!appPrevChatTree || appPrevChatTree.length === 0)
|
||||
return
|
||||
|
||||
// Find all answer items with workflow_run_id that need resumption
|
||||
const checkForPausedWorkflows = (nodes: ChatItemInTree[]) => {
|
||||
// Find the last answer item with workflow_run_id that needs resumption (DFS - find deepest first)
|
||||
let lastPausedNode: ChatItemInTree | undefined
|
||||
const findLastPausedWorkflow = (nodes: ChatItemInTree[]) => {
|
||||
nodes.forEach((node) => {
|
||||
if (node.isAnswer && node.workflow_run_id && node.humanInputFormDataList && node.humanInputFormDataList.length > 0) {
|
||||
// This is a paused workflow waiting for human input
|
||||
const workflowKey = `${node.workflow_run_id}-${node.id}`
|
||||
if (!resumedWorkflowsRef.current.has(workflowKey)) {
|
||||
resumedWorkflowsRef.current.add(workflowKey)
|
||||
// Re-subscribe to workflow events
|
||||
handleResume(
|
||||
node.id,
|
||||
node.workflow_run_id,
|
||||
!isInstalledApp,
|
||||
)
|
||||
}
|
||||
}
|
||||
// DFS: recurse to children first
|
||||
if (node.children && node.children.length > 0)
|
||||
checkForPausedWorkflows(node.children)
|
||||
findLastPausedWorkflow(node.children)
|
||||
|
||||
// Track the last node with humanInputFormDataList
|
||||
if (node.isAnswer && node.workflow_run_id && node.humanInputFormDataList && node.humanInputFormDataList.length > 0)
|
||||
lastPausedNode = node
|
||||
})
|
||||
}
|
||||
|
||||
checkForPausedWorkflows(appPrevChatTree)
|
||||
findLastPausedWorkflow(appPrevChatTree)
|
||||
|
||||
// Only resume the last paused workflow
|
||||
if (lastPausedNode) {
|
||||
handleResume(
|
||||
lastPausedNode.id,
|
||||
lastPausedNode.workflow_run_id!,
|
||||
{
|
||||
onGetSuggestedQuestions: responseItemId => fetchSuggestedQuestions(responseItemId, isInstalledApp, appId),
|
||||
onConversationComplete: currentConversationId ? undefined : handleNewConversationCompleted,
|
||||
isPublicAPI: !isInstalledApp,
|
||||
},
|
||||
)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const doSend: OnSend = useCallback((message, files, isRegenerate = false, parentAnswer: ChatItem | null = null) => {
|
||||
|
|
@ -191,6 +196,14 @@ const ChatWrapper = () => {
|
|||
doSend(editedQuestion ? editedQuestion.message : question.content, editedQuestion ? editedQuestion.files : question.message_files, true, isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null)
|
||||
}, [chatList, doSend])
|
||||
|
||||
const doSwitchSibling = useCallback((siblingMessageId: string) => {
|
||||
handleSwitchSibling(siblingMessageId, {
|
||||
onGetSuggestedQuestions: responseItemId => fetchSuggestedQuestions(responseItemId, isInstalledApp, appId),
|
||||
onConversationComplete: currentConversationId ? undefined : handleNewConversationCompleted,
|
||||
isPublicAPI: !isInstalledApp,
|
||||
})
|
||||
}, [handleSwitchSibling, isInstalledApp, appId, currentConversationId, handleNewConversationCompleted])
|
||||
|
||||
const messageList = useMemo(() => {
|
||||
if (currentConversationId || chatList.length > 1)
|
||||
return chatList
|
||||
|
|
@ -325,7 +338,7 @@ const ChatWrapper = () => {
|
|||
answerIcon={answerIcon}
|
||||
hideProcessDetail
|
||||
themeBuilder={themeBuilder}
|
||||
switchSibling={siblingMessageId => setTargetMessageId(siblingMessageId)}
|
||||
switchSibling={doSwitchSibling}
|
||||
inputDisabled={inputDisabled}
|
||||
sidebarCollapseState={sidebarCollapseState}
|
||||
questionIcon={
|
||||
|
|
|
|||
|
|
@ -76,8 +76,10 @@ const Answer: FC<AnswerProps> = ({
|
|||
|
||||
const [containerWidth, setContainerWidth] = useState(0)
|
||||
const [contentWidth, setContentWidth] = useState(0)
|
||||
const [humanInputFormContainerWidth, setHumanInputFormContainerWidth] = useState(0)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const contentRef = useRef<HTMLDivElement>(null)
|
||||
const humanInputFormContainerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const {
|
||||
getHumanInputNodeData,
|
||||
|
|
@ -101,12 +103,23 @@ const Answer: FC<AnswerProps> = ({
|
|||
getContentWidth()
|
||||
}, [responding])
|
||||
|
||||
const getHumanInputFormContainerWidth = () => {
|
||||
if (humanInputFormContainerRef.current)
|
||||
setHumanInputFormContainerWidth(humanInputFormContainerRef.current?.clientWidth)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (hasHumanInputs)
|
||||
getHumanInputFormContainerWidth()
|
||||
}, [hasHumanInputs])
|
||||
|
||||
// Recalculate contentWidth when content changes (e.g., SVG preview/source toggle)
|
||||
useEffect(() => {
|
||||
if (!containerRef.current)
|
||||
return
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
getContentWidth()
|
||||
getHumanInputFormContainerWidth()
|
||||
})
|
||||
resizeObserver.observe(containerRef.current)
|
||||
return () => {
|
||||
|
|
@ -144,8 +157,23 @@ const Answer: FC<AnswerProps> = ({
|
|||
{hasHumanInputs && (
|
||||
<div className={cn('group relative pr-10', chatAnswerContainerInner)}>
|
||||
<div
|
||||
ref={humanInputFormContainerRef}
|
||||
className={cn('body-lg-regular relative inline-block max-w-full rounded-2xl bg-chat-bubble-bg px-4 py-3 text-text-primary', (workflowProcess || hasHumanInputs) && 'w-full')}
|
||||
>
|
||||
{
|
||||
!responding && contentIsEmpty && !hasAgentThoughts && (
|
||||
<Operation
|
||||
hasWorkflowProcess={!!workflowProcess}
|
||||
maxSize={containerWidth - humanInputFormContainerWidth - 4}
|
||||
contentWidth={humanInputFormContainerWidth}
|
||||
item={item}
|
||||
question={question}
|
||||
index={index}
|
||||
showPromptLog={showPromptLog}
|
||||
noChatInput={noChatInput}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{/** Render workflow process */}
|
||||
{
|
||||
workflowProcess && (
|
||||
|
|
@ -173,6 +201,23 @@ const Answer: FC<AnswerProps> = ({
|
|||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
item.siblingCount
|
||||
&& item.siblingCount > 1
|
||||
&& item.siblingIndex !== undefined
|
||||
&& !responding
|
||||
&& contentIsEmpty
|
||||
&& !hasAgentThoughts
|
||||
&& (
|
||||
<ContentSwitch
|
||||
count={item.siblingCount}
|
||||
currentIndex={item.siblingIndex}
|
||||
prevDisabled={!item.prevSibling}
|
||||
nextDisabled={!item.nextSibling}
|
||||
switchSibling={handleSwitchSibling}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ const Operation: FC<OperationProps> = ({
|
|||
)}
|
||||
style={(!hasWorkflowProcess && positionRight) ? { left: contentWidth + 8 } : {}}
|
||||
>
|
||||
{shouldShowUserFeedbackBar && (
|
||||
{shouldShowUserFeedbackBar && !humanInputFormDataList?.length && (
|
||||
<div className={cn(
|
||||
'ml-1 items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm',
|
||||
hasUserFeedback ? 'flex' : 'hidden group-hover:flex',
|
||||
|
|
@ -227,7 +227,7 @@ const Operation: FC<OperationProps> = ({
|
|||
)}
|
||||
</div>
|
||||
)}
|
||||
{shouldShowAdminFeedbackBar && (
|
||||
{shouldShowAdminFeedbackBar && !humanInputFormDataList?.length && (
|
||||
<div className={cn(
|
||||
'ml-1 items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm',
|
||||
(hasAdminFeedback || hasUserFeedback) ? 'flex' : 'hidden group-hover:flex',
|
||||
|
|
@ -304,28 +304,30 @@ const Operation: FC<OperationProps> = ({
|
|||
<Log logItem={item} />
|
||||
</div>
|
||||
)}
|
||||
{!isOpeningStatement && !humanInputFormDataList?.length && (
|
||||
{!isOpeningStatement && (
|
||||
<div className="ml-1 hidden items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm group-hover:flex">
|
||||
{(config?.text_to_speech?.enabled) && (
|
||||
{(config?.text_to_speech?.enabled && !humanInputFormDataList?.length) && (
|
||||
<NewAudioButton
|
||||
id={id}
|
||||
value={content}
|
||||
voice={config?.text_to_speech?.voice}
|
||||
/>
|
||||
)}
|
||||
<ActionButton onClick={() => {
|
||||
copy(content)
|
||||
Toast.notify({ type: 'success', message: t('actionMsg.copySuccessfully', { ns: 'common' }) })
|
||||
}}
|
||||
>
|
||||
<RiClipboardLine className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
{!humanInputFormDataList?.length && (
|
||||
<ActionButton onClick={() => {
|
||||
copy(content)
|
||||
Toast.notify({ type: 'success', message: t('actionMsg.copySuccessfully', { ns: 'common' }) })
|
||||
}}
|
||||
>
|
||||
<RiClipboardLine className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
)}
|
||||
{!noChatInput && (
|
||||
<ActionButton onClick={() => onRegenerate?.(item)}>
|
||||
<RiResetLeftLine className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
)}
|
||||
{(config?.supportAnnotation && config.annotation_reply?.enabled) && (
|
||||
{config?.supportAnnotation && config.annotation_reply?.enabled && !humanInputFormDataList?.length && (
|
||||
<AnnotationCtrlButton
|
||||
appId={config?.appId || ''}
|
||||
messageId={id}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import type AudioPlayer from '@/app/components/base/audio-btn/audio'
|
|||
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||
import type { Annotation } from '@/models/log'
|
||||
import type {
|
||||
IOnDataMoreInfo,
|
||||
IOtherOptions,
|
||||
} from '@/service/base'
|
||||
import { uniqBy } from 'es-toolkit/compat'
|
||||
|
|
@ -209,10 +210,14 @@ export const useChat = (
|
|||
return getOrCreatePlayer
|
||||
}, [params.token, params.appId, pathname])
|
||||
|
||||
const handleResume = useCallback((
|
||||
const handleResume = useCallback(async (
|
||||
messageId: string,
|
||||
workflowRunId: string,
|
||||
isPublicAPI?: boolean,
|
||||
{
|
||||
onGetSuggestedQuestions,
|
||||
onConversationComplete,
|
||||
isPublicAPI,
|
||||
}: SendCallback,
|
||||
) => {
|
||||
const getOrCreatePlayer = createAudioPlayerManager()
|
||||
// Re-subscribe to workflow events for the specific message
|
||||
|
|
@ -223,7 +228,7 @@ export const useChat = (
|
|||
getAbortController: (abortController) => {
|
||||
workflowEventsAbortControllerRef.current = abortController
|
||||
},
|
||||
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, taskId }: any) => {
|
||||
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: IOnDataMoreInfo) => {
|
||||
updateChatTreeNode(messageId, (responseItem) => {
|
||||
const isAgentMode = responseItem.agent_thoughts && responseItem.agent_thoughts.length > 0
|
||||
if (!isAgentMode) {
|
||||
|
|
@ -234,6 +239,8 @@ export const useChat = (
|
|||
if (lastThought)
|
||||
lastThought.thought = lastThought.thought + message
|
||||
}
|
||||
if (messageId)
|
||||
responseItem.id = messageId
|
||||
})
|
||||
|
||||
if (isFirstMessage && newConversationId)
|
||||
|
|
@ -242,8 +249,28 @@ export const useChat = (
|
|||
if (taskId)
|
||||
taskIdRef.current = taskId
|
||||
},
|
||||
async onCompleted() {
|
||||
async onCompleted(hasError?: boolean) {
|
||||
handleResponding(false)
|
||||
|
||||
if (hasError)
|
||||
return
|
||||
|
||||
if (onConversationComplete)
|
||||
onConversationComplete(conversationId.current)
|
||||
|
||||
if (config?.suggested_questions_after_answer?.enabled && !hasStopResponded.current && onGetSuggestedQuestions) {
|
||||
try {
|
||||
const { data }: any = await onGetSuggestedQuestions(
|
||||
messageId,
|
||||
newAbortController => suggestedQuestionsAbortControllerRef.current = newAbortController,
|
||||
)
|
||||
setSuggestQuestions(data)
|
||||
}
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||
catch (e) {
|
||||
setSuggestQuestions([])
|
||||
}
|
||||
}
|
||||
},
|
||||
onFile(file) {
|
||||
updateChatTreeNode(messageId, (responseItem) => {
|
||||
|
|
@ -300,20 +327,12 @@ export const useChat = (
|
|||
onError() {
|
||||
handleResponding(false)
|
||||
},
|
||||
onWorkflowStarted: ({ workflow_run_id, task_id, data: { is_resumption } }) => {
|
||||
onWorkflowStarted: ({ workflow_run_id, task_id }) => {
|
||||
handleResponding(true)
|
||||
hasStopResponded.current = false
|
||||
updateChatTreeNode(messageId, (responseItem) => {
|
||||
if (is_resumption) {
|
||||
if (responseItem.workflowProcess) {
|
||||
responseItem.workflowProcess.status = WorkflowRunningStatus.Running
|
||||
}
|
||||
else {
|
||||
responseItem.workflowProcess = {
|
||||
status: WorkflowRunningStatus.Running,
|
||||
tracing: [],
|
||||
}
|
||||
}
|
||||
if (responseItem.workflowProcess && responseItem.workflowProcess.tracing.length > 0) {
|
||||
responseItem.workflowProcess.status = WorkflowRunningStatus.Running
|
||||
}
|
||||
else {
|
||||
taskIdRef.current = task_id
|
||||
|
|
@ -366,20 +385,12 @@ export const useChat = (
|
|||
if (!responseItem.workflowProcess.tracing)
|
||||
responseItem.workflowProcess.tracing = []
|
||||
|
||||
const { is_resumption } = nodeStartedData
|
||||
if (is_resumption) {
|
||||
const currentIndex = responseItem.workflowProcess.tracing.findIndex(item => item.node_id === nodeStartedData.node_id)
|
||||
if (currentIndex > -1) {
|
||||
responseItem.workflowProcess.tracing[currentIndex] = {
|
||||
...nodeStartedData,
|
||||
status: NodeRunningStatus.Running,
|
||||
}
|
||||
}
|
||||
else {
|
||||
responseItem.workflowProcess.tracing.push({
|
||||
...nodeStartedData,
|
||||
status: NodeRunningStatus.Running,
|
||||
})
|
||||
const currentIndex = responseItem.workflowProcess.tracing.findIndex(item => item.node_id === nodeStartedData.node_id)
|
||||
// if the node is already started, update the node
|
||||
if (currentIndex > -1) {
|
||||
responseItem.workflowProcess.tracing[currentIndex] = {
|
||||
...nodeStartedData,
|
||||
status: NodeRunningStatus.Running,
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
@ -502,12 +513,15 @@ export const useChat = (
|
|||
},
|
||||
}
|
||||
|
||||
if (workflowEventsAbortControllerRef.current)
|
||||
workflowEventsAbortControllerRef.current.abort()
|
||||
|
||||
sseGet(
|
||||
url,
|
||||
{},
|
||||
otherOptions,
|
||||
)
|
||||
}, [updateChatTreeNode, handleResponding, createAudioPlayerManager])
|
||||
}, [updateChatTreeNode, handleResponding, createAudioPlayerManager, config?.suggested_questions_after_answer])
|
||||
|
||||
const updateCurrentQAOnTree = useCallback(({
|
||||
parentId,
|
||||
|
|
@ -810,9 +824,20 @@ export const useChat = (
|
|||
parentId: data.parent_message_id,
|
||||
})
|
||||
},
|
||||
onWorkflowStarted: ({ workflow_run_id, task_id, data: { is_resumption } }) => {
|
||||
if (is_resumption) {
|
||||
responseItem.workflowProcess!.status = WorkflowRunningStatus.Running
|
||||
onWorkflowStarted: ({ workflow_run_id, task_id, conversation_id, message_id }) => {
|
||||
// If there are no streaming messages, we still need to set the conversation_id to avoid create a new conversation when regeneration in chat-flow.
|
||||
if (conversation_id) {
|
||||
conversationId.current = conversation_id
|
||||
}
|
||||
if (message_id && !hasSetResponseId) {
|
||||
questionItem.id = `question-${message_id}`
|
||||
responseItem.id = message_id
|
||||
responseItem.parentMessageId = questionItem.id
|
||||
hasSetResponseId = true
|
||||
}
|
||||
|
||||
if (responseItem.workflowProcess && responseItem.workflowProcess.tracing.length > 0) {
|
||||
responseItem.workflowProcess.status = WorkflowRunningStatus.Running
|
||||
}
|
||||
else {
|
||||
taskIdRef.current = task_id
|
||||
|
|
@ -868,14 +893,16 @@ export const useChat = (
|
|||
})
|
||||
},
|
||||
onNodeStarted: ({ data: nodeStartedData }) => {
|
||||
const { is_resumption } = nodeStartedData
|
||||
if (is_resumption) {
|
||||
const currentIndex = responseItem.workflowProcess!.tracing!.findIndex(item => item.node_id === data.node_id)
|
||||
if (currentIndex > -1) {
|
||||
responseItem.workflowProcess!.tracing![currentIndex] = {
|
||||
...nodeStartedData,
|
||||
status: NodeRunningStatus.Running,
|
||||
}
|
||||
if (!responseItem.workflowProcess)
|
||||
return
|
||||
if (!responseItem.workflowProcess.tracing)
|
||||
responseItem.workflowProcess.tracing = []
|
||||
|
||||
const currentIndex = responseItem.workflowProcess.tracing.findIndex(item => item.node_id === nodeStartedData.node_id)
|
||||
if (currentIndex > -1) {
|
||||
responseItem.workflowProcess.tracing[currentIndex] = {
|
||||
...nodeStartedData,
|
||||
status: NodeRunningStatus.Running,
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
@ -885,7 +912,7 @@ export const useChat = (
|
|||
if (data.loop_id)
|
||||
return
|
||||
|
||||
responseItem.workflowProcess!.tracing!.push({
|
||||
responseItem.workflowProcess.tracing.push({
|
||||
...nodeStartedData,
|
||||
status: WorkflowRunningStatus.Running,
|
||||
})
|
||||
|
|
@ -1021,6 +1048,10 @@ export const useChat = (
|
|||
},
|
||||
}
|
||||
|
||||
// Abort the previous workflow events SSE request
|
||||
if (workflowEventsAbortControllerRef.current)
|
||||
workflowEventsAbortControllerRef.current.abort()
|
||||
|
||||
ssePost(
|
||||
url,
|
||||
{
|
||||
|
|
@ -1096,6 +1127,36 @@ export const useChat = (
|
|||
})
|
||||
}, [chatList, updateChatTreeNode])
|
||||
|
||||
const handleSwitchSibling = useCallback((
|
||||
siblingMessageId: string,
|
||||
callbacks: SendCallback,
|
||||
) => {
|
||||
setTargetMessageId(siblingMessageId)
|
||||
|
||||
// Helper to find message in tree
|
||||
const findMessageInTree = (nodes: ChatItemInTree[], targetId: string): ChatItemInTree | undefined => {
|
||||
for (const node of nodes) {
|
||||
if (node.id === targetId)
|
||||
return node
|
||||
if (node.children) {
|
||||
const found = findMessageInTree(node.children, targetId)
|
||||
if (found)
|
||||
return found
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
const targetMessage = findMessageInTree(chatTreeRef.current, siblingMessageId)
|
||||
if (targetMessage?.workflow_run_id && targetMessage.humanInputFormDataList && targetMessage.humanInputFormDataList.length > 0) {
|
||||
handleResume(
|
||||
targetMessage.id,
|
||||
targetMessage.workflow_run_id,
|
||||
callbacks,
|
||||
)
|
||||
}
|
||||
}, [setTargetMessageId, handleResume])
|
||||
|
||||
useEffect(() => {
|
||||
if (clearChatList)
|
||||
handleRestart(() => clearChatListCallback?.(false))
|
||||
|
|
@ -1108,6 +1169,7 @@ export const useChat = (
|
|||
setIsResponding,
|
||||
handleSend,
|
||||
handleResume,
|
||||
handleSwitchSibling,
|
||||
suggestedQuestions,
|
||||
handleRestart,
|
||||
handleStop,
|
||||
|
|
|
|||
|
|
@ -68,9 +68,9 @@ const ChatWrapper = () => {
|
|||
}, [appParams, currentConversationItem?.introduction])
|
||||
const {
|
||||
chatList,
|
||||
setTargetMessageId,
|
||||
handleSend,
|
||||
handleStop,
|
||||
handleSwitchSibling,
|
||||
isResponding: respondingState,
|
||||
suggestedQuestions,
|
||||
} = useChat(
|
||||
|
|
@ -154,6 +154,12 @@ const ChatWrapper = () => {
|
|||
doSend(editedQuestion ? editedQuestion.message : question.content, editedQuestion ? editedQuestion.files : question.message_files, true, isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null)
|
||||
}, [chatList, doSend])
|
||||
|
||||
const doSwitchSibling = useCallback((siblingMessageId: string) => {
|
||||
handleSwitchSibling(siblingMessageId, {
|
||||
onGetSuggestedQuestions: responseItemId => fetchSuggestedQuestions(responseItemId, isInstalledApp, appId),
|
||||
})
|
||||
}, [handleSwitchSibling, isInstalledApp, appId])
|
||||
|
||||
const messageList = useMemo(() => {
|
||||
if (currentConversationId || chatList.length > 1)
|
||||
return chatList
|
||||
|
|
@ -268,7 +274,7 @@ const ChatWrapper = () => {
|
|||
answerIcon={answerIcon}
|
||||
hideProcessDetail
|
||||
themeBuilder={themeBuilder}
|
||||
switchSibling={siblingMessageId => setTargetMessageId(siblingMessageId)}
|
||||
switchSibling={doSwitchSibling}
|
||||
inputDisabled={inputDisabled}
|
||||
questionIcon={
|
||||
initUserVariables?.avatar_url
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { IOtherOptions } from '@/service/base'
|
||||
import type { VersionHistory } from '@/types/workflow'
|
||||
import { produce } from 'immer'
|
||||
import { useCallback } from 'react'
|
||||
import { useCallback, useRef } from 'react'
|
||||
import {
|
||||
useReactFlow,
|
||||
useStoreApi,
|
||||
|
|
@ -42,6 +42,8 @@ export const usePipelineRun = () => {
|
|||
handleWorkflowTextReplace,
|
||||
} = useWorkflowRunEvent()
|
||||
|
||||
const abortControllerRef = useRef<AbortController | null>(null)
|
||||
|
||||
const handleBackupDraft = useCallback(() => {
|
||||
const {
|
||||
getNodes,
|
||||
|
|
@ -154,12 +156,18 @@ export const usePipelineRun = () => {
|
|||
resultText: '',
|
||||
})
|
||||
|
||||
abortControllerRef.current?.abort()
|
||||
abortControllerRef.current = null
|
||||
|
||||
ssePost(
|
||||
url,
|
||||
{
|
||||
body: params,
|
||||
},
|
||||
{
|
||||
getAbortController: (controller: AbortController) => {
|
||||
abortControllerRef.current = controller
|
||||
},
|
||||
onWorkflowStarted: (params) => {
|
||||
handleWorkflowStarted(params)
|
||||
|
||||
|
|
@ -267,31 +275,17 @@ export const usePipelineRun = () => {
|
|||
...restCallback,
|
||||
},
|
||||
)
|
||||
}, [
|
||||
store,
|
||||
workflowStore,
|
||||
doSyncWorkflowDraft,
|
||||
handleWorkflowStarted,
|
||||
handleWorkflowFinished,
|
||||
handleWorkflowFailed,
|
||||
handleWorkflowNodeStarted,
|
||||
handleWorkflowNodeFinished,
|
||||
handleWorkflowNodeIterationStarted,
|
||||
handleWorkflowNodeIterationNext,
|
||||
handleWorkflowNodeIterationFinished,
|
||||
handleWorkflowNodeLoopStarted,
|
||||
handleWorkflowNodeLoopNext,
|
||||
handleWorkflowNodeLoopFinished,
|
||||
handleWorkflowNodeRetry,
|
||||
handleWorkflowTextChunk,
|
||||
handleWorkflowTextReplace,
|
||||
handleWorkflowAgentLog,
|
||||
])
|
||||
}, [store, doSyncWorkflowDraft, workflowStore, handleWorkflowStarted, handleWorkflowFinished, fetchInspectVars, invalidAllLastRun, handleWorkflowFailed, handleWorkflowNodeStarted, handleWorkflowNodeFinished, handleWorkflowNodeIterationStarted, handleWorkflowNodeIterationNext, handleWorkflowNodeIterationFinished, handleWorkflowNodeLoopStarted, handleWorkflowNodeLoopNext, handleWorkflowNodeLoopFinished, handleWorkflowNodeRetry, handleWorkflowAgentLog, handleWorkflowTextChunk, handleWorkflowTextReplace])
|
||||
|
||||
const handleStopRun = useCallback((taskId: string) => {
|
||||
const { pipelineId } = workflowStore.getState()
|
||||
|
||||
stopWorkflowRun(`/rag/pipelines/${pipelineId}/workflow-runs/tasks/${taskId}/stop`)
|
||||
|
||||
if (abortControllerRef.current)
|
||||
abortControllerRef.current.abort()
|
||||
|
||||
abortControllerRef.current = null
|
||||
}, [workflowStore])
|
||||
|
||||
const handleRestoreFromPublishedWorkflow = useCallback((publishedWorkflow: VersionHistory) => {
|
||||
|
|
|
|||
|
|
@ -291,9 +291,10 @@ const Result: FC<IResultProps> = ({
|
|||
if (isWorkflow) {
|
||||
const otherOptions: IOtherOptions = {
|
||||
isPublicAPI: !isInstalledApp,
|
||||
onWorkflowStarted: ({ workflow_run_id, task_id, data }) => {
|
||||
if (data.is_resumption) {
|
||||
setWorkflowProcessData(produce(getWorkflowProcessData()!, (draft) => {
|
||||
onWorkflowStarted: ({ workflow_run_id, task_id }) => {
|
||||
const workflowProcessData = getWorkflowProcessData()
|
||||
if (workflowProcessData && workflowProcessData.tracing.length > 0) {
|
||||
setWorkflowProcessData(produce(workflowProcessData, (draft) => {
|
||||
draft.expand = true
|
||||
draft.status = WorkflowRunningStatus.Running
|
||||
}))
|
||||
|
|
@ -369,8 +370,9 @@ const Result: FC<IResultProps> = ({
|
|||
}))
|
||||
},
|
||||
onNodeStarted: ({ data }) => {
|
||||
if (data.is_resumption) {
|
||||
setWorkflowProcessData(produce(getWorkflowProcessData()!, (draft) => {
|
||||
const workflowProcessData = getWorkflowProcessData()
|
||||
if (workflowProcessData && workflowProcessData.tracing.length > 0) {
|
||||
setWorkflowProcessData(produce(workflowProcessData, (draft) => {
|
||||
const currentIndex = draft.tracing!.findIndex(item => item.node_id === data.node_id)
|
||||
if (currentIndex > -1) {
|
||||
draft.expand = true
|
||||
|
|
|
|||
|
|
@ -92,7 +92,6 @@ export const useWorkflowRun = () => {
|
|||
handleWorkflowTextChunk,
|
||||
handleWorkflowTextReplace,
|
||||
handleWorkflowPaused,
|
||||
handleWorkflowResume,
|
||||
} = useWorkflowRunEvent()
|
||||
|
||||
const handleBackupDraft = useCallback(() => {
|
||||
|
|
@ -379,13 +378,7 @@ export const useWorkflowRun = () => {
|
|||
const baseSseOptions: IOtherOptions = {
|
||||
...restCallback,
|
||||
onWorkflowStarted: (params) => {
|
||||
const { is_resumption } = params.data
|
||||
if (is_resumption) {
|
||||
handleWorkflowResume()
|
||||
}
|
||||
else {
|
||||
handleWorkflowStarted(params)
|
||||
}
|
||||
handleWorkflowStarted(params)
|
||||
|
||||
if (onWorkflowStarted)
|
||||
onWorkflowStarted(params)
|
||||
|
|
@ -831,7 +824,7 @@ export const useWorkflowRun = () => {
|
|||
},
|
||||
finalCallbacks,
|
||||
)
|
||||
}, [store, doSyncWorkflowDraft, workflowStore, pathname, handleWorkflowFailed, flowId, handleWorkflowResume, handleWorkflowStarted, handleWorkflowFinished, fetchInspectVars, invalidAllLastRun, handleWorkflowNodeStarted, handleWorkflowNodeFinished, handleWorkflowNodeIterationStarted, handleWorkflowNodeIterationNext, handleWorkflowNodeIterationFinished, handleWorkflowNodeLoopStarted, handleWorkflowNodeLoopNext, handleWorkflowNodeLoopFinished, handleWorkflowNodeRetry, handleWorkflowAgentLog, handleWorkflowTextChunk, handleWorkflowTextReplace, handleWorkflowPaused, handleWorkflowNodeHumanInputRequired, handleWorkflowNodeHumanInputFormFilled])
|
||||
}, [store, doSyncWorkflowDraft, workflowStore, pathname, handleWorkflowFailed, flowId, handleWorkflowStarted, handleWorkflowFinished, fetchInspectVars, invalidAllLastRun, handleWorkflowNodeStarted, handleWorkflowNodeFinished, handleWorkflowNodeIterationStarted, handleWorkflowNodeIterationNext, handleWorkflowNodeIterationFinished, handleWorkflowNodeLoopStarted, handleWorkflowNodeLoopNext, handleWorkflowNodeLoopFinished, handleWorkflowNodeRetry, handleWorkflowAgentLog, handleWorkflowTextChunk, handleWorkflowTextReplace, handleWorkflowPaused, handleWorkflowNodeHumanInputRequired, handleWorkflowNodeHumanInputFormFilled])
|
||||
|
||||
const handleStopRun = useCallback((taskId: string) => {
|
||||
const setStoppedState = () => {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,13 @@ export const useWorkflowNodeHumanInputRequired = () => {
|
|||
draft.humanInputFormDataList.push(data)
|
||||
}
|
||||
}
|
||||
const currentIndex = draft.tracing!.findIndex(item => item.node_id === data.node_id)
|
||||
if (currentIndex > -1) {
|
||||
draft.tracing![currentIndex] = {
|
||||
...draft.tracing![currentIndex],
|
||||
status: NodeRunningStatus.Paused,
|
||||
}
|
||||
}
|
||||
})
|
||||
setWorkflowRunningData(newWorkflowRunningData)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ export const useWorkflowNodeStarted = () => {
|
|||
},
|
||||
) => {
|
||||
const { data } = params
|
||||
const { is_resumption } = data
|
||||
const {
|
||||
workflowRunningData,
|
||||
setWorkflowRunningData,
|
||||
|
|
@ -34,16 +33,14 @@ export const useWorkflowNodeStarted = () => {
|
|||
transform,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
if (is_resumption) {
|
||||
const currentIndex = workflowRunningData?.tracing?.findIndex(item => item.node_id === data.node_id)
|
||||
if (currentIndex && currentIndex > -1) {
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
draft.tracing![currentIndex] = {
|
||||
...data,
|
||||
status: NodeRunningStatus.Running,
|
||||
}
|
||||
}))
|
||||
}
|
||||
const currentIndex = workflowRunningData?.tracing?.findIndex(item => item.node_id === data.node_id)
|
||||
if (currentIndex && currentIndex > -1) {
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
draft.tracing![currentIndex] = {
|
||||
...data,
|
||||
status: NodeRunningStatus.Running,
|
||||
}
|
||||
}))
|
||||
}
|
||||
else {
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
import { produce } from 'immer'
|
||||
import { useCallback } from 'react'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
|
||||
export const useWorkflowResume = () => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
|
||||
const handleWorkflowResume = useCallback(() => {
|
||||
const {
|
||||
workflowRunningData,
|
||||
setWorkflowRunningData,
|
||||
} = workflowStore.getState()
|
||||
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
draft.result = {
|
||||
...draft.result,
|
||||
status: WorkflowRunningStatus.Running,
|
||||
}
|
||||
}))
|
||||
}, [workflowStore])
|
||||
|
||||
return {
|
||||
handleWorkflowResume,
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,6 @@ import {
|
|||
useWorkflowTextChunk,
|
||||
useWorkflowTextReplace,
|
||||
} from '.'
|
||||
import { useWorkflowResume } from './use-workflow-resume'
|
||||
|
||||
export const useWorkflowRunEvent = () => {
|
||||
const { handleWorkflowStarted } = useWorkflowStarted()
|
||||
|
|
@ -39,7 +38,6 @@ export const useWorkflowRunEvent = () => {
|
|||
const { handleWorkflowPaused } = useWorkflowPaused()
|
||||
const { handleWorkflowNodeHumanInputRequired } = useWorkflowNodeHumanInputRequired()
|
||||
const { handleWorkflowNodeHumanInputFormFilled } = useWorkflowNodeHumanInputFormFilled()
|
||||
const { handleWorkflowResume } = useWorkflowResume()
|
||||
|
||||
return {
|
||||
handleWorkflowStarted,
|
||||
|
|
@ -60,6 +58,5 @@ export const useWorkflowRunEvent = () => {
|
|||
handleWorkflowPaused,
|
||||
handleWorkflowNodeHumanInputFormFilled,
|
||||
handleWorkflowNodeHumanInputRequired,
|
||||
handleWorkflowResume,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,15 @@ export const useWorkflowStarted = () => {
|
|||
edges,
|
||||
setEdges,
|
||||
} = store.getState()
|
||||
if (workflowRunningData?.result?.status === WorkflowRunningStatus.Paused) {
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
draft.result = {
|
||||
...draft.result,
|
||||
status: WorkflowRunningStatus.Running,
|
||||
}
|
||||
}))
|
||||
return
|
||||
}
|
||||
setIterParallelLogMap(new Map())
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
draft.task_id = task_id
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ const ChatWrapper = (
|
|||
suggestedQuestions,
|
||||
handleSend,
|
||||
handleRestart,
|
||||
setTargetMessageId,
|
||||
handleSwitchSibling,
|
||||
handleSubmitHumanInputForm,
|
||||
getHumanInputNodeData,
|
||||
} = useChat(
|
||||
|
|
@ -123,6 +123,12 @@ const ChatWrapper = (
|
|||
doSend(editedQuestion ? editedQuestion.message : question.content, editedQuestion ? editedQuestion.files : question.message_files, true, isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null)
|
||||
}, [chatList, doSend])
|
||||
|
||||
const doSwitchSibling = useCallback((siblingMessageId: string) => {
|
||||
handleSwitchSibling(siblingMessageId, {
|
||||
onGetSuggestedQuestions: (messageId, getAbortController) => fetchSuggestedQuestions(appDetail!.id, messageId, getAbortController),
|
||||
})
|
||||
}, [handleSwitchSibling, appDetail])
|
||||
|
||||
const doHumanInputFormSubmit = useCallback(async (formToken: string, formData: any) => {
|
||||
// Handle human input form submission
|
||||
await handleSubmitHumanInputForm(formToken, formData)
|
||||
|
|
@ -196,7 +202,7 @@ const ChatWrapper = (
|
|||
suggestedQuestions={suggestedQuestions}
|
||||
showPromptLog
|
||||
chatAnswerContainerInner="!pr-2"
|
||||
switchSibling={setTargetMessageId}
|
||||
switchSibling={doSwitchSibling}
|
||||
inputDisabled={inputDisabled}
|
||||
hideAvatar
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import type {
|
|||
Inputs,
|
||||
} from '@/app/components/base/chat/types'
|
||||
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||
import type { IOtherOptions } from '@/service/base'
|
||||
import { uniqBy } from 'es-toolkit/compat'
|
||||
import { produce, setAutoFreeze } from 'immer'
|
||||
import {
|
||||
|
|
@ -29,6 +30,7 @@ import { useToastContext } from '@/app/components/base/toast'
|
|||
import {
|
||||
CUSTOM_NODE,
|
||||
} from '@/app/components/workflow/constants'
|
||||
import { sseGet } from '@/service/base'
|
||||
import { useInvalidAllLastRun } from '@/service/use-workflow'
|
||||
import { submitHumanInputForm } from '@/service/workflow'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
|
|
@ -63,6 +65,7 @@ export const useChat = (
|
|||
const taskIdRef = useRef('')
|
||||
const [isResponding, setIsResponding] = useState(false)
|
||||
const isRespondingRef = useRef(false)
|
||||
const workflowEventsAbortControllerRef = useRef<AbortController | null>(null)
|
||||
const configsMap = useHooksStore(s => s.configsMap)
|
||||
const invalidAllLastRun = useInvalidAllLastRun(configsMap?.flowType, configsMap?.flowId)
|
||||
const { fetchInspectVars } = useSetWorkflowVarsWithValue()
|
||||
|
|
@ -137,6 +140,29 @@ export const useChat = (
|
|||
})
|
||||
}, [])
|
||||
|
||||
type UpdateChatTreeNode = {
|
||||
(id: string, fields: Partial<ChatItemInTree>): void
|
||||
(id: string, update: (node: ChatItemInTree) => void): void
|
||||
}
|
||||
|
||||
const updateChatTreeNode: UpdateChatTreeNode = useCallback((
|
||||
id: string,
|
||||
fieldsOrUpdate: Partial<ChatItemInTree> | ((node: ChatItemInTree) => void),
|
||||
) => {
|
||||
const nextState = produceChatTreeNode(id, (node) => {
|
||||
if (typeof fieldsOrUpdate === 'function') {
|
||||
fieldsOrUpdate(node)
|
||||
}
|
||||
else {
|
||||
Object.keys(fieldsOrUpdate).forEach((key) => {
|
||||
(node as any)[key] = (fieldsOrUpdate as any)[key]
|
||||
})
|
||||
}
|
||||
})
|
||||
setChatTree(nextState)
|
||||
chatTreeRef.current = nextState
|
||||
}, [produceChatTreeNode])
|
||||
|
||||
const handleStop = useCallback(() => {
|
||||
hasStopResponded.current = true
|
||||
handleResponding(false)
|
||||
|
|
@ -146,6 +172,8 @@ export const useChat = (
|
|||
setLoopTimes(DEFAULT_LOOP_TIMES)
|
||||
if (suggestedQuestionsAbortControllerRef.current)
|
||||
suggestedQuestionsAbortControllerRef.current.abort()
|
||||
if (workflowEventsAbortControllerRef.current)
|
||||
workflowEventsAbortControllerRef.current.abort()
|
||||
}, [handleResponding, setIterTimes, setLoopTimes, stopChat])
|
||||
|
||||
const handleRestart = useCallback(() => {
|
||||
|
|
@ -212,6 +240,10 @@ export const useChat = (
|
|||
return false
|
||||
}
|
||||
|
||||
// Abort previous handleResume SSE connection if any
|
||||
if (workflowEventsAbortControllerRef.current)
|
||||
workflowEventsAbortControllerRef.current.abort()
|
||||
|
||||
const parentMessage = threadMessages.find(item => item.id === params.parent_message_id)
|
||||
|
||||
const placeholderQuestionId = `question-${Date.now()}`
|
||||
|
|
@ -278,6 +310,9 @@ export const useChat = (
|
|||
handleRun(
|
||||
bodyParams,
|
||||
{
|
||||
getAbortController: (abortController) => {
|
||||
workflowEventsAbortControllerRef.current = abortController
|
||||
},
|
||||
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
|
||||
responseItem.content = responseItem.content + message
|
||||
|
||||
|
|
@ -356,10 +391,21 @@ export const useChat = (
|
|||
onError() {
|
||||
handleResponding(false)
|
||||
},
|
||||
onWorkflowStarted: ({ workflow_run_id, task_id, data: { is_resumption } }) => {
|
||||
if (is_resumption) {
|
||||
onWorkflowStarted: ({ workflow_run_id, task_id, conversation_id, message_id }) => {
|
||||
// If there are no streaming messages, we still need to set the conversation_id to avoid create a new conversation when regeneration in chat-flow.
|
||||
if (conversation_id) {
|
||||
conversationId.current = conversation_id
|
||||
}
|
||||
if (message_id && !hasSetResponseId) {
|
||||
questionItem.id = `question-${message_id}`
|
||||
responseItem.id = message_id
|
||||
responseItem.parentMessageId = questionItem.id
|
||||
hasSetResponseId = true
|
||||
}
|
||||
|
||||
if (responseItem.workflowProcess && responseItem.workflowProcess.tracing.length > 0) {
|
||||
handleResponding(true)
|
||||
responseItem.workflowProcess!.status = WorkflowRunningStatus.Running
|
||||
responseItem.workflowProcess.status = WorkflowRunningStatus.Running
|
||||
}
|
||||
else {
|
||||
taskIdRef.current = task_id
|
||||
|
|
@ -440,14 +486,11 @@ export const useChat = (
|
|||
}
|
||||
},
|
||||
onNodeStarted: ({ data }) => {
|
||||
const { is_resumption } = data
|
||||
if (is_resumption) {
|
||||
const currentIndex = responseItem.workflowProcess!.tracing!.findIndex(item => item.node_id === data.node_id)
|
||||
if (currentIndex > -1) {
|
||||
responseItem.workflowProcess!.tracing![currentIndex] = {
|
||||
...data,
|
||||
status: NodeRunningStatus.Running,
|
||||
}
|
||||
const currentIndex = responseItem.workflowProcess!.tracing!.findIndex(item => item.node_id === data.node_id)
|
||||
if (currentIndex > -1) {
|
||||
responseItem.workflowProcess!.tracing![currentIndex] = {
|
||||
...data,
|
||||
status: NodeRunningStatus.Running,
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
@ -596,13 +639,293 @@ export const useChat = (
|
|||
return node
|
||||
}
|
||||
|
||||
const handleResume = useCallback((
|
||||
messageId: string,
|
||||
workflowRunId: string,
|
||||
{
|
||||
onGetSuggestedQuestions,
|
||||
}: SendCallback,
|
||||
) => {
|
||||
// Re-subscribe to workflow events for the specific message
|
||||
const url = `/workflow/${workflowRunId}/events`
|
||||
|
||||
const otherOptions: IOtherOptions = {
|
||||
getAbortController: (abortController) => {
|
||||
workflowEventsAbortControllerRef.current = abortController
|
||||
},
|
||||
onData: (message: string, _isFirstMessage: boolean, { conversationId: newConversationId, messageId: msgId, taskId }: any) => {
|
||||
updateChatTreeNode(messageId, (responseItem) => {
|
||||
responseItem.content = responseItem.content + message
|
||||
if (msgId)
|
||||
responseItem.id = msgId
|
||||
})
|
||||
|
||||
if (newConversationId)
|
||||
conversationId.current = newConversationId
|
||||
|
||||
if (taskId)
|
||||
taskIdRef.current = taskId
|
||||
},
|
||||
async onCompleted(hasError?: boolean) {
|
||||
const { workflowRunningData } = workflowStore.getState()
|
||||
handleResponding(false)
|
||||
|
||||
if (workflowRunningData?.result.status !== WorkflowRunningStatus.Paused) {
|
||||
fetchInspectVars({})
|
||||
invalidAllLastRun()
|
||||
|
||||
if (hasError)
|
||||
return
|
||||
|
||||
if (config?.suggested_questions_after_answer?.enabled && !hasStopResponded.current && onGetSuggestedQuestions) {
|
||||
try {
|
||||
const { data }: any = await onGetSuggestedQuestions(
|
||||
messageId,
|
||||
newAbortController => suggestedQuestionsAbortControllerRef.current = newAbortController,
|
||||
)
|
||||
setSuggestQuestions(data)
|
||||
}
|
||||
catch {
|
||||
setSuggestQuestions([])
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onMessageEnd: (messageEnd) => {
|
||||
updateChatTreeNode(messageId, (responseItem) => {
|
||||
responseItem.citation = messageEnd.metadata?.retriever_resources || []
|
||||
const processedFilesFromResponse = getProcessedFilesFromResponse(messageEnd.files || [])
|
||||
responseItem.allFiles = uniqBy([...(responseItem.allFiles || []), ...(processedFilesFromResponse || [])], 'id')
|
||||
})
|
||||
},
|
||||
onMessageReplace: (messageReplace) => {
|
||||
updateChatTreeNode(messageId, (responseItem) => {
|
||||
responseItem.content = messageReplace.answer
|
||||
})
|
||||
},
|
||||
onError() {
|
||||
handleResponding(false)
|
||||
},
|
||||
onWorkflowStarted: ({ workflow_run_id, task_id }) => {
|
||||
handleResponding(true)
|
||||
hasStopResponded.current = false
|
||||
updateChatTreeNode(messageId, (responseItem) => {
|
||||
if (responseItem.workflowProcess && responseItem.workflowProcess.tracing.length > 0) {
|
||||
responseItem.workflowProcess.status = WorkflowRunningStatus.Running
|
||||
}
|
||||
else {
|
||||
taskIdRef.current = task_id
|
||||
responseItem.workflow_run_id = workflow_run_id
|
||||
responseItem.workflowProcess = {
|
||||
status: WorkflowRunningStatus.Running,
|
||||
tracing: [],
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
onWorkflowFinished: ({ data: workflowFinishedData }) => {
|
||||
updateChatTreeNode(messageId, (responseItem) => {
|
||||
if (responseItem.workflowProcess)
|
||||
responseItem.workflowProcess.status = workflowFinishedData.status as WorkflowRunningStatus
|
||||
})
|
||||
},
|
||||
onIterationStart: ({ data: iterationStartedData }) => {
|
||||
updateChatTreeNode(messageId, (responseItem) => {
|
||||
if (!responseItem.workflowProcess)
|
||||
return
|
||||
if (!responseItem.workflowProcess.tracing)
|
||||
responseItem.workflowProcess.tracing = []
|
||||
responseItem.workflowProcess.tracing.push({
|
||||
...iterationStartedData,
|
||||
status: WorkflowRunningStatus.Running,
|
||||
})
|
||||
})
|
||||
},
|
||||
onIterationFinish: ({ data: iterationFinishedData }) => {
|
||||
updateChatTreeNode(messageId, (responseItem) => {
|
||||
if (!responseItem.workflowProcess?.tracing)
|
||||
return
|
||||
const tracing = responseItem.workflowProcess.tracing
|
||||
const iterationIndex = tracing.findIndex(item => item.node_id === iterationFinishedData.node_id
|
||||
&& (item.execution_metadata?.parallel_id === iterationFinishedData.execution_metadata?.parallel_id || item.parallel_id === iterationFinishedData.execution_metadata?.parallel_id))!
|
||||
if (iterationIndex > -1) {
|
||||
tracing[iterationIndex] = {
|
||||
...tracing[iterationIndex],
|
||||
...iterationFinishedData,
|
||||
status: WorkflowRunningStatus.Succeeded,
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
onNodeStarted: ({ data: nodeStartedData }) => {
|
||||
updateChatTreeNode(messageId, (responseItem) => {
|
||||
if (!responseItem.workflowProcess)
|
||||
return
|
||||
if (!responseItem.workflowProcess.tracing)
|
||||
responseItem.workflowProcess.tracing = []
|
||||
|
||||
const currentIndex = responseItem.workflowProcess.tracing.findIndex(item => item.node_id === nodeStartedData.node_id)
|
||||
if (currentIndex > -1) {
|
||||
responseItem.workflowProcess.tracing[currentIndex] = {
|
||||
...nodeStartedData,
|
||||
status: NodeRunningStatus.Running,
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (nodeStartedData.iteration_id)
|
||||
return
|
||||
|
||||
responseItem.workflowProcess.tracing.push({
|
||||
...nodeStartedData,
|
||||
status: WorkflowRunningStatus.Running,
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
onNodeFinished: ({ data: nodeFinishedData }) => {
|
||||
updateChatTreeNode(messageId, (responseItem) => {
|
||||
if (!responseItem.workflowProcess?.tracing)
|
||||
return
|
||||
|
||||
if (nodeFinishedData.iteration_id)
|
||||
return
|
||||
|
||||
const currentIndex = responseItem.workflowProcess.tracing.findIndex((item) => {
|
||||
if (!item.execution_metadata?.parallel_id)
|
||||
return item.id === nodeFinishedData.id
|
||||
|
||||
return item.id === nodeFinishedData.id && (item.execution_metadata?.parallel_id === nodeFinishedData.execution_metadata?.parallel_id)
|
||||
})
|
||||
if (currentIndex > -1)
|
||||
responseItem.workflowProcess.tracing[currentIndex] = nodeFinishedData as any
|
||||
})
|
||||
},
|
||||
onLoopStart: ({ data: loopStartedData }) => {
|
||||
updateChatTreeNode(messageId, (responseItem) => {
|
||||
if (!responseItem.workflowProcess)
|
||||
return
|
||||
if (!responseItem.workflowProcess.tracing)
|
||||
responseItem.workflowProcess.tracing = []
|
||||
responseItem.workflowProcess.tracing.push({
|
||||
...loopStartedData,
|
||||
status: WorkflowRunningStatus.Running,
|
||||
})
|
||||
})
|
||||
},
|
||||
onLoopFinish: ({ data: loopFinishedData }) => {
|
||||
updateChatTreeNode(messageId, (responseItem) => {
|
||||
if (!responseItem.workflowProcess?.tracing)
|
||||
return
|
||||
const tracing = responseItem.workflowProcess.tracing
|
||||
const loopIndex = tracing.findIndex(item => item.node_id === loopFinishedData.node_id
|
||||
&& (item.execution_metadata?.parallel_id === loopFinishedData.execution_metadata?.parallel_id || item.parallel_id === loopFinishedData.execution_metadata?.parallel_id))!
|
||||
if (loopIndex > -1) {
|
||||
tracing[loopIndex] = {
|
||||
...tracing[loopIndex],
|
||||
...loopFinishedData,
|
||||
status: WorkflowRunningStatus.Succeeded,
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
onHumanInputRequired: ({ data: humanInputRequiredData }) => {
|
||||
updateChatTreeNode(messageId, (responseItem) => {
|
||||
if (!responseItem.humanInputFormDataList) {
|
||||
responseItem.humanInputFormDataList = [humanInputRequiredData]
|
||||
}
|
||||
else {
|
||||
const currentFormIndex = responseItem.humanInputFormDataList.findIndex(item => item.node_id === humanInputRequiredData.node_id)
|
||||
if (currentFormIndex > -1) {
|
||||
responseItem.humanInputFormDataList[currentFormIndex] = humanInputRequiredData
|
||||
}
|
||||
else {
|
||||
responseItem.humanInputFormDataList.push(humanInputRequiredData)
|
||||
}
|
||||
}
|
||||
if (responseItem.workflowProcess?.tracing) {
|
||||
const currentTracingIndex = responseItem.workflowProcess.tracing.findIndex(item => item.node_id === humanInputRequiredData.node_id)
|
||||
if (currentTracingIndex > -1)
|
||||
responseItem.workflowProcess.tracing[currentTracingIndex].status = NodeRunningStatus.Paused
|
||||
}
|
||||
})
|
||||
},
|
||||
onHumanInputFormFilled: ({ data: humanInputFilledFormData }) => {
|
||||
updateChatTreeNode(messageId, (responseItem) => {
|
||||
if (responseItem.humanInputFormDataList?.length) {
|
||||
const currentFormIndex = responseItem.humanInputFormDataList.findIndex(item => item.node_id === humanInputFilledFormData.node_id)
|
||||
if (currentFormIndex > -1)
|
||||
responseItem.humanInputFormDataList.splice(currentFormIndex, 1)
|
||||
}
|
||||
if (!responseItem.humanInputFilledFormDataList) {
|
||||
responseItem.humanInputFilledFormDataList = [humanInputFilledFormData]
|
||||
}
|
||||
else {
|
||||
responseItem.humanInputFilledFormDataList.push(humanInputFilledFormData)
|
||||
}
|
||||
})
|
||||
},
|
||||
onWorkflowPaused: ({ data: workflowPausedData }) => {
|
||||
const resumeUrl = `/apps/${configsMap?.flowId}/workflow/${workflowPausedData.workflow_run_id}/events`
|
||||
sseGet(
|
||||
resumeUrl,
|
||||
{},
|
||||
otherOptions,
|
||||
)
|
||||
updateChatTreeNode(messageId, (responseItem) => {
|
||||
responseItem.workflowProcess!.status = WorkflowRunningStatus.Paused
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
if (workflowEventsAbortControllerRef.current)
|
||||
workflowEventsAbortControllerRef.current.abort()
|
||||
|
||||
sseGet(
|
||||
url,
|
||||
{},
|
||||
otherOptions,
|
||||
)
|
||||
}, [updateChatTreeNode, handleResponding, workflowStore, fetchInspectVars, invalidAllLastRun, config?.suggested_questions_after_answer, configsMap?.flowId])
|
||||
|
||||
const handleSwitchSibling = useCallback((
|
||||
siblingMessageId: string,
|
||||
callbacks: SendCallback,
|
||||
) => {
|
||||
setTargetMessageId(siblingMessageId)
|
||||
|
||||
// Helper to find message in tree
|
||||
const findMessageInTree = (nodes: ChatItemInTree[], targetId: string): ChatItemInTree | undefined => {
|
||||
for (const node of nodes) {
|
||||
if (node.id === targetId)
|
||||
return node
|
||||
if (node.children) {
|
||||
const found = findMessageInTree(node.children, targetId)
|
||||
if (found)
|
||||
return found
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
const targetMessage = findMessageInTree(chatTreeRef.current, siblingMessageId)
|
||||
if (targetMessage?.workflow_run_id && targetMessage.humanInputFormDataList && targetMessage.humanInputFormDataList.length > 0) {
|
||||
handleResume(
|
||||
targetMessage.id,
|
||||
targetMessage.workflow_run_id,
|
||||
callbacks,
|
||||
)
|
||||
}
|
||||
}, [handleResume])
|
||||
|
||||
return {
|
||||
conversationId: conversationId.current,
|
||||
chatList,
|
||||
setTargetMessageId,
|
||||
handleSwitchSibling,
|
||||
handleSend,
|
||||
handleStop,
|
||||
handleRestart,
|
||||
handleResume,
|
||||
handleSubmitHumanInputForm,
|
||||
getHumanInputNodeData,
|
||||
isResponding,
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ const MetaData: FC<Props> = ({
|
|||
<div className="system-xs-regular w-[104px] shrink-0 truncate px-2 py-1.5 text-text-tertiary">{t('meta.tokens', { ns: 'runLog' })}</div>
|
||||
<div className="system-xs-regular grow px-2 py-1.5 text-text-secondary">
|
||||
{['running', 'paused'].includes(status) && (
|
||||
<div className="my-1 h-2 w-[48px] rounded-sm bg-text-quaternary" />
|
||||
<div className="my-1 h-2 w-[48px] animate-pulse rounded-sm bg-text-quaternary" />
|
||||
)}
|
||||
{!['running', 'paused'].includes(status) && (
|
||||
<span>{`${tokens || 0} Tokens`}</span>
|
||||
|
|
|
|||
|
|
@ -99,6 +99,11 @@
|
|||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/(humanInputLayout)/form/[token]/form.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 7
|
||||
}
|
||||
},
|
||||
"app/(shareLayout)/components/splash.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 1
|
||||
|
|
@ -586,7 +591,7 @@
|
|||
"count": 3
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 4
|
||||
"count": 5
|
||||
}
|
||||
},
|
||||
"app/components/app/text-generate/item/result-tab.tsx": {
|
||||
|
|
@ -737,7 +742,7 @@
|
|||
},
|
||||
"app/components/base/chat/chat-with-history/chat-wrapper.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 6
|
||||
"count": 7
|
||||
}
|
||||
},
|
||||
"app/components/base/chat/chat-with-history/context.tsx": {
|
||||
|
|
@ -786,9 +791,32 @@
|
|||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/chat/chat/answer/human-input-content/human-input-form.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"app/components/base/chat/chat/answer/human-input-content/type.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"app/components/base/chat/chat/answer/human-input-content/utils.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/chat/chat/answer/human-input-form-list.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/base/chat/chat/answer/index.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 2
|
||||
"count": 3
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/chat/chat/answer/workflow-process.tsx": {
|
||||
|
|
@ -819,7 +847,7 @@
|
|||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 15
|
||||
"count": 18
|
||||
}
|
||||
},
|
||||
"app/components/base/chat/chat/index.tsx": {
|
||||
|
|
@ -827,7 +855,7 @@
|
|||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"app/components/base/chat/chat/type.ts": {
|
||||
|
|
@ -842,7 +870,7 @@
|
|||
},
|
||||
"app/components/base/chat/embedded-chatbot/chat-wrapper.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 6
|
||||
"count": 7
|
||||
}
|
||||
},
|
||||
"app/components/base/chat/embedded-chatbot/context.tsx": {
|
||||
|
|
@ -1235,7 +1263,7 @@
|
|||
},
|
||||
"app/components/base/markdown/react-markdown-wrapper.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 8
|
||||
"count": 9
|
||||
}
|
||||
},
|
||||
"app/components/base/mermaid/index.tsx": {
|
||||
|
|
@ -1341,7 +1369,7 @@
|
|||
},
|
||||
"app/components/base/prompt-editor/index.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"app/components/base/prompt-editor/plugins/component-picker-block/index.tsx": {
|
||||
|
|
@ -1354,16 +1382,41 @@
|
|||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/prompt-editor/plugins/draggable-plugin/index.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/base/prompt-editor/plugins/history-block/component.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/prompt-editor/plugins/hitl-input-block/hitl-input-block-replacement-block.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/prompt-editor/plugins/hitl-input-block/input-field.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/prompt-editor/plugins/hitl-input-block/pre-populate.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/prompt-editor/plugins/on-blur-or-focus-block.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/prompt-editor/plugins/shortcuts-popup-plugin/index.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/base/prompt-editor/plugins/update-block.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
|
|
@ -3004,7 +3057,7 @@
|
|||
},
|
||||
"app/components/workflow/nodes/_base/components/before-run-form/index.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 8
|
||||
"count": 12
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx": {
|
||||
|
|
@ -3342,6 +3395,72 @@
|
|||
"count": 5
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/human-input/components/delivery-method/email-configure-modal.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/human-input/components/delivery-method/method-item.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/human-input/components/delivery-method/recipient/email-input.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/human-input/components/delivery-method/recipient/member-list.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/human-input/components/delivery-method/recipient/member-selector.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/human-input/components/delivery-method/test-email-sender.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/human-input/components/form-content-preview.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/human-input/components/form-content.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 1
|
||||
},
|
||||
"react/no-nested-component-definitions": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/human-input/components/single-run-form.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/human-input/components/variable-in-markdown.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 8
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/human-input/default.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/human-input/hooks/use-single-run-form-params.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 7
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/if-else/components/condition-list/condition-input.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
|
|
@ -3830,7 +3949,7 @@
|
|||
},
|
||||
"app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 5
|
||||
"count": 6
|
||||
}
|
||||
},
|
||||
"app/components/workflow/panel/debug-and-preview/conversation-variable-modal.tsx": {
|
||||
|
|
@ -3840,7 +3959,7 @@
|
|||
},
|
||||
"app/components/workflow/panel/debug-and-preview/hooks.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 7
|
||||
"count": 12
|
||||
}
|
||||
},
|
||||
"app/components/workflow/panel/env-panel/variable-modal.tsx": {
|
||||
|
|
@ -3851,6 +3970,11 @@
|
|||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/panel/human-input-form-list.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/panel/inputs-panel.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 4
|
||||
|
|
@ -3866,7 +3990,7 @@
|
|||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/workflow/run/hooks.ts": {
|
||||
|
|
@ -3980,6 +4104,11 @@
|
|||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/store/workflow/workflow-slice.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/types.ts": {
|
||||
"ts/no-empty-object-type": {
|
||||
"count": 3
|
||||
|
|
@ -4358,7 +4487,7 @@
|
|||
},
|
||||
"service/share.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 4
|
||||
"count": 5
|
||||
}
|
||||
},
|
||||
"service/tools.ts": {
|
||||
|
|
@ -4412,7 +4541,7 @@
|
|||
},
|
||||
"service/use-workflow.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"service/utils.spec.ts": {
|
||||
|
|
@ -4425,6 +4554,11 @@
|
|||
"count": 10
|
||||
}
|
||||
},
|
||||
"service/workflow.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"testing/testing.md": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
|
|
@ -4457,7 +4591,7 @@
|
|||
},
|
||||
"types/workflow.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 15
|
||||
"count": 17
|
||||
}
|
||||
},
|
||||
"utils/clipboard.ts": {
|
||||
|
|
|
|||
|
|
@ -105,7 +105,6 @@ export type NodeTracing = {
|
|||
parent_parallel_id?: string
|
||||
parent_parallel_start_node_id?: string
|
||||
agentLog?: AgentLogItemWithChildren[] // agent log
|
||||
is_resumption?: boolean // for human input node
|
||||
}
|
||||
|
||||
export type FetchWorkflowDraftResponse = {
|
||||
|
|
@ -166,8 +165,9 @@ export type WorkflowStartedResponse = {
|
|||
id: string
|
||||
workflow_id: string
|
||||
created_at: number
|
||||
is_resumption: boolean
|
||||
}
|
||||
conversation_id?: string // only in chatflow
|
||||
message_id?: string // only in chatflow
|
||||
}
|
||||
|
||||
export type WorkflowPausedResponse = {
|
||||
|
|
|
|||
Loading…
Reference in New Issue