Merge branch 'refs/heads/main' into feat/trigger

This commit is contained in:
zhsama
2025-10-24 14:44:15 +08:00
11 changed files with 165 additions and 194 deletions

View File

@ -14,7 +14,6 @@ import timezone from 'dayjs/plugin/timezone'
import { createContext, useContext } from 'use-context-selector'
import { useShallow } from 'zustand/react/shallow'
import { useTranslation } from 'react-i18next'
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
import type { ChatItemInTree } from '../../base/chat/types'
import Indicator from '../../header/indicator'
import VarPanel from './var-panel'
@ -44,8 +43,6 @@ import { noop } from 'lodash-es'
import PromptLogModal from '../../base/prompt-log-modal'
type AppStoreState = ReturnType<typeof useAppStore.getState>
type ConversationListItem = ChatConversationGeneralDetail | CompletionConversationGeneralDetail
type ConversationSelection = ConversationListItem | { id: string; isPlaceholder?: true }
dayjs.extend(utc)
dayjs.extend(timezone)
@ -206,7 +203,7 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
const { formatTime } = useTimestamp()
const { onClose, appDetail } = useContext(DrawerContext)
const { notify } = useContext(ToastContext)
const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal, showPromptLogModal, setShowPromptLogModal, currentLogModalActiveTab } = useAppStore(useShallow((state: AppStoreState) => ({
const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal, showPromptLogModal, setShowPromptLogModal, currentLogModalActiveTab } = useAppStore(useShallow(state => ({
currentLogItem: state.currentLogItem,
setCurrentLogItem: state.setCurrentLogItem,
showMessageLogModal: state.showMessageLogModal,
@ -898,19 +895,12 @@ const ChatConversationDetailComp: FC<{ appId?: string; conversationId?: string }
const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh }) => {
const { t } = useTranslation()
const { formatTime } = useTimestamp()
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
const conversationIdInUrl = searchParams.get('conversation_id') ?? undefined
const media = useBreakpoints()
const isMobile = media === MediaType.mobile
const [showDrawer, setShowDrawer] = useState<boolean>(false) // Whether to display the chat details drawer
const [currentConversation, setCurrentConversation] = useState<ConversationSelection | undefined>() // Currently selected conversation
const closingConversationIdRef = useRef<string | null>(null)
const pendingConversationIdRef = useRef<string | null>(null)
const pendingConversationCacheRef = useRef<ConversationSelection | undefined>(undefined)
const [currentConversation, setCurrentConversation] = useState<ChatConversationGeneralDetail | CompletionConversationGeneralDetail | undefined>() // Currently selected conversation
const isChatMode = appDetail.mode !== AppModeEnum.COMPLETION // Whether the app is a chat app
const isChatflow = appDetail.mode === AppModeEnum.ADVANCED_CHAT // Whether the app is a chatflow app
const { setShowPromptLogModal, setShowAgentLogModal, setShowMessageLogModal } = useAppStore(useShallow((state: AppStoreState) => ({
@ -919,92 +909,6 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
setShowMessageLogModal: state.setShowMessageLogModal,
})))
const activeConversationId = conversationIdInUrl ?? pendingConversationIdRef.current ?? currentConversation?.id
const buildUrlWithConversation = useCallback((conversationId?: string) => {
const params = new URLSearchParams(searchParams.toString())
if (conversationId)
params.set('conversation_id', conversationId)
else
params.delete('conversation_id')
const queryString = params.toString()
return queryString ? `${pathname}?${queryString}` : pathname
}, [pathname, searchParams])
const handleRowClick = useCallback((log: ConversationListItem) => {
if (conversationIdInUrl === log.id) {
if (!showDrawer)
setShowDrawer(true)
if (!currentConversation || currentConversation.id !== log.id)
setCurrentConversation(log)
return
}
pendingConversationIdRef.current = log.id
pendingConversationCacheRef.current = log
if (!showDrawer)
setShowDrawer(true)
if (currentConversation?.id !== log.id)
setCurrentConversation(undefined)
router.push(buildUrlWithConversation(log.id), { scroll: false })
}, [buildUrlWithConversation, conversationIdInUrl, currentConversation, router, showDrawer])
const currentConversationId = currentConversation?.id
useEffect(() => {
if (!conversationIdInUrl) {
if (pendingConversationIdRef.current)
return
if (showDrawer || currentConversationId) {
setShowDrawer(false)
setCurrentConversation(undefined)
}
closingConversationIdRef.current = null
pendingConversationCacheRef.current = undefined
return
}
if (closingConversationIdRef.current === conversationIdInUrl)
return
if (pendingConversationIdRef.current === conversationIdInUrl)
pendingConversationIdRef.current = null
const matchedConversation = logs?.data?.find((item: ConversationListItem) => item.id === conversationIdInUrl)
const nextConversation: ConversationSelection = matchedConversation
?? pendingConversationCacheRef.current
?? { id: conversationIdInUrl, isPlaceholder: true }
if (!showDrawer)
setShowDrawer(true)
if (!currentConversation || currentConversation.id !== conversationIdInUrl || (matchedConversation && currentConversation !== matchedConversation))
setCurrentConversation(nextConversation)
if (pendingConversationCacheRef.current?.id === conversationIdInUrl || matchedConversation)
pendingConversationCacheRef.current = undefined
}, [conversationIdInUrl, currentConversation, isChatMode, logs?.data, showDrawer])
const onCloseDrawer = useCallback(() => {
onRefresh()
setShowDrawer(false)
setCurrentConversation(undefined)
setShowPromptLogModal(false)
setShowAgentLogModal(false)
setShowMessageLogModal(false)
pendingConversationIdRef.current = null
pendingConversationCacheRef.current = undefined
closingConversationIdRef.current = conversationIdInUrl ?? null
if (conversationIdInUrl)
router.replace(buildUrlWithConversation(), { scroll: false })
}, [buildUrlWithConversation, conversationIdInUrl, onRefresh, router, setShowAgentLogModal, setShowMessageLogModal, setShowPromptLogModal])
// Annotated data needs to be highlighted
const renderTdValue = (value: string | number | null, isEmptyStyle: boolean, isHighlight = false, annotation?: LogAnnotation) => {
return (
@ -1023,6 +927,15 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
)
}
const onCloseDrawer = () => {
onRefresh()
setShowDrawer(false)
setCurrentConversation(undefined)
setShowPromptLogModal(false)
setShowAgentLogModal(false)
setShowMessageLogModal(false)
}
if (!logs)
return <Loading />
@ -1049,8 +962,11 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
const rightValue = get(log, isChatMode ? 'message_count' : 'message.answer')
return <tr
key={log.id}
className={cn('cursor-pointer border-b border-divider-subtle hover:bg-background-default-hover', activeConversationId !== log.id ? '' : 'bg-background-default-hover')}
onClick={() => handleRowClick(log)}>
className={cn('cursor-pointer border-b border-divider-subtle hover:bg-background-default-hover', currentConversation?.id !== log.id ? '' : 'bg-background-default-hover')}
onClick={() => {
setShowDrawer(true)
setCurrentConversation(log)
}}>
<td className='h-4'>
{!log.read_at && (
<div className='flex items-center p-3 pr-0.5'>

View File

@ -15,6 +15,7 @@ import {
InputVarType,
NodeRunningStatus,
VarType,
WorkflowRunningStatus,
} from '@/app/components/workflow/types'
import type { TriggerNodeType } from '@/app/components/workflow/types'
import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types'
@ -204,6 +205,45 @@ const useOneStepRun = <T>({
setListeningTriggerIsAll,
setShowVariableInspectPanel,
} = workflowStore.getState()
const updateNodeInspectRunningState = useCallback((nodeId: string, isRunning: boolean) => {
const {
nodesWithInspectVars,
setNodesWithInspectVars,
} = workflowStore.getState()
let hasChanges = false
const nodes = produce(nodesWithInspectVars, (draft) => {
const index = draft.findIndex(node => node.nodeId === nodeId)
if (index !== -1) {
const targetNode = draft[index]
if (targetNode.isSingRunRunning !== isRunning) {
targetNode.isSingRunRunning = isRunning
if (isRunning)
targetNode.isValueFetched = false
hasChanges = true
}
}
else if (isRunning) {
const { getNodes } = store.getState()
const target = getNodes().find(node => node.id === nodeId)
if (target) {
draft.unshift({
nodeId,
nodeType: target.data.type,
title: target.data.title,
vars: [],
nodePayload: target.data,
isSingRunRunning: true,
isValueFetched: false,
})
hasChanges = true
}
}
})
if (hasChanges)
setNodesWithInspectVars(nodes)
}, [workflowStore, store])
const invalidLastRun = useInvalidLastRun(flowType, flowId!, id)
const [runResult, doSetRunResult] = useState<NodeRunResult | null>(null)
const {
@ -246,13 +286,14 @@ const useOneStepRun = <T>({
const { getNodes } = store.getState()
const nodes = getNodes()
appendNodeInspectVars(id, vars, nodes)
updateNodeInspectRunningState(id, false)
if (data?.status === NodeRunningStatus.Succeeded) {
invalidLastRun()
if (isStartNode)
invalidateSysVarValues()
invalidateConversationVarValues() // loop, iteration, variable assigner node can update the conversation variables, but to simple the logic(some nodes may also can update in the future), all nodes refresh.
}
}, [isRunAfterSingleRun, runningStatus, flowId, id, store, appendNodeInspectVars, invalidLastRun, isStartNode, invalidateSysVarValues, invalidateConversationVarValues])
}, [isRunAfterSingleRun, runningStatus, flowId, id, store, appendNodeInspectVars, updateNodeInspectRunningState, invalidLastRun, isStartNode, invalidateSysVarValues, invalidateConversationVarValues])
const { handleNodeDataUpdate }: { handleNodeDataUpdate: (data: any) => void } = useNodeDataUpdate()
const setNodeRunning = () => {
@ -581,6 +622,8 @@ const useOneStepRun = <T>({
if (isPluginTriggerNode)
cancelPluginSingleRun()
updateNodeInspectRunningState(id, true)
if (isTriggerNode)
startTriggerListening()
else
@ -883,6 +926,8 @@ const useOneStepRun = <T>({
cancelPluginSingleRun()
if (isTriggerNode)
stopTriggerListening()
if (!isIteration && !isLoop)
updateNodeInspectRunningState(id, false)
if (!isPausedRef.current && !isIteration && !isLoop && res) {
setRunResult({
...res,
@ -930,6 +975,16 @@ const useOneStepRun = <T>({
},
})
stopTriggerListening()
updateNodeInspectRunningState(id, false)
const {
workflowRunningData,
setWorkflowRunningData,
} = workflowStore.getState()
if (workflowRunningData) {
setWorkflowRunningData(produce(workflowRunningData, (draft) => {
draft.result.status = WorkflowRunningStatus.Stopped
}))
}
}, [
isTriggerNode,
runningStatus,
@ -938,6 +993,8 @@ const useOneStepRun = <T>({
handleNodeDataUpdate,
id,
stopTriggerListening,
updateNodeInspectRunningState,
workflowStore,
])
const toVarInputs = (variables: Variable[]): InputVar[] => {