mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 01:48:04 +08:00
Merge fix/chore-fix into dev/plugin-deploy
This commit is contained in:
@ -66,9 +66,12 @@ const AllTools = ({
|
||||
return mergedTools.filter(toolWithProvider => toolWithProvider.tools.length > 0)
|
||||
|
||||
return mergedTools.filter((toolWithProvider) => {
|
||||
return isMatchingKeywords(toolWithProvider.name, searchText) || toolWithProvider.tools.some((tool) => {
|
||||
return tool.label[language].toLowerCase().includes(searchText.toLowerCase()) || tool.name.toLowerCase().includes(searchText.toLowerCase())
|
||||
})
|
||||
return isMatchingKeywords(toolWithProvider.name, searchText)
|
||||
|| toolWithProvider.tools.some((tool) => {
|
||||
return Object.values(tool.label).some((label) => {
|
||||
return isMatchingKeywords(label.toLowerCase(), searchText.toLowerCase())
|
||||
})
|
||||
})
|
||||
})
|
||||
}, [activeTab, buildInTools, customTools, workflowTools, searchText, language, hasFilter])
|
||||
|
||||
|
||||
@ -58,7 +58,7 @@ const Blocks = ({
|
||||
>
|
||||
{
|
||||
classification !== '-' && !!list.length && (
|
||||
<div className='flex items-start px-3 h-[22px] text-xs font-medium text-gray-500'>
|
||||
<div className='flex items-start px-3 h-[22px] text-xs font-medium text-text-tertiary'>
|
||||
{t(`workflow.tabs.${classification}`)}
|
||||
</div>
|
||||
)
|
||||
@ -68,7 +68,7 @@ const Blocks = ({
|
||||
<Tooltip
|
||||
key={block.type}
|
||||
position='right'
|
||||
popupClassName='!p-0 !px-3 !py-2.5 !w-[200px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !rounded-xl !shadow-lg'
|
||||
popupClassName='w-[200px]'
|
||||
popupContent={(
|
||||
<div>
|
||||
<BlockIcon
|
||||
@ -76,21 +76,21 @@ const Blocks = ({
|
||||
className='mb-2'
|
||||
type={block.type}
|
||||
/>
|
||||
<div className='mb-1 text-sm leading-5 text-gray-900'>{block.title}</div>
|
||||
<div className='text-xs text-gray-700 leading-[18px]'>{nodesExtraData[block.type].about}</div>
|
||||
<div className='mb-1 system-md-medium text-text-primary'>{block.title}</div>
|
||||
<div className='text-text-tertiary system-xs-regular'>{nodesExtraData[block.type].about}</div>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<div
|
||||
key={block.type}
|
||||
className='flex items-center px-3 w-full h-8 rounded-lg hover:bg-gray-50 cursor-pointer'
|
||||
className='flex items-center px-3 w-full h-8 rounded-lg hover:bg-state-base-hover cursor-pointer'
|
||||
onClick={() => onSelect(block.type)}
|
||||
>
|
||||
<BlockIcon
|
||||
className='mr-2 shrink-0'
|
||||
type={block.type}
|
||||
/>
|
||||
<div className='text-sm text-gray-900'>{block.title}</div>
|
||||
<div className='text-sm text-text-secondary'>{block.title}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
))
|
||||
@ -103,7 +103,7 @@ const Blocks = ({
|
||||
<div className='p-1'>
|
||||
{
|
||||
isEmpty && (
|
||||
<div className='flex items-center px-3 h-[22px] text-xs font-medium text-gray-500'>{t('workflow.tabs.noResult')}</div>
|
||||
<div className='flex items-center px-3 h-[22px] text-xs font-medium text-text-tertiary'>{t('workflow.tabs.noResult')}</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
|
||||
@ -117,12 +117,12 @@ const NodeSelector: FC<NodeSelectorProps> = ({
|
||||
<div
|
||||
className={`
|
||||
flex items-center justify-center
|
||||
w-4 h-4 rounded-full bg-primary-600 cursor-pointer z-10
|
||||
w-4 h-4 rounded-full bg-components-button-primary-bg text-text-primary-on-surface hover:bg-components-button-primary-bg-hover cursor-pointer z-10
|
||||
${triggerClassName?.(open)}
|
||||
`}
|
||||
style={triggerStyle}
|
||||
>
|
||||
<Plus02 className='w-2.5 h-2.5 text-white' />
|
||||
<Plus02 className='w-2.5 h-2.5' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -36,16 +36,16 @@ const Tabs: FC<TabsProps> = ({
|
||||
<div onClick={e => e.stopPropagation()}>
|
||||
{
|
||||
!noBlocks && (
|
||||
<div className='flex items-center px-3 border-b-[0.5px] border-b-black/5'>
|
||||
<div className='flex items-center px-3 border-b-[0.5px] border-divider-subtle'>
|
||||
{
|
||||
tabs.map(tab => (
|
||||
<div
|
||||
key={tab.key}
|
||||
className={cn(
|
||||
'relative mr-4 h-[34px] text-[13px] leading-[34px] font-medium cursor-pointer',
|
||||
'relative mr-4 pt-1 pb-2 system-sm-medium cursor-pointer',
|
||||
activeTab === tab.key
|
||||
? 'text-gray-700 after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-full after:bg-primary-600'
|
||||
: 'text-gray-500',
|
||||
? 'text-text-primary after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-full after:bg-util-colors-blue-brand-blue-brand-600'
|
||||
: 'text-text-tertiary',
|
||||
)}
|
||||
onClick={() => onActiveTabChange(tab.key)}
|
||||
>
|
||||
|
||||
@ -78,7 +78,7 @@ const Blocks = ({
|
||||
<div className='p-1 max-w-[320px]'>
|
||||
{
|
||||
!tools.length && !showWorkflowEmpty && (
|
||||
<div className='flex items-center px-3 h-[22px] text-xs font-medium text-gray-500'>{t('workflow.tabs.noResult')}</div>
|
||||
<div className='flex items-center px-3 h-[22px] text-xs font-medium text-text-tertiary'>{t('workflow.tabs.noResult')}</div>
|
||||
)
|
||||
}
|
||||
{!tools.length && showWorkflowEmpty && (
|
||||
|
||||
@ -506,3 +506,5 @@ export const WORKFLOW_DATA_UPDATE = 'WORKFLOW_DATA_UPDATE'
|
||||
export const CUSTOM_NODE = 'custom'
|
||||
export const CUSTOM_EDGE = 'custom'
|
||||
export const DSL_EXPORT_CHECK = 'DSL_EXPORT_CHECK'
|
||||
export const DEFAULT_RETRY_MAX = 3
|
||||
export const DEFAULT_RETRY_INTERVAL = 100
|
||||
|
||||
@ -13,7 +13,7 @@ const EditingTitle = () => {
|
||||
const isSyncingWorkflowDraft = useStore(s => s.isSyncingWorkflowDraft)
|
||||
|
||||
return (
|
||||
<div className='flex items-center h-[18px] text-xs text-gray-500'>
|
||||
<div className='flex items-center h-[18px] system-xs-regular text-text-tertiary'>
|
||||
{
|
||||
!!draftUpdatedAt && (
|
||||
<>
|
||||
|
||||
@ -27,6 +27,7 @@ import {
|
||||
} from '../hooks'
|
||||
import AppPublisher from '../../app/app-publisher'
|
||||
import { ToastContext } from '../../base/toast'
|
||||
import Divider from '../../base/divider'
|
||||
import RunAndHistory from './run-and-history'
|
||||
import EditingTitle from './editing-title'
|
||||
import RunningTitle from './running-title'
|
||||
@ -144,15 +145,12 @@ const Header: FC = () => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className='absolute top-0 left-0 z-10 flex items-center justify-between w-full px-3 h-14'
|
||||
style={{
|
||||
background: 'linear-gradient(180deg, #F9FAFB 0%, rgba(249, 250, 251, 0.00) 100%)',
|
||||
}}
|
||||
className='absolute top-0 left-0 z-10 flex items-center justify-between w-full px-3 h-14 bg-mask-top2bottom-gray-50-to-transparent'
|
||||
>
|
||||
<div>
|
||||
{
|
||||
appSidebarExpand === 'collapse' && (
|
||||
<div className='text-xs font-medium text-gray-700'>{appDetail?.name}</div>
|
||||
<div className='system-xs-regular text-text-tertiary'>{appDetail?.name}</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
@ -171,7 +169,7 @@ const Header: FC = () => {
|
||||
{/* <GlobalVariableButton disabled={nodesReadOnly} /> */}
|
||||
{isChatMode && <ChatVariableButton disabled={nodesReadOnly} />}
|
||||
<EnvButton disabled={nodesReadOnly} />
|
||||
<div className='w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<Divider type='vertical' className='h-3.5 mx-auto' />
|
||||
<RunAndHistory />
|
||||
<Button className='text-components-button-secondary-text' onClick={handleShowFeatures}>
|
||||
<RiApps2AddLine className='w-4 h-4 mr-1 text-components-button-secondary-text' />
|
||||
@ -196,12 +194,11 @@ const Header: FC = () => {
|
||||
}
|
||||
{
|
||||
viewHistory && (
|
||||
<div className='flex items-center'>
|
||||
<div className='flex items-center space-x-2'>
|
||||
<ViewHistory withText />
|
||||
<div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<Divider type='vertical' className='h-3.5 mx-auto' />
|
||||
<Button
|
||||
variant='primary'
|
||||
className='mr-2'
|
||||
onClick={handleGoBackToEdit}
|
||||
>
|
||||
<ArrowNarrowLeft className='w-4 h-4 mr-1' />
|
||||
@ -212,14 +209,13 @@ const Header: FC = () => {
|
||||
}
|
||||
{
|
||||
restoring && (
|
||||
<div className='flex items-center'>
|
||||
<div className='flex items-center space-x-2'>
|
||||
<Button className='text-components-button-secondary-text' onClick={handleShowFeatures}>
|
||||
<RiApps2AddLine className='w-4 h-4 mr-1 text-components-button-secondary-text' />
|
||||
{t('workflow.common.features')}
|
||||
</Button>
|
||||
<div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<Divider type='vertical' className='h-3.5 mx-auto' />
|
||||
<Button
|
||||
className='mr-2'
|
||||
onClick={handleCancelRestore}
|
||||
>
|
||||
{t('common.operation.cancel')}
|
||||
|
||||
@ -7,8 +7,10 @@ import {
|
||||
} from '@remixicon/react'
|
||||
import TipPopup from '../operator/tip-popup'
|
||||
import { useWorkflowHistoryStore } from '../workflow-history-store'
|
||||
import Divider from '../../base/divider'
|
||||
import { useNodesReadOnly } from '@/app/components/workflow/hooks'
|
||||
import ViewWorkflowHistory from '@/app/components/workflow/header/view-workflow-history'
|
||||
import classNames from '@/utils/classnames'
|
||||
|
||||
export type UndoRedoProps = { handleUndo: () => void; handleRedo: () => void }
|
||||
const UndoRedo: FC<UndoRedoProps> = ({ handleUndo, handleRedo }) => {
|
||||
@ -29,36 +31,35 @@ const UndoRedo: FC<UndoRedoProps> = ({ handleUndo, handleRedo }) => {
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
|
||||
return (
|
||||
<div className='flex items-center p-0.5 rounded-lg border-[0.5px] border-gray-100 bg-white shadow-lg text-gray-500'>
|
||||
<div className='flex items-center space-x-0.5 p-0.5 backdrop-blur-[5px] rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg shadow-lg'>
|
||||
<TipPopup title={t('workflow.common.undo')!} shortcuts={['ctrl', 'z']}>
|
||||
<div
|
||||
data-tooltip-id='workflow.undo'
|
||||
className={`
|
||||
flex items-center px-1.5 w-8 h-8 rounded-md text-[13px] font-medium
|
||||
hover:bg-black/5 hover:text-gray-700 cursor-pointer select-none
|
||||
${(nodesReadOnly || buttonsDisabled.undo) && 'hover:bg-transparent opacity-50 !cursor-not-allowed'}
|
||||
`}
|
||||
className={
|
||||
classNames('flex items-center px-1.5 w-8 h-8 rounded-md system-sm-medium text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary cursor-pointer select-none',
|
||||
(nodesReadOnly || buttonsDisabled.undo)
|
||||
&& 'hover:bg-transparent text-text-disabled hover:text-text-disabled cursor-not-allowed')}
|
||||
onClick={() => !nodesReadOnly && !buttonsDisabled.undo && handleUndo()}
|
||||
>
|
||||
<RiArrowGoBackLine className='h-4 w-4' />
|
||||
</div>
|
||||
</TipPopup>
|
||||
</TipPopup >
|
||||
<TipPopup title={t('workflow.common.redo')!} shortcuts={['ctrl', 'y']}>
|
||||
<div
|
||||
data-tooltip-id='workflow.redo'
|
||||
className={`
|
||||
flex items-center px-1.5 w-8 h-8 rounded-md text-[13px] font-medium
|
||||
hover:bg-black/5 hover:text-gray-700 cursor-pointer select-none
|
||||
${(nodesReadOnly || buttonsDisabled.redo) && 'hover:bg-transparent opacity-50 !cursor-not-allowed'}
|
||||
`}
|
||||
className={
|
||||
classNames('flex items-center px-1.5 w-8 h-8 rounded-md system-sm-medium text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary cursor-pointer select-none',
|
||||
(nodesReadOnly || buttonsDisabled.redo)
|
||||
&& 'hover:bg-transparent text-text-disabled hover:text-text-disabled cursor-not-allowed',
|
||||
)}
|
||||
onClick={() => !nodesReadOnly && !buttonsDisabled.redo && handleRedo()}
|
||||
>
|
||||
<RiArrowGoForwardFill className='h-4 w-4' />
|
||||
</div>
|
||||
</TipPopup>
|
||||
<div className="mx-[3px] w-[1px] h-3.5 bg-gray-200"></div>
|
||||
<Divider type='vertical' className="h-3.5 mx-0.5" />
|
||||
<ViewWorkflowHistory />
|
||||
</div>
|
||||
</div >
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
} from '../hooks'
|
||||
import TipPopup from '../operator/tip-popup'
|
||||
import type { WorkflowHistoryState } from '../workflow-history-store'
|
||||
import Divider from '../../base/divider'
|
||||
import cn from '@/utils/classnames'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
@ -24,6 +25,7 @@ import {
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import classNames from '@/utils/classnames'
|
||||
|
||||
type ChangeHistoryEntry = {
|
||||
label: string
|
||||
@ -47,7 +49,7 @@ const ViewWorkflowHistory = () => {
|
||||
setCurrentLogItem: state.setCurrentLogItem,
|
||||
setShowMessageLogModal: state.setShowMessageLogModal,
|
||||
})))
|
||||
const reactflowStore = useStoreApi()
|
||||
const reactFlowStore = useStoreApi()
|
||||
const { store, getHistoryLabel } = useWorkflowHistory()
|
||||
|
||||
const { pastStates, futureStates, undo, redo, clear } = store.temporal.getState()
|
||||
@ -59,7 +61,7 @@ const ViewWorkflowHistory = () => {
|
||||
}, [clear])
|
||||
|
||||
const handleSetState = useCallback(({ index }: ChangeHistoryEntry) => {
|
||||
const { setEdges, setNodes } = reactflowStore.getState()
|
||||
const { setEdges, setNodes } = reactFlowStore.getState()
|
||||
const diff = currentHistoryStateIndex + index
|
||||
if (diff === 0)
|
||||
return
|
||||
@ -75,7 +77,7 @@ const ViewWorkflowHistory = () => {
|
||||
|
||||
setEdges(edges)
|
||||
setNodes(nodes)
|
||||
}, [currentHistoryStateIndex, reactflowStore, redo, store, undo])
|
||||
}, [currentHistoryStateIndex, reactFlowStore, redo, store, undo])
|
||||
|
||||
const calculateStepLabel = useCallback((index: number) => {
|
||||
if (!index)
|
||||
@ -83,8 +85,7 @@ const ViewWorkflowHistory = () => {
|
||||
|
||||
const count = index < 0 ? index * -1 : index
|
||||
return `${index > 0 ? t('workflow.changeHistory.stepForward', { count }) : t('workflow.changeHistory.stepBackward', { count })}`
|
||||
}
|
||||
, [t])
|
||||
}, [t])
|
||||
|
||||
const calculateChangeList: ChangeHistoryList = useMemo(() => {
|
||||
const filterList = (list: any, startIndex = 0, reverse = false) => list.map((state: Partial<WorkflowHistoryState>, index: number) => {
|
||||
@ -125,10 +126,11 @@ const ViewWorkflowHistory = () => {
|
||||
title={t('workflow.changeHistory.title')}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
flex items-center justify-center w-8 h-8 rounded-md hover:bg-black/5 cursor-pointer
|
||||
${open && 'bg-primary-50'} ${nodesReadOnly && 'bg-primary-50 opacity-50 !cursor-not-allowed'}
|
||||
`}
|
||||
className={
|
||||
classNames('flex items-center justify-center w-8 h-8 rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary cursor-pointer',
|
||||
open && 'bg-state-accent-active text-text-accent',
|
||||
nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled',
|
||||
)}
|
||||
onClick={() => {
|
||||
if (nodesReadOnly)
|
||||
return
|
||||
@ -136,16 +138,16 @@ const ViewWorkflowHistory = () => {
|
||||
setShowMessageLogModal(false)
|
||||
}}
|
||||
>
|
||||
<RiHistoryLine className={`w-4 h-4 hover:bg-black/5 hover:text-gray-700 ${open ? 'text-primary-600' : 'text-gray-500'}`} />
|
||||
<RiHistoryLine className='w-4 h-4' />
|
||||
</div>
|
||||
</TipPopup>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[12]'>
|
||||
<div
|
||||
className='flex flex-col ml-2 min-w-[240px] max-w-[360px] bg-white border-[0.5px] border-gray-200 shadow-xl rounded-xl overflow-y-auto'
|
||||
className='flex flex-col ml-2 min-w-[240px] max-w-[360px] bg-components-panel-bg-blur backdrop-blur-[5px] border-[0.5px] border-components-panel-border shadow-xl rounded-xl overflow-y-auto'
|
||||
>
|
||||
<div className='sticky top-0 bg-white flex items-center justify-between px-4 pt-3 text-base font-semibold text-gray-900'>
|
||||
<div className='grow'>{t('workflow.changeHistory.title')}</div>
|
||||
<div className='sticky top-0 flex items-center justify-between px-4 pt-3'>
|
||||
<div className='grow text-text-secondary system-mg-regular'>{t('workflow.changeHistory.title')}</div>
|
||||
<div
|
||||
className='shrink-0 flex items-center justify-center w-6 h-6 cursor-pointer'
|
||||
onClick={() => {
|
||||
@ -154,7 +156,7 @@ const ViewWorkflowHistory = () => {
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
<RiCloseLine className='w-4 h-4 text-gray-500' />
|
||||
<RiCloseLine className='w-4 h-4 text-text-secondary' />
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
@ -168,8 +170,8 @@ const ViewWorkflowHistory = () => {
|
||||
{
|
||||
!calculateChangeList.statesCount && (
|
||||
<div className='py-12'>
|
||||
<RiHistoryLine className='mx-auto mb-2 w-8 h-8 text-gray-300' />
|
||||
<div className='text-center text-[13px] text-gray-400'>
|
||||
<RiHistoryLine className='mx-auto mb-2 w-8 h-8 text-text-tertiary' />
|
||||
<div className='text-center text-[13px] text-text-tertiary'>
|
||||
{t('workflow.changeHistory.placeholder')}
|
||||
</div>
|
||||
</div>
|
||||
@ -181,8 +183,8 @@ const ViewWorkflowHistory = () => {
|
||||
<div
|
||||
key={item?.index}
|
||||
className={cn(
|
||||
'flex mb-0.5 px-2 py-[7px] rounded-lg hover:bg-primary-50 cursor-pointer',
|
||||
item?.index === currentHistoryStateIndex && 'bg-primary-50',
|
||||
'flex mb-0.5 px-2 py-[7px] rounded-lg hover:bg-state-base-hover text-text-secondary cursor-pointer',
|
||||
item?.index === currentHistoryStateIndex && 'bg-state-base-hover',
|
||||
)}
|
||||
onClick={() => {
|
||||
handleSetState(item)
|
||||
@ -192,8 +194,7 @@ const ViewWorkflowHistory = () => {
|
||||
<div>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center text-[13px] font-medium leading-[18px]',
|
||||
item?.index === currentHistoryStateIndex && 'text-primary-600',
|
||||
'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary',
|
||||
)}
|
||||
>
|
||||
{item?.label || t('workflow.changeHistory.sessionStart')} ({calculateStepLabel(item?.index)}{item?.index === currentHistoryStateIndex && t('workflow.changeHistory.currentState')})
|
||||
@ -207,8 +208,8 @@ const ViewWorkflowHistory = () => {
|
||||
<div
|
||||
key={item?.index}
|
||||
className={cn(
|
||||
'flex mb-0.5 px-2 py-[7px] rounded-lg hover:bg-primary-50 cursor-pointer',
|
||||
item?.index === calculateChangeList.statesCount - 1 && 'bg-primary-50',
|
||||
'flex mb-0.5 px-2 py-[7px] rounded-lg hover:bg-state-base-hover cursor-pointer',
|
||||
item?.index === calculateChangeList.statesCount - 1 && 'bg-state-base-hover',
|
||||
)}
|
||||
onClick={() => {
|
||||
handleSetState(item)
|
||||
@ -218,8 +219,7 @@ const ViewWorkflowHistory = () => {
|
||||
<div>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center text-[13px] font-medium leading-[18px]',
|
||||
item?.index === calculateChangeList.statesCount - 1 && 'text-primary-600',
|
||||
'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary',
|
||||
)}
|
||||
>
|
||||
{item?.label || t('workflow.changeHistory.sessionStart')} ({calculateStepLabel(item?.index)})
|
||||
@ -234,12 +234,12 @@ const ViewWorkflowHistory = () => {
|
||||
}
|
||||
{
|
||||
!!calculateChangeList.statesCount && (
|
||||
<>
|
||||
<div className="h-[1px] bg-gray-100" />
|
||||
<div className='px-0.5'>
|
||||
<Divider className='m-0' />
|
||||
<div
|
||||
className={cn(
|
||||
'flex my-0.5 px-2 py-[7px] rounded-lg cursor-pointer',
|
||||
'hover:bg-red-50 hover:text-red-600',
|
||||
'flex my-0.5 px-2 py-[7px] rounded-lg text-text-secondary cursor-pointer',
|
||||
'hover:bg-state-base-hover',
|
||||
)}
|
||||
onClick={() => {
|
||||
handleClearHistory()
|
||||
@ -256,12 +256,12 @@ const ViewWorkflowHistory = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className="px-3 w-[240px] py-2 text-xs text-gray-500" >
|
||||
<div className="px-3 w-[240px] py-2 text-xs text-text-tertiary" >
|
||||
<div className="flex items-center mb-1 h-[22px] font-medium uppercase">{t('workflow.changeHistory.hint')}</div>
|
||||
<div className="mb-1 text-gray-700 leading-[18px]">{t('workflow.changeHistory.hintText')}</div>
|
||||
<div className="mb-1 text-text-tertiary leading-[18px]">{t('workflow.changeHistory.hintText')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
|
||||
@ -28,6 +28,7 @@ import {
|
||||
getFilesInLogs,
|
||||
} from '@/app/components/base/file-uploader/utils'
|
||||
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
|
||||
export const useWorkflowRun = () => {
|
||||
const store = useStoreApi()
|
||||
@ -114,6 +115,7 @@ export const useWorkflowRun = () => {
|
||||
onIterationStart,
|
||||
onIterationNext,
|
||||
onIterationFinish,
|
||||
onNodeRetry,
|
||||
onError,
|
||||
...restCallback
|
||||
} = callback || {}
|
||||
@ -385,6 +387,9 @@ export const useWorkflowRun = () => {
|
||||
if (nodeIndex !== -1) {
|
||||
currIteration[nodeIndex] = {
|
||||
...currIteration[nodeIndex],
|
||||
...(currIteration[nodeIndex].retryDetail
|
||||
? { retryDetail: currIteration[nodeIndex].retryDetail }
|
||||
: {}),
|
||||
...data,
|
||||
} as any
|
||||
}
|
||||
@ -440,10 +445,13 @@ export const useWorkflowRun = () => {
|
||||
})
|
||||
if (currentIndex > -1 && draft.tracing) {
|
||||
draft.tracing[currentIndex] = {
|
||||
...data,
|
||||
...(draft.tracing[currentIndex].extras
|
||||
? { extras: draft.tracing[currentIndex].extras }
|
||||
: {}),
|
||||
...data,
|
||||
...(draft.tracing[currentIndex].retryDetail
|
||||
? { retryDetail: draft.tracing[currentIndex].retryDetail }
|
||||
: {}),
|
||||
} as any
|
||||
}
|
||||
}))
|
||||
@ -616,6 +624,89 @@ export const useWorkflowRun = () => {
|
||||
if (onIterationFinish)
|
||||
onIterationFinish(params)
|
||||
},
|
||||
onNodeRetry: (params) => {
|
||||
const { data } = params
|
||||
const {
|
||||
workflowRunningData,
|
||||
setWorkflowRunningData,
|
||||
iterParallelLogMap,
|
||||
setIterParallelLogMap,
|
||||
} = workflowStore.getState()
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
} = store.getState()
|
||||
|
||||
const nodes = getNodes()
|
||||
const currentNode = nodes.find(node => node.id === data.node_id)!
|
||||
const nodeParent = nodes.find(node => node.id === currentNode.parentId)
|
||||
if (nodeParent) {
|
||||
if (!data.execution_metadata.parallel_mode_run_id) {
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
const tracing = draft.tracing!
|
||||
const iteration = tracing.find(trace => trace.node_id === nodeParent.id)
|
||||
|
||||
if (iteration && iteration.details?.length) {
|
||||
const currentNodeRetry = iteration.details[nodeParent.data._iterationIndex - 1]?.find(item => item.node_id === data.node_id)
|
||||
|
||||
if (currentNodeRetry) {
|
||||
if (currentNodeRetry?.retryDetail)
|
||||
currentNodeRetry?.retryDetail.push(data as NodeTracing)
|
||||
else
|
||||
currentNodeRetry.retryDetail = [data as NodeTracing]
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
else {
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
const tracing = draft.tracing!
|
||||
const iteration = tracing.find(trace => trace.node_id === nodeParent.id)
|
||||
|
||||
if (iteration && iteration.details?.length) {
|
||||
const iterRunID = data.execution_metadata?.parallel_mode_run_id
|
||||
|
||||
const currIteration = iterParallelLogMap.get(iteration.node_id)?.get(iterRunID)
|
||||
const currentNodeRetry = currIteration?.find(item => item.node_id === data.node_id)
|
||||
|
||||
if (currentNodeRetry) {
|
||||
if (currentNodeRetry?.retryDetail)
|
||||
currentNodeRetry?.retryDetail.push(data as NodeTracing)
|
||||
else
|
||||
currentNodeRetry.retryDetail = [data as NodeTracing]
|
||||
}
|
||||
setIterParallelLogMap(iterParallelLogMap)
|
||||
const iterLogMap = iterParallelLogMap.get(iteration.node_id)
|
||||
if (iterLogMap)
|
||||
iteration.details = Array.from(iterLogMap.values())
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
else {
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
const tracing = draft.tracing!
|
||||
const currentRetryNodeIndex = tracing.findIndex(trace => trace.node_id === data.node_id)
|
||||
|
||||
if (currentRetryNodeIndex > -1) {
|
||||
const currentRetryNode = tracing[currentRetryNodeIndex]
|
||||
if (currentRetryNode.retryDetail)
|
||||
draft.tracing![currentRetryNodeIndex].retryDetail!.push(data as NodeTracing)
|
||||
else
|
||||
draft.tracing![currentRetryNodeIndex].retryDetail = [data as NodeTracing]
|
||||
}
|
||||
}))
|
||||
}
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
const currentNode = draft.find(node => node.id === data.node_id)!
|
||||
|
||||
currentNode.data._retryIndex = data.retry_index
|
||||
})
|
||||
setNodes(newNodes)
|
||||
|
||||
if (onNodeRetry)
|
||||
onNodeRetry(params)
|
||||
},
|
||||
onParallelBranchStarted: (params) => {
|
||||
// console.log(params, 'parallel start')
|
||||
},
|
||||
|
||||
@ -57,6 +57,7 @@ import {
|
||||
import I18n from '@/context/i18n'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants'
|
||||
import { useWorkflowConfig } from '@/service/use-workflow'
|
||||
|
||||
export const useIsChatMode = () => {
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
@ -69,7 +70,9 @@ export const useWorkflow = () => {
|
||||
const { locale } = useContext(I18n)
|
||||
const store = useStoreApi()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const appId = useStore(s => s.appId)
|
||||
const nodesExtraData = useNodesExtraData()
|
||||
const { data: workflowConfig } = useWorkflowConfig(appId)
|
||||
const setPanelWidth = useCallback((width: number) => {
|
||||
localStorage.setItem('workflow-node-panel-width', `${width}`)
|
||||
workflowStore.setState({ panelWidth: width })
|
||||
@ -336,15 +339,15 @@ export const useWorkflow = () => {
|
||||
for (let i = 0; i < parallelList.length; i++) {
|
||||
const parallel = parallelList[i]
|
||||
|
||||
if (parallel.depth > PARALLEL_DEPTH_LIMIT) {
|
||||
if (parallel.depth > (workflowConfig?.parallel_depth_limit || PARALLEL_DEPTH_LIMIT)) {
|
||||
const { setShowTips } = workflowStore.getState()
|
||||
setShowTips(t('workflow.common.parallelTip.depthLimit', { num: PARALLEL_DEPTH_LIMIT }))
|
||||
setShowTips(t('workflow.common.parallelTip.depthLimit', { num: (workflowConfig?.parallel_depth_limit || PARALLEL_DEPTH_LIMIT) }))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}, [t, workflowStore])
|
||||
}, [t, workflowStore, workflowConfig?.parallel_depth_limit])
|
||||
|
||||
const isValidConnection = useCallback(({ source, sourceHandle, target }: Connection) => {
|
||||
const {
|
||||
|
||||
@ -180,7 +180,7 @@ const Workflow: FC<WorkflowProps> = memo(({
|
||||
return () => {
|
||||
handleSyncWorkflowDraft(true, true)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const { handleRefreshWorkflowDraft } = useWorkflowUpdate()
|
||||
@ -281,7 +281,7 @@ const Workflow: FC<WorkflowProps> = memo(({
|
||||
<div
|
||||
id='workflow-container'
|
||||
className={`
|
||||
relative w-full min-w-[960px] h-full bg-[#F0F2F7]
|
||||
relative w-full min-w-[960px] h-full
|
||||
${workflowReadOnly && 'workflow-panel-animation'}
|
||||
${nodeAnimation && 'workflow-node-animation'}
|
||||
`}
|
||||
@ -373,7 +373,8 @@ const Workflow: FC<WorkflowProps> = memo(({
|
||||
<Background
|
||||
gap={[14, 14]}
|
||||
size={2}
|
||||
color='#E4E5E7'
|
||||
className="bg-workflow-canvas-workflow-bg"
|
||||
color='var(--color-workflow-canvas-workflow-dot-color)'
|
||||
/>
|
||||
</ReactFlow>
|
||||
</div>
|
||||
@ -403,7 +404,7 @@ const WorkflowWrap = memo(() => {
|
||||
|
||||
if (!data || isLoading) {
|
||||
return (
|
||||
<div className='flex justify-center items-center relative w-full h-full bg-[#F0F2F7]'>
|
||||
<div className='flex justify-center items-center relative w-full h-full'>
|
||||
<Loading />
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -17,17 +17,25 @@ import ResultPanel from '@/app/components/workflow/run/result-panel'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { getProcessedFiles } from '@/app/components/base/file-uploader/utils'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
import RetryResultPanel from '@/app/components/workflow/run/retry-result-panel'
|
||||
import type { BlockEnum } from '@/app/components/workflow/types'
|
||||
import type { Emoji } from '@/app/components/tools/types'
|
||||
|
||||
const i18nPrefix = 'workflow.singleRun'
|
||||
|
||||
type BeforeRunFormProps = {
|
||||
nodeName: string
|
||||
nodeType?: BlockEnum
|
||||
toolIcon?: string | Emoji
|
||||
onHide: () => void
|
||||
onRun: (submitData: Record<string, any>) => void
|
||||
onStop: () => void
|
||||
runningStatus: NodeRunningStatus
|
||||
result?: JSX.Element
|
||||
forms: FormProps[]
|
||||
retryDetails?: NodeTracing[]
|
||||
onRetryDetailBack?: any
|
||||
}
|
||||
|
||||
function formatValue(value: string | any, type: InputVarType) {
|
||||
@ -50,12 +58,16 @@ function formatValue(value: string | any, type: InputVarType) {
|
||||
}
|
||||
const BeforeRunForm: FC<BeforeRunFormProps> = ({
|
||||
nodeName,
|
||||
nodeType,
|
||||
toolIcon,
|
||||
onHide,
|
||||
onRun,
|
||||
onStop,
|
||||
runningStatus,
|
||||
result,
|
||||
forms,
|
||||
retryDetails,
|
||||
onRetryDetailBack = () => { },
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@ -122,48 +134,69 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
|
||||
<div className='text-base font-semibold text-gray-900 truncate'>
|
||||
{t(`${i18nPrefix}.testRun`)} {nodeName}
|
||||
</div>
|
||||
<div className='ml-2 shrink-0 p-1 cursor-pointer' onClick={onHide}>
|
||||
<div className='ml-2 shrink-0 p-1 cursor-pointer' onClick={() => {
|
||||
onHide()
|
||||
}}>
|
||||
<RiCloseLine className='w-4 h-4 text-gray-500 ' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='h-0 grow overflow-y-auto pb-4'>
|
||||
<div className='mt-3 px-4 space-y-4'>
|
||||
{forms.map((form, index) => (
|
||||
<div key={index}>
|
||||
<Form
|
||||
key={index}
|
||||
className={cn(index < forms.length - 1 && 'mb-4')}
|
||||
{...form}
|
||||
/>
|
||||
{index < forms.length - 1 && <Split />}
|
||||
{
|
||||
retryDetails?.length && (
|
||||
<div className='h-0 grow overflow-y-auto pb-4'>
|
||||
<RetryResultPanel
|
||||
list={retryDetails.map((item, index) => ({
|
||||
...item,
|
||||
title: `${t('workflow.nodes.common.retry.retry')} ${index + 1}`,
|
||||
node_type: nodeType!,
|
||||
extras: {
|
||||
icon: toolIcon!,
|
||||
},
|
||||
}))}
|
||||
onBack={onRetryDetailBack}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!retryDetails?.length && (
|
||||
<div className='h-0 grow overflow-y-auto pb-4'>
|
||||
<div className='mt-3 px-4 space-y-4'>
|
||||
{forms.map((form, index) => (
|
||||
<div key={index}>
|
||||
<Form
|
||||
key={index}
|
||||
className={cn(index < forms.length - 1 && 'mb-4')}
|
||||
{...form}
|
||||
/>
|
||||
{index < forms.length - 1 && <Split />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className='mt-4 flex justify-between space-x-2 px-4' >
|
||||
{isRunning && (
|
||||
<div
|
||||
className='p-2 rounded-lg border border-gray-200 bg-white shadow-xs cursor-pointer'
|
||||
onClick={onStop}
|
||||
>
|
||||
<StopCircle className='w-4 h-4 text-gray-500' />
|
||||
<div className='mt-4 flex justify-between space-x-2 px-4' >
|
||||
{isRunning && (
|
||||
<div
|
||||
className='p-2 rounded-lg border border-gray-200 bg-white shadow-xs cursor-pointer'
|
||||
onClick={onStop}
|
||||
>
|
||||
<StopCircle className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
)}
|
||||
<Button disabled={!isFileLoaded || isRunning} variant='primary' className='w-0 grow space-x-2' onClick={handleRun}>
|
||||
{isRunning && <RiLoader2Line className='animate-spin w-4 h-4 text-white' />}
|
||||
<div>{t(`${i18nPrefix}.${isRunning ? 'running' : 'startRun'}`)}</div>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<Button disabled={!isFileLoaded || isRunning} variant='primary' className='w-0 grow space-x-2' onClick={handleRun}>
|
||||
{isRunning && <RiLoader2Line className='animate-spin w-4 h-4 text-white' />}
|
||||
<div>{t(`${i18nPrefix}.${isRunning ? 'running' : 'startRun'}`)}</div>
|
||||
</Button>
|
||||
</div>
|
||||
{isRunning && (
|
||||
<ResultPanel status='running' showSteps={false} />
|
||||
)}
|
||||
{isFinished && (
|
||||
<>
|
||||
{result}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{isRunning && (
|
||||
<ResultPanel status='running' showSteps={false} />
|
||||
)}
|
||||
{isFinished && (
|
||||
<>
|
||||
{result}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -14,7 +14,6 @@ import type {
|
||||
CommonNodeType,
|
||||
Node,
|
||||
} from '@/app/components/workflow/types'
|
||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
||||
type ErrorHandleProps = Pick<Node, 'id' | 'data'>
|
||||
@ -45,7 +44,6 @@ const ErrorHandle = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Split />
|
||||
<div className='py-4'>
|
||||
<Collapse
|
||||
disabled={!error_strategy}
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
import {
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import type { WorkflowRetryConfig } from './types'
|
||||
import {
|
||||
useNodeDataUpdate,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
|
||||
export const useRetryConfig = (
|
||||
id: string,
|
||||
) => {
|
||||
const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate()
|
||||
|
||||
const handleRetryConfigChange = useCallback((value?: WorkflowRetryConfig) => {
|
||||
handleNodeDataUpdateWithSyncDraft({
|
||||
id,
|
||||
data: {
|
||||
retry_config: value,
|
||||
},
|
||||
})
|
||||
}, [id, handleNodeDataUpdateWithSyncDraft])
|
||||
|
||||
return {
|
||||
handleRetryConfigChange,
|
||||
}
|
||||
}
|
||||
|
||||
export const useRetryDetailShowInSingleRun = () => {
|
||||
const [retryDetails, setRetryDetails] = useState<NodeTracing[] | undefined>()
|
||||
|
||||
const handleRetryDetailsChange = useCallback((details: NodeTracing[] | undefined) => {
|
||||
setRetryDetails(details)
|
||||
}, [])
|
||||
|
||||
return {
|
||||
retryDetails,
|
||||
handleRetryDetailsChange,
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiAlertFill,
|
||||
RiCheckboxCircleFill,
|
||||
RiLoader2Line,
|
||||
} from '@remixicon/react'
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
import { NodeRunningStatus } from '@/app/components/workflow/types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type RetryOnNodeProps = Pick<Node, 'id' | 'data'>
|
||||
const RetryOnNode = ({
|
||||
data,
|
||||
}: RetryOnNodeProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { retry_config } = data
|
||||
const showSelectedBorder = data.selected || data._isBundled || data._isEntering
|
||||
const {
|
||||
isRunning,
|
||||
isSuccessful,
|
||||
isException,
|
||||
isFailed,
|
||||
} = useMemo(() => {
|
||||
return {
|
||||
isRunning: data._runningStatus === NodeRunningStatus.Running && !showSelectedBorder,
|
||||
isSuccessful: data._runningStatus === NodeRunningStatus.Succeeded && !showSelectedBorder,
|
||||
isFailed: data._runningStatus === NodeRunningStatus.Failed && !showSelectedBorder,
|
||||
isException: data._runningStatus === NodeRunningStatus.Exception && !showSelectedBorder,
|
||||
}
|
||||
}, [data._runningStatus, showSelectedBorder])
|
||||
const showDefault = !isRunning && !isSuccessful && !isException && !isFailed
|
||||
|
||||
if (!retry_config?.retry_enabled)
|
||||
return null
|
||||
|
||||
if (!showDefault && !data._retryIndex)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className='px-3'>
|
||||
<div className={cn(
|
||||
'flex items-center justify-between px-[5px] py-1 bg-workflow-block-parma-bg border-[0.5px] border-transparent rounded-md system-xs-medium-uppercase text-text-tertiary',
|
||||
isRunning && 'bg-state-accent-hover border-state-accent-active text-text-accent',
|
||||
isSuccessful && 'bg-state-success-hover border-state-success-active text-text-success',
|
||||
(isException || isFailed) && 'bg-state-warning-hover border-state-warning-active text-text-warning',
|
||||
)}>
|
||||
<div className='flex items-center'>
|
||||
{
|
||||
showDefault && (
|
||||
t('workflow.nodes.common.retry.retryTimes', { times: retry_config.max_retries })
|
||||
)
|
||||
}
|
||||
{
|
||||
isRunning && (
|
||||
<>
|
||||
<RiLoader2Line className='animate-spin mr-1 w-3.5 h-3.5' />
|
||||
{t('workflow.nodes.common.retry.retrying')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
isSuccessful && (
|
||||
<>
|
||||
<RiCheckboxCircleFill className='mr-1 w-3.5 h-3.5' />
|
||||
{t('workflow.nodes.common.retry.retrySuccessful')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
(isFailed || isException) && (
|
||||
<>
|
||||
<RiAlertFill className='mr-1 w-3.5 h-3.5' />
|
||||
{t('workflow.nodes.common.retry.retryFailed')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
!showDefault && !!data._retryIndex && (
|
||||
<div>
|
||||
{data._retryIndex}/{data.retry_config?.max_retries}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RetryOnNode
|
||||
@ -0,0 +1,117 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useRetryConfig } from './hooks'
|
||||
import s from './style.module.css'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Slider from '@/app/components/base/slider'
|
||||
import Input from '@/app/components/base/input'
|
||||
import type {
|
||||
Node,
|
||||
} from '@/app/components/workflow/types'
|
||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
|
||||
type RetryOnPanelProps = Pick<Node, 'id' | 'data'>
|
||||
const RetryOnPanel = ({
|
||||
id,
|
||||
data,
|
||||
}: RetryOnPanelProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleRetryConfigChange } = useRetryConfig(id)
|
||||
const { retry_config } = data
|
||||
|
||||
const handleRetryEnabledChange = (value: boolean) => {
|
||||
handleRetryConfigChange({
|
||||
retry_enabled: value,
|
||||
max_retries: retry_config?.max_retries || 3,
|
||||
retry_interval: retry_config?.retry_interval || 1000,
|
||||
})
|
||||
}
|
||||
|
||||
const handleMaxRetriesChange = (value: number) => {
|
||||
if (value > 10)
|
||||
value = 10
|
||||
else if (value < 1)
|
||||
value = 1
|
||||
handleRetryConfigChange({
|
||||
retry_enabled: true,
|
||||
max_retries: value,
|
||||
retry_interval: retry_config?.retry_interval || 1000,
|
||||
})
|
||||
}
|
||||
|
||||
const handleRetryIntervalChange = (value: number) => {
|
||||
if (value > 5000)
|
||||
value = 5000
|
||||
else if (value < 100)
|
||||
value = 100
|
||||
handleRetryConfigChange({
|
||||
retry_enabled: true,
|
||||
max_retries: retry_config?.max_retries || 3,
|
||||
retry_interval: value,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='pt-2'>
|
||||
<div className='flex items-center justify-between px-4 py-2 h-10'>
|
||||
<div className='flex items-center'>
|
||||
<div className='mr-0.5 system-sm-semibold-uppercase text-text-secondary'>{t('workflow.nodes.common.retry.retryOnFailure')}</div>
|
||||
</div>
|
||||
<Switch
|
||||
defaultValue={retry_config?.retry_enabled}
|
||||
onChange={v => handleRetryEnabledChange(v)}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
retry_config?.retry_enabled && (
|
||||
<div className='px-4 pb-2'>
|
||||
<div className='flex items-center mb-1 w-full'>
|
||||
<div className='grow mr-2 system-xs-medium-uppercase'>{t('workflow.nodes.common.retry.maxRetries')}</div>
|
||||
<Slider
|
||||
className='mr-3 w-[108px]'
|
||||
value={retry_config?.max_retries || 3}
|
||||
onChange={handleMaxRetriesChange}
|
||||
min={1}
|
||||
max={10}
|
||||
/>
|
||||
<Input
|
||||
type='number'
|
||||
wrapperClassName='w-[80px]'
|
||||
value={retry_config?.max_retries || 3}
|
||||
onChange={e => handleMaxRetriesChange(e.target.value as any)}
|
||||
min={1}
|
||||
max={10}
|
||||
unit={t('workflow.nodes.common.retry.times') || ''}
|
||||
className={s.input}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
<div className='grow mr-2 system-xs-medium-uppercase'>{t('workflow.nodes.common.retry.retryInterval')}</div>
|
||||
<Slider
|
||||
className='mr-3 w-[108px]'
|
||||
value={retry_config?.retry_interval || 1000}
|
||||
onChange={handleRetryIntervalChange}
|
||||
min={100}
|
||||
max={5000}
|
||||
/>
|
||||
<Input
|
||||
type='number'
|
||||
wrapperClassName='w-[80px]'
|
||||
value={retry_config?.retry_interval || 1000}
|
||||
onChange={e => handleRetryIntervalChange(e.target.value as any)}
|
||||
min={100}
|
||||
max={5000}
|
||||
unit={t('workflow.nodes.common.retry.ms') || ''}
|
||||
className={s.input}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<Split className='mx-4 mt-2' />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default RetryOnPanel
|
||||
@ -0,0 +1,5 @@
|
||||
.input::-webkit-inner-spin-button,
|
||||
.input::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
export type WorkflowRetryConfig = {
|
||||
max_retries: number
|
||||
retry_interval: number
|
||||
retry_enabled: boolean
|
||||
}
|
||||
@ -25,7 +25,10 @@ import {
|
||||
useNodesReadOnly,
|
||||
useToolIcon,
|
||||
} from '../../hooks'
|
||||
import { hasErrorHandleNode } from '../../utils'
|
||||
import {
|
||||
hasErrorHandleNode,
|
||||
hasRetryNode,
|
||||
} from '../../utils'
|
||||
import { useNodeIterationInteractions } from '../iteration/use-interactions'
|
||||
import type { IterationNodeType } from '../iteration/types'
|
||||
import {
|
||||
@ -35,6 +38,7 @@ import {
|
||||
import NodeResizer from './components/node-resizer'
|
||||
import NodeControl from './components/node-control'
|
||||
import ErrorHandleOnNode from './components/error-handle/error-handle-on-node'
|
||||
import RetryOnNode from './components/retry/retry-on-node'
|
||||
import AddVariablePopupWithPosition from './components/add-variable-popup-with-position'
|
||||
import cn from '@/utils/classnames'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
@ -237,6 +241,14 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
hasRetryNode(data.type) && (
|
||||
<RetryOnNode
|
||||
id={id}
|
||||
data={data}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
hasErrorHandleNode(data.type) && (
|
||||
<ErrorHandleOnNode
|
||||
|
||||
@ -21,9 +21,11 @@ import {
|
||||
TitleInput,
|
||||
} from './components/title-description-input'
|
||||
import ErrorHandleOnPanel from './components/error-handle/error-handle-on-panel'
|
||||
import RetryOnPanel from './components/retry/retry-on-panel'
|
||||
import { useResizePanel } from './hooks/use-resize-panel'
|
||||
import cn from '@/utils/classnames'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
import {
|
||||
WorkflowHistoryEvent,
|
||||
useAvailableBlocks,
|
||||
@ -38,6 +40,7 @@ import {
|
||||
import {
|
||||
canRunBySingle,
|
||||
hasErrorHandleNode,
|
||||
hasRetryNode,
|
||||
} from '@/app/components/workflow/utils'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
@ -58,7 +61,7 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
showMessageLogModal: state.showMessageLogModal,
|
||||
})))
|
||||
const showSingleRunPanel = useStore(s => s.showSingleRunPanel)
|
||||
const panelWidth = localStorage.getItem('workflow-node-panel-width') ? parseFloat(localStorage.getItem('workflow-node-panel-width')!) : 420
|
||||
const panelWidth = localStorage.getItem('workflow-node-panel-width') ? Number.parseFloat(localStorage.getItem('workflow-node-panel-width')!) : 420
|
||||
const {
|
||||
setPanelWidth,
|
||||
} = useWorkflow()
|
||||
@ -168,6 +171,15 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
<div>
|
||||
{cloneElement(children, { id, data })}
|
||||
</div>
|
||||
<Split />
|
||||
{
|
||||
hasRetryNode(data.type) && (
|
||||
<RetryOnPanel
|
||||
id={id}
|
||||
data={data}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
hasErrorHandleNode(data.type) && (
|
||||
<ErrorHandleOnPanel
|
||||
|
||||
@ -2,7 +2,10 @@ import { BlockEnum } from '../../types'
|
||||
import type { NodeDefault } from '../../types'
|
||||
import { AuthorizationType, BodyType, Method } from './types'
|
||||
import type { BodyPayload, HttpNodeType } from './types'
|
||||
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/constants'
|
||||
import {
|
||||
ALL_CHAT_AVAILABLE_BLOCKS,
|
||||
ALL_COMPLETION_AVAILABLE_BLOCKS,
|
||||
} from '@/app/components/workflow/constants'
|
||||
|
||||
const nodeDefault: NodeDefault<HttpNodeType> = {
|
||||
defaultValue: {
|
||||
@ -24,6 +27,11 @@ const nodeDefault: NodeDefault<HttpNodeType> = {
|
||||
max_read_timeout: 0,
|
||||
max_write_timeout: 0,
|
||||
},
|
||||
retry_config: {
|
||||
retry_enabled: true,
|
||||
max_retries: 3,
|
||||
retry_interval: 100,
|
||||
},
|
||||
},
|
||||
getAvailablePrevNodes(isChatMode: boolean) {
|
||||
const nodes = isChatMode
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useConfig from './use-config'
|
||||
import ApiInput from './components/api-input'
|
||||
@ -18,6 +18,7 @@ import { FileArrow01 } from '@/app/components/base/icons/src/vender/line/files'
|
||||
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
||||
import { useRetryDetailShowInSingleRun } from '@/app/components/workflow/nodes/_base/components/retry/hooks'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.http'
|
||||
|
||||
@ -60,6 +61,10 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||
hideCurlPanel,
|
||||
handleCurlImport,
|
||||
} = useConfig(id, data)
|
||||
const {
|
||||
retryDetails,
|
||||
handleRetryDetailsChange,
|
||||
} = useRetryDetailShowInSingleRun()
|
||||
// To prevent prompt editor in body not update data.
|
||||
if (!isDataReady)
|
||||
return null
|
||||
@ -181,6 +186,7 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||
{isShowSingleRun && (
|
||||
<BeforeRunForm
|
||||
nodeName={inputs.title}
|
||||
nodeType={inputs.type}
|
||||
onHide={hideSingleRun}
|
||||
forms={[
|
||||
{
|
||||
@ -192,7 +198,9 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||
runningStatus={runningStatus}
|
||||
onRun={handleRun}
|
||||
onStop={handleStop}
|
||||
result={<ResultPanel {...runResult} showSteps={false} />}
|
||||
retryDetails={retryDetails}
|
||||
onRetryDetailBack={handleRetryDetailsChange}
|
||||
result={<ResultPanel {...runResult} showSteps={false} onShowRetryDetail={handleRetryDetailsChange} />}
|
||||
/>
|
||||
)}
|
||||
{(isShowCurlPanel && !readOnly) && (
|
||||
@ -207,4 +215,4 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Panel)
|
||||
export default memo(Panel)
|
||||
|
||||
@ -129,9 +129,6 @@ export const getMultipleRetrievalConfig = (
|
||||
reranking_enable: ((allInternal && allEconomic) || allExternal) ? reranking_enable : true,
|
||||
}
|
||||
|
||||
if (!rerankModelIsValid)
|
||||
result.reranking_model = undefined
|
||||
|
||||
const setDefaultWeights = () => {
|
||||
result.weights = {
|
||||
vector_setting: {
|
||||
@ -198,7 +195,6 @@ export const getMultipleRetrievalConfig = (
|
||||
setDefaultWeights()
|
||||
}
|
||||
}
|
||||
|
||||
if (reranking_mode === RerankingModeEnum.RerankingModel && !rerankModelIsValid && shouldSetWeightDefaultValue) {
|
||||
result.reranking_mode = RerankingModeEnum.WeightedScore
|
||||
setDefaultWeights()
|
||||
|
||||
@ -19,6 +19,7 @@ import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/c
|
||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
|
||||
import { useRetryDetailShowInSingleRun } from '@/app/components/workflow/nodes/_base/components/retry/hooks'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.llm'
|
||||
|
||||
@ -69,6 +70,10 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
runResult,
|
||||
filterJinjia2InputVar,
|
||||
} = useConfig(id, data)
|
||||
const {
|
||||
retryDetails,
|
||||
handleRetryDetailsChange,
|
||||
} = useRetryDetailShowInSingleRun()
|
||||
|
||||
const model = inputs.model
|
||||
|
||||
@ -282,12 +287,15 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
{isShowSingleRun && (
|
||||
<BeforeRunForm
|
||||
nodeName={inputs.title}
|
||||
nodeType={inputs.type}
|
||||
onHide={hideSingleRun}
|
||||
forms={singleRunForms}
|
||||
runningStatus={runningStatus}
|
||||
onRun={handleRun}
|
||||
onStop={handleStop}
|
||||
result={<ResultPanel {...runResult} showSteps={false} />}
|
||||
retryDetails={retryDetails}
|
||||
onRetryDetailBack={handleRetryDetailsChange}
|
||||
result={<ResultPanel {...runResult} showSteps={false} onShowRetryDetail={handleRetryDetailsChange} />}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -207,7 +207,7 @@ const InputVarList: FC<Props> = ({
|
||||
readonly={readOnly}
|
||||
isShowNodeName
|
||||
nodeId={nodeId}
|
||||
value={varInput?.type === VarKindType.constant ? (varInput?.value || '') : (varInput?.value || [])}
|
||||
value={varInput?.type === VarKindType.constant ? (varInput?.value ?? '') : (varInput?.value ?? [])}
|
||||
onChange={handleNotMixedTypeChange(variable)}
|
||||
onOpen={handleOpen(index)}
|
||||
defaultVarKindType={varInput?.type || (isNumber ? VarKindType.constant : VarKindType.variable)}
|
||||
|
||||
@ -14,6 +14,8 @@ import Loading from '@/app/components/base/loading'
|
||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
||||
import { useRetryDetailShowInSingleRun } from '@/app/components/workflow/nodes/_base/components/retry/hooks'
|
||||
import { useToolIcon } from '@/app/components/workflow/hooks'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.tool'
|
||||
|
||||
@ -48,6 +50,11 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
||||
handleStop,
|
||||
runResult,
|
||||
} = useConfig(id, data)
|
||||
const toolIcon = useToolIcon(data)
|
||||
const {
|
||||
retryDetails,
|
||||
handleRetryDetailsChange,
|
||||
} = useRetryDetailShowInSingleRun()
|
||||
|
||||
if (isLoading) {
|
||||
return <div className='flex h-[200px] items-center justify-center'>
|
||||
@ -143,12 +150,16 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
||||
{isShowSingleRun && (
|
||||
<BeforeRunForm
|
||||
nodeName={inputs.title}
|
||||
nodeType={inputs.type}
|
||||
toolIcon={toolIcon}
|
||||
onHide={hideSingleRun}
|
||||
forms={singleRunForms}
|
||||
runningStatus={runningStatus}
|
||||
onRun={handleRun}
|
||||
onStop={handleStop}
|
||||
result={<ResultPanel {...runResult} showSteps={false} />}
|
||||
retryDetails={retryDetails}
|
||||
onRetryDetailBack={handleRetryDetailsChange}
|
||||
result={<ResultPanel {...runResult} showSteps={false} onShowRetryDetail={handleRetryDetailsChange} />}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -78,9 +78,9 @@ const AddBlock = ({
|
||||
title={t('workflow.common.addBlock')}
|
||||
>
|
||||
<div className={cn(
|
||||
'flex items-center justify-center w-8 h-8 rounded-lg hover:bg-black/5 hover:text-gray-700 cursor-pointer',
|
||||
`${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
|
||||
open && '!bg-black/5',
|
||||
'flex items-center justify-center w-8 h-8 rounded-lg text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary cursor-pointer',
|
||||
`${nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`,
|
||||
open && 'bg-state-accent-active text-text-accent',
|
||||
)}>
|
||||
<RiAddCircleFill className='w-4 h-4' />
|
||||
</div>
|
||||
|
||||
@ -18,6 +18,7 @@ import {
|
||||
ControlMode,
|
||||
} from '../types'
|
||||
import { useStore } from '../store'
|
||||
import Divider from '../../base/divider'
|
||||
import AddBlock from './add-block'
|
||||
import TipPopup from './tip-popup'
|
||||
import { useOperator } from './hooks'
|
||||
@ -43,26 +44,26 @@ const Control = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex items-center p-0.5 rounded-lg border-[0.5px] border-gray-100 bg-white shadow-lg text-gray-500'>
|
||||
<div className='flex items-center p-0.5 rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg shadow-lg text-text-tertiary'>
|
||||
<AddBlock />
|
||||
<TipPopup title={t('workflow.nodes.note.addNote')}>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-center ml-[1px] w-8 h-8 rounded-lg hover:bg-black/5 hover:text-gray-700 cursor-pointer',
|
||||
`${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
|
||||
'flex items-center justify-center ml-[1px] w-8 h-8 rounded-lg hover:bg-state-base-hover hover:text-text-secondary cursor-pointer',
|
||||
`${nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`,
|
||||
)}
|
||||
onClick={addNote}
|
||||
>
|
||||
<RiStickyNoteAddLine className='w-4 h-4' />
|
||||
</div>
|
||||
</TipPopup>
|
||||
<div className='mx-[3px] w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<Divider type='vertical' className='h-3.5 mx-0.5' />
|
||||
<TipPopup title={t('workflow.common.pointerMode')} shortcuts={['v']}>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-center mr-[1px] w-8 h-8 rounded-lg cursor-pointer',
|
||||
controlMode === ControlMode.Pointer ? 'bg-primary-50 text-primary-600' : 'hover:bg-black/5 hover:text-gray-700',
|
||||
`${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
|
||||
controlMode === ControlMode.Pointer ? 'bg-state-accent-active text-text-accent' : 'hover:bg-state-base-hover hover:text-text-secondary',
|
||||
`${nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`,
|
||||
)}
|
||||
onClick={handleModePointer}
|
||||
>
|
||||
@ -73,20 +74,20 @@ const Control = () => {
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer',
|
||||
controlMode === ControlMode.Hand ? 'bg-primary-50 text-primary-600' : 'hover:bg-black/5 hover:text-gray-700',
|
||||
`${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
|
||||
controlMode === ControlMode.Hand ? 'bg-state-accent-active text-text-accent' : 'hover:bg-state-base-hover hover:text-text-secondary',
|
||||
`${nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`,
|
||||
)}
|
||||
onClick={handleModeHand}
|
||||
>
|
||||
<RiHand className='w-4 h-4' />
|
||||
</div>
|
||||
</TipPopup>
|
||||
<div className='mx-[3px] w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<Divider type='vertical' className='h-3.5 mx-0.5' />
|
||||
<TipPopup title={t('workflow.panel.organizeBlocks')} shortcuts={['ctrl', 'o']}>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-center w-8 h-8 rounded-lg hover:bg-black/5 hover:text-gray-700 cursor-pointer',
|
||||
`${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
|
||||
'flex items-center justify-center w-8 h-8 rounded-lg hover:bg-state-base-hover hover:text-text-secondary cursor-pointer',
|
||||
`${nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`,
|
||||
)}
|
||||
onClick={handleLayout}
|
||||
>
|
||||
|
||||
@ -17,7 +17,9 @@ const Operator = ({ handleUndo, handleRedo }: OperatorProps) => {
|
||||
width: 102,
|
||||
height: 72,
|
||||
}}
|
||||
className='!absolute !left-4 !bottom-14 z-[9] !m-0 !w-[102px] !h-[72px] !border-[0.5px] !border-black/8 !rounded-lg !shadow-lg'
|
||||
maskColor='var(--color-shadow-shadow-5)'
|
||||
className='!absolute !left-4 !bottom-14 z-[9] !m-0 !w-[102px] !h-[72px] !border-[0.5px] !border-divider-subtle
|
||||
!rounded-lg !shadow-md !shadow-shadow-shadow-5 !bg-workflow-minimap-bg'
|
||||
/>
|
||||
<div className='flex items-center mt-1 gap-2 absolute left-4 bottom-4 z-[9]'>
|
||||
<ZoomInOut />
|
||||
|
||||
@ -15,12 +15,12 @@ const TipPopup = ({
|
||||
return (
|
||||
<Tooltip
|
||||
offset={4}
|
||||
popupClassName='!p-0 !bg-gray-25'
|
||||
popupClassName='p-0 bg-transparent'
|
||||
popupContent={
|
||||
<div className='flex items-center gap-1 px-2 h-6 text-xs font-medium text-gray-700 rounded-lg border-[0.5px] border-black/5'>
|
||||
{title}
|
||||
<div className='flex items-center gap-1 p-1.5 backdrop-blur-[5px] shadow-lg rounded-lg border-[0.5px] border-components-panel-border bg-components-tooltip-bg'>
|
||||
<span className='system-xs-medium text-text-secondary'>{title}</span>
|
||||
{
|
||||
shortcuts && <ShortcutsName keys={shortcuts} className='!text-[11px]' />
|
||||
shortcuts && <ShortcutsName keys={shortcuts} />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -18,10 +18,9 @@ import {
|
||||
useNodesSyncDraft,
|
||||
useWorkflowReadOnly,
|
||||
} from '../hooks'
|
||||
import {
|
||||
getKeyboardKeyNameBySystem,
|
||||
} from '../utils'
|
||||
|
||||
import ShortcutsName from '../shortcuts-name'
|
||||
import Divider from '../../base/divider'
|
||||
import TipPopup from './tip-popup'
|
||||
import cn from '@/utils/classnames'
|
||||
import {
|
||||
@ -132,53 +131,54 @@ const ZoomInOut: FC = () => {
|
||||
>
|
||||
<PortalToFollowElemTrigger asChild onClick={handleTrigger}>
|
||||
<div className={`
|
||||
p-0.5 h-9 cursor-pointer text-[13px] text-gray-500 font-medium rounded-lg bg-white shadow-lg border-[0.5px] border-gray-100
|
||||
p-0.5 h-9 cursor-pointer text-[13px] backdrop-blur-[5px] rounded-lg
|
||||
bg-components-actionbar-bg shadow-lg border-[0.5px] border-components-actionbar-border
|
||||
hover:bg-state-base-hover
|
||||
${workflowReadOnly && '!cursor-not-allowed opacity-50'}
|
||||
`}>
|
||||
<div className={cn(
|
||||
'flex items-center justify-between w-[98px] h-8 hover:bg-gray-50 rounded-lg',
|
||||
open && 'bg-gray-50',
|
||||
'flex items-center justify-between w-[98px] h-8 rounded-lg',
|
||||
)}>
|
||||
<TipPopup
|
||||
title={t('workflow.operator.zoomOut')}
|
||||
shortcuts={['ctrl', '-']}
|
||||
>
|
||||
<div
|
||||
className='flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer hover:bg-black/5'
|
||||
className='flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
zoomOut()
|
||||
}}
|
||||
>
|
||||
<RiZoomOutLine className='w-4 h-4' />
|
||||
<RiZoomOutLine className='w-4 h-4 text-text-tertiary hover:text-text-secondary' />
|
||||
</div>
|
||||
</TipPopup>
|
||||
<div className='w-[34px]'>{parseFloat(`${zoom * 100}`).toFixed(0)}%</div>
|
||||
<div className={cn('w-[34px] system-sm-medium text-text-tertiary hover:text-text-secondary')}>{Number.parseFloat(`${zoom * 100}`).toFixed(0)}%</div>
|
||||
<TipPopup
|
||||
title={t('workflow.operator.zoomIn')}
|
||||
shortcuts={['ctrl', '+']}
|
||||
>
|
||||
<div
|
||||
className='flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer hover:bg-black/5'
|
||||
className='flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
zoomIn()
|
||||
}}
|
||||
>
|
||||
<RiZoomInLine className='w-4 h-4' />
|
||||
<RiZoomInLine className='w-4 h-4 text-text-tertiary hover:text-text-secondary' />
|
||||
</div>
|
||||
</TipPopup>
|
||||
</div>
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-10'>
|
||||
<div className='w-[145px] rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg'>
|
||||
<div className='w-[145px] backdrop-blur-[5px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'>
|
||||
{
|
||||
ZOOM_IN_OUT_OPTIONS.map((options, i) => (
|
||||
<Fragment key={i}>
|
||||
{
|
||||
i !== 0 && (
|
||||
<div className='h-[1px] bg-gray-100' />
|
||||
<Divider className='m-0' />
|
||||
)
|
||||
}
|
||||
<div className='p-1'>
|
||||
@ -186,25 +186,27 @@ const ZoomInOut: FC = () => {
|
||||
options.map(option => (
|
||||
<div
|
||||
key={option.key}
|
||||
className='flex items-center justify-between px-3 h-8 rounded-lg hover:bg-gray-50 cursor-pointer text-sm text-gray-700'
|
||||
className='flex items-center justify-between space-x-1 py-1.5 pl-3 pr-2 h-8 rounded-lg hover:bg-state-base-hover cursor-pointer system-md-regular text-text-secondary'
|
||||
onClick={() => handleZoom(option.key)}
|
||||
>
|
||||
{option.text}
|
||||
{
|
||||
option.key === ZoomType.zoomToFit && (
|
||||
<ShortcutsName keys={[`${getKeyboardKeyNameBySystem('ctrl')}`, '1']} />
|
||||
)
|
||||
}
|
||||
{
|
||||
option.key === ZoomType.zoomTo50 && (
|
||||
<ShortcutsName keys={['shift', '5']} />
|
||||
)
|
||||
}
|
||||
{
|
||||
option.key === ZoomType.zoomTo100 && (
|
||||
<ShortcutsName keys={['shift', '1']} />
|
||||
)
|
||||
}
|
||||
<span>{option.text}</span>
|
||||
<div className='flex items-center space-x-0.5'>
|
||||
{
|
||||
option.key === ZoomType.zoomToFit && (
|
||||
<ShortcutsName keys={['ctrl', '1']} />
|
||||
)
|
||||
}
|
||||
{
|
||||
option.key === ZoomType.zoomTo50 && (
|
||||
<ShortcutsName keys={['shift', '5']} />
|
||||
)
|
||||
}
|
||||
{
|
||||
option.key === ZoomType.zoomTo100 && (
|
||||
<ShortcutsName keys={['shift', '1']} />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useClickAway } from 'ahooks'
|
||||
import Divider from '../base/divider'
|
||||
import ShortcutsName from './shortcuts-name'
|
||||
import { useStore } from './store'
|
||||
import {
|
||||
@ -41,7 +42,7 @@ const PanelContextmenu = () => {
|
||||
const renderTrigger = () => {
|
||||
return (
|
||||
<div
|
||||
className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
|
||||
className='flex items-center justify-between px-3 h-8 text-sm text-text-secondary rounded-lg cursor-pointer hover:bg-state-base-hover'
|
||||
>
|
||||
{t('workflow.common.addBlock')}
|
||||
</div>
|
||||
@ -53,7 +54,7 @@ const PanelContextmenu = () => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className='absolute w-[200px] rounded-lg border-[0.5px] border-gray-200 bg-white shadow-xl z-[9]'
|
||||
className='absolute w-[200px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg z-[9]'
|
||||
style={{
|
||||
left: panelMenu.left,
|
||||
top: panelMenu.top,
|
||||
@ -69,7 +70,7 @@ const PanelContextmenu = () => {
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
|
||||
className='flex items-center justify-between px-3 h-8 text-sm text-text-secondary rounded-lg cursor-pointer hover:bg-state-base-hover'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleAddNote()
|
||||
@ -79,7 +80,7 @@ const PanelContextmenu = () => {
|
||||
{t('workflow.nodes.note.addNote')}
|
||||
</div>
|
||||
<div
|
||||
className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
|
||||
className='flex items-center justify-between px-3 h-8 text-sm text-text-secondary rounded-lg cursor-pointer hover:bg-state-base-hover'
|
||||
onClick={() => {
|
||||
handleStartWorkflowRun()
|
||||
handlePaneContextmenuCancel()
|
||||
@ -89,12 +90,12 @@ const PanelContextmenu = () => {
|
||||
<ShortcutsName keys={['alt', 'r']} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-[1px] bg-gray-100'></div>
|
||||
<Divider className='m-0' />
|
||||
<div className='p-1'>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer',
|
||||
!clipboardElements.length ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-50',
|
||||
'flex items-center justify-between px-3 h-8 text-sm text-text-secondary rounded-lg cursor-pointer',
|
||||
!clipboardElements.length ? 'opacity-50 cursor-not-allowed' : 'hover:bg-state-base-hover',
|
||||
)}
|
||||
onClick={() => {
|
||||
if (clipboardElements.length) {
|
||||
@ -107,16 +108,16 @@ const PanelContextmenu = () => {
|
||||
<ShortcutsName keys={['ctrl', 'v']} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-[1px] bg-gray-100'></div>
|
||||
<Divider className='m-0' />
|
||||
<div className='p-1'>
|
||||
<div
|
||||
className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
|
||||
className='flex items-center justify-between px-3 h-8 text-sm text-text-secondary rounded-lg cursor-pointer hover:bg-state-base-hover'
|
||||
onClick={() => exportCheck()}
|
||||
>
|
||||
{t('app.export')}
|
||||
</div>
|
||||
<div
|
||||
className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
|
||||
className='flex items-center justify-between px-3 h-8 text-sm text-text-secondary rounded-lg cursor-pointer hover:bg-state-base-hover'
|
||||
onClick={() => setShowImportDSLModal(true)}
|
||||
>
|
||||
{t('workflow.common.importDSL')}
|
||||
|
||||
@ -27,6 +27,7 @@ import {
|
||||
getProcessedFilesFromResponse,
|
||||
} from '@/app/components/base/file-uploader/utils'
|
||||
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
|
||||
type GetAbortController = (abortController: AbortController) => void
|
||||
type SendCallback = {
|
||||
@ -381,6 +382,28 @@ export const useChat = (
|
||||
}
|
||||
}))
|
||||
},
|
||||
onNodeRetry: ({ data }) => {
|
||||
if (data.iteration_id)
|
||||
return
|
||||
|
||||
const currentIndex = responseItem.workflowProcess!.tracing!.findIndex((item) => {
|
||||
if (!item.execution_metadata?.parallel_id)
|
||||
return item.node_id === data.node_id
|
||||
return item.node_id === data.node_id && (item.execution_metadata?.parallel_id === data.execution_metadata?.parallel_id || item.parallel_id === data.execution_metadata?.parallel_id)
|
||||
})
|
||||
if (responseItem.workflowProcess!.tracing[currentIndex].retryDetail)
|
||||
responseItem.workflowProcess!.tracing[currentIndex].retryDetail?.push(data as NodeTracing)
|
||||
else
|
||||
responseItem.workflowProcess!.tracing[currentIndex].retryDetail = [data as NodeTracing]
|
||||
|
||||
handleUpdateChatList(produce(chatListRef.current, (draft) => {
|
||||
const currentIndex = draft.findIndex(item => item.id === responseItem.id)
|
||||
draft[currentIndex] = {
|
||||
...draft[currentIndex],
|
||||
...responseItem,
|
||||
}
|
||||
}))
|
||||
},
|
||||
onNodeFinished: ({ data }) => {
|
||||
if (data.iteration_id)
|
||||
return
|
||||
@ -394,6 +417,9 @@ export const useChat = (
|
||||
...(responseItem.workflowProcess!.tracing[currentIndex]?.extras
|
||||
? { extras: responseItem.workflowProcess!.tracing[currentIndex].extras }
|
||||
: {}),
|
||||
...(responseItem.workflowProcess!.tracing[currentIndex]?.retryDetail
|
||||
? { retryDetail: responseItem.workflowProcess!.tracing[currentIndex].retryDetail }
|
||||
: {}),
|
||||
...data,
|
||||
} as any
|
||||
handleUpdateChatList(produce(chatListRef.current, (draft) => {
|
||||
|
||||
@ -25,6 +25,7 @@ import {
|
||||
import { SimpleBtn } from '../../app/text-generate/item'
|
||||
import Toast from '../../base/toast'
|
||||
import IterationResultPanel from '../run/iteration-result-panel'
|
||||
import RetryResultPanel from '../run/retry-result-panel'
|
||||
import InputsPanel from './inputs-panel'
|
||||
import cn from '@/utils/classnames'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
@ -53,11 +54,16 @@ const WorkflowPreview = () => {
|
||||
}, [workflowRunningData])
|
||||
|
||||
const [iterationRunResult, setIterationRunResult] = useState<NodeTracing[][]>([])
|
||||
const [retryRunResult, setRetryRunResult] = useState<NodeTracing[]>([])
|
||||
const [iterDurationMap, setIterDurationMap] = useState<IterationDurationMap>({})
|
||||
const [isShowIterationDetail, {
|
||||
setTrue: doShowIterationDetail,
|
||||
setFalse: doHideIterationDetail,
|
||||
}] = useBoolean(false)
|
||||
const [isShowRetryDetail, {
|
||||
setTrue: doShowRetryDetail,
|
||||
setFalse: doHideRetryDetail,
|
||||
}] = useBoolean(false)
|
||||
|
||||
const handleShowIterationDetail = useCallback((detail: NodeTracing[][], iterationDurationMap: IterationDurationMap) => {
|
||||
setIterDurationMap(iterationDurationMap)
|
||||
@ -65,6 +71,11 @@ const WorkflowPreview = () => {
|
||||
doShowIterationDetail()
|
||||
}, [doShowIterationDetail])
|
||||
|
||||
const handleRetryDetail = useCallback((detail: NodeTracing[]) => {
|
||||
setRetryRunResult(detail)
|
||||
doShowRetryDetail()
|
||||
}, [doShowRetryDetail])
|
||||
|
||||
if (isShowIterationDetail) {
|
||||
return (
|
||||
<div className={`
|
||||
@ -201,11 +212,12 @@ const WorkflowPreview = () => {
|
||||
<Loading />
|
||||
</div>
|
||||
)}
|
||||
{currentTab === 'TRACING' && (
|
||||
{currentTab === 'TRACING' && !isShowRetryDetail && (
|
||||
<TracingPanel
|
||||
className='bg-background-section-burn'
|
||||
list={workflowRunningData?.tracing || []}
|
||||
onShowIterationDetail={handleShowIterationDetail}
|
||||
onShowRetryDetail={handleRetryDetail}
|
||||
/>
|
||||
)}
|
||||
{currentTab === 'TRACING' && !workflowRunningData?.tracing?.length && (
|
||||
@ -213,7 +225,14 @@ const WorkflowPreview = () => {
|
||||
<Loading />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{
|
||||
currentTab === 'TRACING' && isShowRetryDetail && (
|
||||
<RetryResultPanel
|
||||
list={retryRunResult}
|
||||
onBack={doHideRetryDetail}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -9,6 +9,7 @@ import OutputPanel from './output-panel'
|
||||
import ResultPanel from './result-panel'
|
||||
import TracingPanel from './tracing-panel'
|
||||
import IterationResultPanel from './iteration-result-panel'
|
||||
import RetryResultPanel from './retry-result-panel'
|
||||
import cn from '@/utils/classnames'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
@ -77,11 +78,24 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
|
||||
|
||||
const groupMap = nodeGroupMap.get(iterationNode.node_id)!
|
||||
|
||||
if (!groupMap.has(runId))
|
||||
if (!groupMap.has(runId)) {
|
||||
groupMap.set(runId, [item])
|
||||
}
|
||||
else {
|
||||
if (item.status === 'retry') {
|
||||
const retryNode = groupMap.get(runId)!.find(node => node.node_id === item.node_id)
|
||||
|
||||
else
|
||||
groupMap.get(runId)!.push(item)
|
||||
if (retryNode) {
|
||||
if (retryNode?.retryDetail)
|
||||
retryNode.retryDetail.push(item)
|
||||
else
|
||||
retryNode.retryDetail = [item]
|
||||
}
|
||||
}
|
||||
else {
|
||||
groupMap.get(runId)!.push(item)
|
||||
}
|
||||
}
|
||||
|
||||
if (item.status === 'failed') {
|
||||
iterationNode.status = 'failed'
|
||||
@ -93,10 +107,24 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
|
||||
const updateSequentialModeGroup = (index: number, item: NodeTracing, iterationNode: NodeTracing) => {
|
||||
const { details } = iterationNode
|
||||
if (details) {
|
||||
if (!details[index])
|
||||
if (!details[index]) {
|
||||
details[index] = [item]
|
||||
else
|
||||
details[index].push(item)
|
||||
}
|
||||
else {
|
||||
if (item.status === 'retry') {
|
||||
const retryNode = details[index].find(node => node.node_id === item.node_id)
|
||||
|
||||
if (retryNode) {
|
||||
if (retryNode?.retryDetail)
|
||||
retryNode.retryDetail.push(item)
|
||||
else
|
||||
retryNode.retryDetail = [item]
|
||||
}
|
||||
}
|
||||
else {
|
||||
details[index].push(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item.status === 'failed') {
|
||||
@ -107,6 +135,18 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
|
||||
const processNonIterationNode = (item: NodeTracing) => {
|
||||
const { execution_metadata } = item
|
||||
if (!execution_metadata?.iteration_id) {
|
||||
if (item.status === 'retry') {
|
||||
const retryNode = result.find(node => node.node_id === item.node_id)
|
||||
|
||||
if (retryNode) {
|
||||
if (retryNode?.retryDetail)
|
||||
retryNode.retryDetail.push(item)
|
||||
else
|
||||
retryNode.retryDetail = [item]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
result.push(item)
|
||||
return
|
||||
}
|
||||
@ -181,10 +221,15 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
|
||||
|
||||
const [iterationRunResult, setIterationRunResult] = useState<NodeTracing[][]>([])
|
||||
const [iterDurationMap, setIterDurationMap] = useState<IterationDurationMap>({})
|
||||
const [retryRunResult, setRetryRunResult] = useState<NodeTracing[]>([])
|
||||
const [isShowIterationDetail, {
|
||||
setTrue: doShowIterationDetail,
|
||||
setFalse: doHideIterationDetail,
|
||||
}] = useBoolean(false)
|
||||
const [isShowRetryDetail, {
|
||||
setTrue: doShowRetryDetail,
|
||||
setFalse: doHideRetryDetail,
|
||||
}] = useBoolean(false)
|
||||
|
||||
const handleShowIterationDetail = useCallback((detail: NodeTracing[][], iterDurationMap: IterationDurationMap) => {
|
||||
setIterationRunResult(detail)
|
||||
@ -192,6 +237,11 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
|
||||
setIterDurationMap(iterDurationMap)
|
||||
}, [doShowIterationDetail, setIterationRunResult, setIterDurationMap])
|
||||
|
||||
const handleShowRetryDetail = useCallback((detail: NodeTracing[]) => {
|
||||
setRetryRunResult(detail)
|
||||
doShowRetryDetail()
|
||||
}, [doShowRetryDetail, setRetryRunResult])
|
||||
|
||||
if (isShowIterationDetail) {
|
||||
return (
|
||||
<div className='grow relative flex flex-col'>
|
||||
@ -261,13 +311,22 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
|
||||
exceptionCounts={runDetail.exceptions_count}
|
||||
/>
|
||||
)}
|
||||
{!loading && currentTab === 'TRACING' && (
|
||||
{!loading && currentTab === 'TRACING' && !isShowRetryDetail && (
|
||||
<TracingPanel
|
||||
className='bg-background-section-burn'
|
||||
list={list}
|
||||
onShowIterationDetail={handleShowIterationDetail}
|
||||
onShowRetryDetail={handleShowRetryDetail}
|
||||
/>
|
||||
)}
|
||||
{
|
||||
!loading && currentTab === 'TRACING' && isShowRetryDetail && (
|
||||
<RetryResultPanel
|
||||
list={retryRunResult}
|
||||
onBack={doHideRetryDetail}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
import { ArrowNarrowLeft } from '../../base/icons/src/vender/line/arrows'
|
||||
import { NodeRunningStatus } from '../types'
|
||||
import TracingPanel from './tracing-panel'
|
||||
import RetryResultPanel from './retry-result-panel'
|
||||
import { Iteration } from '@/app/components/base/icons/src/vender/workflow'
|
||||
import cn from '@/utils/classnames'
|
||||
import type { IterationDurationMap, NodeTracing } from '@/types/workflow'
|
||||
@ -41,8 +42,8 @@ const IterationResultPanel: FC<Props> = ({
|
||||
}))
|
||||
}, [])
|
||||
const countIterDuration = (iteration: NodeTracing[], iterDurationMap: IterationDurationMap): string => {
|
||||
const IterRunIndex = iteration[0].execution_metadata.iteration_index as number
|
||||
const iterRunId = iteration[0].execution_metadata.parallel_mode_run_id
|
||||
const IterRunIndex = iteration[0]?.execution_metadata?.iteration_index as number
|
||||
const iterRunId = iteration[0]?.execution_metadata?.parallel_mode_run_id
|
||||
const iterItem = iterDurationMap[iterRunId || IterRunIndex]
|
||||
const duration = iterItem
|
||||
return `${(duration && duration > 0.01) ? duration.toFixed(2) : 0.01}s`
|
||||
@ -74,6 +75,10 @@ const IterationResultPanel: FC<Props> = ({
|
||||
</>
|
||||
)
|
||||
}
|
||||
const [retryRunResult, setRetryRunResult] = useState<Record<string, NodeTracing[]> | undefined>()
|
||||
const handleRetryDetail = (v: number, detail?: NodeTracing[]) => {
|
||||
setRetryRunResult({ ...retryRunResult, [v]: detail })
|
||||
}
|
||||
|
||||
const main = (
|
||||
<>
|
||||
@ -116,15 +121,28 @@ const IterationResultPanel: FC<Props> = ({
|
||||
{expandedIterations[index] && <div
|
||||
className="grow h-px bg-divider-subtle"
|
||||
></div>}
|
||||
<div className={cn(
|
||||
'overflow-hidden transition-all duration-200',
|
||||
expandedIterations[index] ? 'max-h-[1000px] opacity-100' : 'max-h-0 opacity-0',
|
||||
)}>
|
||||
<TracingPanel
|
||||
list={iteration}
|
||||
className='bg-background-section-burn'
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
!retryRunResult?.[index] && (
|
||||
<div className={cn(
|
||||
'overflow-hidden transition-all duration-200',
|
||||
expandedIterations[index] ? 'max-h-[1000px] opacity-100' : 'max-h-0 opacity-0',
|
||||
)}>
|
||||
<TracingPanel
|
||||
list={iteration}
|
||||
className='bg-background-section-burn'
|
||||
onShowRetryDetail={v => handleRetryDetail(index, v)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
retryRunResult?.[index] && (
|
||||
<RetryResultPanel
|
||||
list={retryRunResult[index]}
|
||||
onBack={() => handleRetryDetail(index, undefined)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
RiCheckboxCircleFill,
|
||||
RiErrorWarningLine,
|
||||
RiLoader2Line,
|
||||
RiRestartFill,
|
||||
} from '@remixicon/react'
|
||||
import BlockIcon from '../block-icon'
|
||||
import { BlockEnum } from '../types'
|
||||
@ -20,6 +21,7 @@ import Button from '@/app/components/base/button'
|
||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||
import type { IterationDurationMap, NodeTracing } from '@/types/workflow'
|
||||
import ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip'
|
||||
import { hasRetryNode } from '@/app/components/workflow/utils'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
@ -28,8 +30,10 @@ type Props = {
|
||||
hideInfo?: boolean
|
||||
hideProcessDetail?: boolean
|
||||
onShowIterationDetail?: (detail: NodeTracing[][], iterDurationMap: IterationDurationMap) => void
|
||||
onShowRetryDetail?: (detail: NodeTracing[]) => void
|
||||
notShowIterationNav?: boolean
|
||||
justShowIterationNavArrow?: boolean
|
||||
justShowRetryNavArrow?: boolean
|
||||
}
|
||||
|
||||
const NodePanel: FC<Props> = ({
|
||||
@ -39,6 +43,7 @@ const NodePanel: FC<Props> = ({
|
||||
hideInfo = false,
|
||||
hideProcessDetail,
|
||||
onShowIterationDetail,
|
||||
onShowRetryDetail,
|
||||
notShowIterationNav,
|
||||
justShowIterationNavArrow,
|
||||
}) => {
|
||||
@ -88,11 +93,17 @@ const NodePanel: FC<Props> = ({
|
||||
}, [nodeInfo.expand, setCollapseState])
|
||||
|
||||
const isIterationNode = nodeInfo.node_type === BlockEnum.Iteration
|
||||
const isRetryNode = hasRetryNode(nodeInfo.node_type) && nodeInfo.retryDetail
|
||||
const handleOnShowIterationDetail = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation()
|
||||
e.nativeEvent.stopImmediatePropagation()
|
||||
onShowIterationDetail?.(nodeInfo.details || [], nodeInfo?.iterDurationMap || nodeInfo.execution_metadata?.iteration_duration_map || {})
|
||||
}
|
||||
const handleOnShowRetryDetail = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation()
|
||||
e.nativeEvent.stopImmediatePropagation()
|
||||
onShowRetryDetail?.(nodeInfo.retryDetail || [])
|
||||
}
|
||||
return (
|
||||
<div className={cn('px-2 py-1', className)}>
|
||||
<div className='group transition-all bg-background-default border border-components-panel-border rounded-[10px] shadow-xs hover:shadow-md'>
|
||||
@ -169,6 +180,19 @@ const NodePanel: FC<Props> = ({
|
||||
<Split className='mt-2' />
|
||||
</div>
|
||||
)}
|
||||
{isRetryNode && (
|
||||
<Button
|
||||
className='flex items-center justify-between mb-1 w-full'
|
||||
variant='tertiary'
|
||||
onClick={handleOnShowRetryDetail}
|
||||
>
|
||||
<div className='flex items-center'>
|
||||
<RiRestartFill className='mr-0.5 w-4 h-4 text-components-button-tertiary-text shrink-0' />
|
||||
{t('workflow.nodes.common.retry.retries', { num: nodeInfo.retryDetail?.length })}
|
||||
</div>
|
||||
<RiArrowRightSLine className='w-4 h-4 text-components-button-tertiary-text shrink-0' />
|
||||
</Button>
|
||||
)}
|
||||
<div className={cn('mb-1', hideInfo && '!px-2 !py-0.5')}>
|
||||
{(nodeInfo.status === 'stopped') && (
|
||||
<StatusContainer status='stopped'>
|
||||
@ -192,6 +216,11 @@ const NodePanel: FC<Props> = ({
|
||||
{nodeInfo.error}
|
||||
</StatusContainer>
|
||||
)}
|
||||
{nodeInfo.status === 'retry' && (
|
||||
<StatusContainer status='failed'>
|
||||
{nodeInfo.error}
|
||||
</StatusContainer>
|
||||
)}
|
||||
</div>
|
||||
{nodeInfo.inputs && (
|
||||
<div className={cn('mb-1')}>
|
||||
|
||||
@ -1,11 +1,17 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiArrowRightSLine,
|
||||
RiRestartFill,
|
||||
} from '@remixicon/react'
|
||||
import StatusPanel from './status'
|
||||
import MetaData from './meta'
|
||||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||
import ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
type ResultPanelProps = {
|
||||
inputs?: string
|
||||
@ -22,6 +28,8 @@ type ResultPanelProps = {
|
||||
showSteps?: boolean
|
||||
exceptionCounts?: number
|
||||
execution_metadata?: any
|
||||
retry_events?: NodeTracing[]
|
||||
onShowRetryDetail?: (retries: NodeTracing[]) => void
|
||||
}
|
||||
|
||||
const ResultPanel: FC<ResultPanelProps> = ({
|
||||
@ -38,8 +46,11 @@ const ResultPanel: FC<ResultPanelProps> = ({
|
||||
showSteps,
|
||||
exceptionCounts,
|
||||
execution_metadata,
|
||||
retry_events,
|
||||
onShowRetryDetail,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='bg-components-panel-bg py-2'>
|
||||
<div className='px-4 py-2'>
|
||||
@ -51,6 +62,23 @@ const ResultPanel: FC<ResultPanelProps> = ({
|
||||
exceptionCounts={exceptionCounts}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
retry_events?.length && onShowRetryDetail && (
|
||||
<div className='px-4'>
|
||||
<Button
|
||||
className='flex items-center justify-between w-full'
|
||||
variant='tertiary'
|
||||
onClick={() => onShowRetryDetail(retry_events)}
|
||||
>
|
||||
<div className='flex items-center'>
|
||||
<RiRestartFill className='mr-0.5 w-4 h-4 text-components-button-tertiary-text shrink-0' />
|
||||
{t('workflow.nodes.common.retry.retries', { num: retry_events?.length })}
|
||||
</div>
|
||||
<RiArrowRightSLine className='w-4 h-4 text-components-button-tertiary-text shrink-0' />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className='px-4 py-2 flex flex-col gap-2'>
|
||||
<CodeEditor
|
||||
readOnly
|
||||
|
||||
46
web/app/components/workflow/run/retry-result-panel.tsx
Normal file
46
web/app/components/workflow/run/retry-result-panel.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
'use client'
|
||||
|
||||
import type { FC } from 'react'
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiArrowLeftLine,
|
||||
} from '@remixicon/react'
|
||||
import TracingPanel from './tracing-panel'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
|
||||
type Props = {
|
||||
list: NodeTracing[]
|
||||
onBack: () => void
|
||||
}
|
||||
|
||||
const RetryResultPanel: FC<Props> = ({
|
||||
list,
|
||||
onBack,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className='flex items-center px-4 h-8 text-text-accent-secondary bg-components-panel-bg system-sm-medium cursor-pointer'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
e.nativeEvent.stopImmediatePropagation()
|
||||
onBack()
|
||||
}}
|
||||
>
|
||||
<RiArrowLeftLine className='mr-1 w-4 h-4' />
|
||||
{t('workflow.singleRun.back')}
|
||||
</div>
|
||||
<TracingPanel
|
||||
list={list.map((item, index) => ({
|
||||
...item,
|
||||
title: `${t('workflow.nodes.common.retry.retry')} ${index + 1}`,
|
||||
}))}
|
||||
className='bg-background-section-burn'
|
||||
/>
|
||||
</div >
|
||||
)
|
||||
}
|
||||
export default memo(RetryResultPanel)
|
||||
@ -21,6 +21,7 @@ import type { IterationDurationMap, NodeTracing } from '@/types/workflow'
|
||||
type TracingPanelProps = {
|
||||
list: NodeTracing[]
|
||||
onShowIterationDetail?: (detail: NodeTracing[][], iterDurationMap: IterationDurationMap) => void
|
||||
onShowRetryDetail?: (detail: NodeTracing[]) => void
|
||||
className?: string
|
||||
hideNodeInfo?: boolean
|
||||
hideNodeProcessDetail?: boolean
|
||||
@ -160,6 +161,7 @@ function buildLogTree(nodes: NodeTracing[], t: (key: string) => string): Tracing
|
||||
const TracingPanel: FC<TracingPanelProps> = ({
|
||||
list,
|
||||
onShowIterationDetail,
|
||||
onShowRetryDetail,
|
||||
className,
|
||||
hideNodeInfo = false,
|
||||
hideNodeProcessDetail = false,
|
||||
@ -251,7 +253,9 @@ const TracingPanel: FC<TracingPanelProps> = ({
|
||||
<NodePanel
|
||||
nodeInfo={node.data!}
|
||||
onShowIterationDetail={onShowIterationDetail}
|
||||
onShowRetryDetail={onShowRetryDetail}
|
||||
justShowIterationNavArrow={true}
|
||||
justShowRetryNavArrow={true}
|
||||
hideInfo={hideNodeInfo}
|
||||
hideProcessDetail={hideNodeProcessDetail}
|
||||
/>
|
||||
|
||||
@ -12,14 +12,14 @@ const ShortcutsName = ({
|
||||
}: ShortcutsNameProps) => {
|
||||
return (
|
||||
<div className={cn(
|
||||
'flex items-center gap-0.5 h-4 text-xs text-gray-400',
|
||||
'flex items-center gap-0.5',
|
||||
className,
|
||||
)}>
|
||||
{
|
||||
keys.map(key => (
|
||||
<div
|
||||
key={key}
|
||||
className='capitalize'
|
||||
className='w-4 h-4 flex items-center justify-center bg-components-kbd-bg-gray rounded-[4px] system-kbd capitalize'
|
||||
>
|
||||
{getKeyboardKeyNameBySystem(key)}
|
||||
</div>
|
||||
|
||||
@ -19,4 +19,6 @@
|
||||
|
||||
#workflow-container .react-flow__node-custom-note {
|
||||
z-index: -1000 !important;
|
||||
}
|
||||
}
|
||||
|
||||
#workflow-container .react-flow {}
|
||||
@ -13,6 +13,7 @@ import type {
|
||||
DefaultValueForm,
|
||||
ErrorHandleTypeEnum,
|
||||
} from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
||||
import type { WorkflowRetryConfig } from '@/app/components/workflow/nodes/_base/components/retry/types'
|
||||
|
||||
export enum BlockEnum {
|
||||
Start = 'start',
|
||||
@ -68,6 +69,7 @@ export type CommonNodeType<T = {}> = {
|
||||
_iterationIndex?: number
|
||||
_inParallelHovering?: boolean
|
||||
_waitingRun?: boolean
|
||||
_retryIndex?: number
|
||||
isInIteration?: boolean
|
||||
iteration_id?: string
|
||||
selected?: boolean
|
||||
@ -77,6 +79,7 @@ export type CommonNodeType<T = {}> = {
|
||||
width?: number
|
||||
height?: number
|
||||
error_strategy?: ErrorHandleTypeEnum
|
||||
retry_config?: WorkflowRetryConfig
|
||||
default_value?: DefaultValueForm[]
|
||||
} & T & Partial<Pick<ToolDefaultValue, 'provider_id' | 'provider_type' | 'provider_name' | 'tool_name'>>
|
||||
|
||||
@ -293,6 +296,7 @@ export enum NodeRunningStatus {
|
||||
Succeeded = 'succeeded',
|
||||
Failed = 'failed',
|
||||
Exception = 'exception',
|
||||
Retry = 'retry',
|
||||
}
|
||||
|
||||
export type OnNodeAdd = (
|
||||
|
||||
@ -26,6 +26,8 @@ import {
|
||||
} from './types'
|
||||
import {
|
||||
CUSTOM_NODE,
|
||||
DEFAULT_RETRY_INTERVAL,
|
||||
DEFAULT_RETRY_MAX,
|
||||
ITERATION_CHILDREN_Z_INDEX,
|
||||
ITERATION_NODE_Z_INDEX,
|
||||
NODE_WIDTH_X_OFFSET,
|
||||
@ -292,6 +294,13 @@ export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => {
|
||||
|
||||
if (node.data.type === BlockEnum.ParameterExtractor)
|
||||
(node as any).data.model.provider = correctProvider((node as any).data.model.provider)
|
||||
if (node.data.type === BlockEnum.HttpRequest && !node.data.retry_config) {
|
||||
node.data.retry_config = {
|
||||
retry_enabled: true,
|
||||
max_retries: DEFAULT_RETRY_MAX,
|
||||
retry_interval: DEFAULT_RETRY_INTERVAL,
|
||||
}
|
||||
}
|
||||
|
||||
return node
|
||||
})
|
||||
@ -563,6 +572,7 @@ export const isMac = () => {
|
||||
const specialKeysNameMap: Record<string, string | undefined> = {
|
||||
ctrl: '⌘',
|
||||
alt: '⌥',
|
||||
shift: '⇧',
|
||||
}
|
||||
|
||||
export const getKeyboardKeyNameBySystem = (key: string) => {
|
||||
@ -810,3 +820,7 @@ export const isExceptionVariable = (variable: string, nodeType?: BlockEnum) => {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export const hasRetryNode = (nodeType?: BlockEnum) => {
|
||||
return nodeType === BlockEnum.LLM || nodeType === BlockEnum.Tool || nodeType === BlockEnum.HttpRequest || nodeType === BlockEnum.Code
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user