mirror of
https://github.com/langgenius/dify.git
synced 2026-03-13 11:07:40 +08:00
Merge remote-tracking branch 'origin/main' into feat/trigger
This commit is contained in:
@ -4,17 +4,20 @@ import { Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useInputFieldPanel } from '@/app/components/rag-pipeline/hooks'
|
||||
|
||||
const EnvButton = ({ disabled }: { disabled: boolean }) => {
|
||||
const { theme } = useTheme()
|
||||
const setShowChatVariablePanel = useStore(s => s.setShowChatVariablePanel)
|
||||
const setShowEnvPanel = useStore(s => s.setShowEnvPanel)
|
||||
const setShowDebugAndPreviewPanel = useStore(s => s.setShowDebugAndPreviewPanel)
|
||||
const { closeAllInputFieldPanels } = useInputFieldPanel()
|
||||
|
||||
const handleClick = () => {
|
||||
setShowEnvPanel(true)
|
||||
setShowChatVariablePanel(false)
|
||||
setShowDebugAndPreviewPanel(false)
|
||||
closeAllInputFieldPanels()
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -13,10 +13,12 @@ import {
|
||||
useWorkflowRun,
|
||||
} from '../hooks'
|
||||
import Divider from '../../base/divider'
|
||||
import type { RunAndHistoryProps } from './run-and-history'
|
||||
import RunAndHistory from './run-and-history'
|
||||
import EditingTitle from './editing-title'
|
||||
import EnvButton from './env-button'
|
||||
import VersionHistoryButton from './version-history-button'
|
||||
import { useInputFieldPanel } from '@/app/components/rag-pipeline/hooks'
|
||||
import ScrollToSelectedNodeButton from './scroll-to-selected-node-button'
|
||||
|
||||
export type HeaderInNormalProps = {
|
||||
@ -24,9 +26,11 @@ export type HeaderInNormalProps = {
|
||||
left?: React.ReactNode
|
||||
middle?: React.ReactNode
|
||||
}
|
||||
runAndHistoryProps?: RunAndHistoryProps
|
||||
}
|
||||
const HeaderInNormal = ({
|
||||
components,
|
||||
runAndHistoryProps,
|
||||
}: HeaderInNormalProps) => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
@ -39,6 +43,7 @@ const HeaderInNormal = ({
|
||||
const nodes = useNodes<StartNodeType>()
|
||||
const selectedNode = nodes.find(node => node.data.selected)
|
||||
const { handleBackupDraft } = useWorkflowRun()
|
||||
const { closeAllInputFieldPanels } = useInputFieldPanel()
|
||||
|
||||
const onStartRestoring = useCallback(() => {
|
||||
workflowStore.setState({ isRestoring: true })
|
||||
@ -51,6 +56,7 @@ const HeaderInNormal = ({
|
||||
setShowDebugAndPreviewPanel(false)
|
||||
setShowVariableInspectPanel(false)
|
||||
setShowChatVariablePanel(false)
|
||||
closeAllInputFieldPanels()
|
||||
}, [workflowStore, handleBackupDraft, selectedNode, handleNodeSelect, setShowWorkflowVersionHistoryPanel, setShowEnvPanel, setShowDebugAndPreviewPanel, setShowVariableInspectPanel, setShowChatVariablePanel])
|
||||
|
||||
return (
|
||||
@ -65,7 +71,7 @@ const HeaderInNormal = ({
|
||||
{components?.left}
|
||||
<EnvButton disabled={nodesReadOnly} />
|
||||
<Divider type='vertical' className='mx-auto h-3.5' />
|
||||
<RunAndHistory />
|
||||
<RunAndHistory {...runAndHistoryProps} />
|
||||
{components?.middle}
|
||||
<VersionHistoryButton onClick={onStartRestoring} />
|
||||
</div>
|
||||
|
||||
@ -17,8 +17,8 @@ import {
|
||||
import Toast from '../../base/toast'
|
||||
import RestoringTitle from './restoring-title'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { useInvalidAllLastRun } from '@/service/use-workflow'
|
||||
import { useHooksStore } from '../hooks-store'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
@ -31,9 +31,8 @@ const HeaderInRestoring = ({
|
||||
const { t } = useTranslation()
|
||||
const { theme } = useTheme()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const appDetail = useAppStore.getState().appDetail
|
||||
|
||||
const invalidAllLastRun = useInvalidAllLastRun(appDetail!.id)
|
||||
const configsMap = useHooksStore(s => s.configsMap)
|
||||
const invalidAllLastRun = useInvalidAllLastRun(configsMap?.flowType, configsMap?.flowId)
|
||||
const {
|
||||
deleteAllInspectVars,
|
||||
} = workflowStore.getState()
|
||||
|
||||
@ -10,11 +10,17 @@ import {
|
||||
} from '../hooks'
|
||||
import Divider from '../../base/divider'
|
||||
import RunningTitle from './running-title'
|
||||
import type { ViewHistoryProps } from './view-history'
|
||||
import ViewHistory from './view-history'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { ArrowNarrowLeft } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
|
||||
const HeaderInHistory = () => {
|
||||
export type HeaderInHistoryProps = {
|
||||
viewHistoryProps?: ViewHistoryProps
|
||||
}
|
||||
const HeaderInHistory = ({
|
||||
viewHistoryProps,
|
||||
}: HeaderInHistoryProps) => {
|
||||
const { t } = useTranslation()
|
||||
const workflowStore = useWorkflowStore()
|
||||
|
||||
@ -33,7 +39,7 @@ const HeaderInHistory = () => {
|
||||
<RunningTitle />
|
||||
</div>
|
||||
<div className='flex items-center space-x-2'>
|
||||
<ViewHistory withText />
|
||||
<ViewHistory {...viewHistoryProps} withText />
|
||||
<Divider type='vertical' className='mx-auto h-3.5' />
|
||||
<Button
|
||||
variant='primary'
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
} from '../hooks'
|
||||
import type { HeaderInNormalProps } from './header-in-normal'
|
||||
import HeaderInNormal from './header-in-normal'
|
||||
import type { HeaderInHistoryProps } from './header-in-view-history'
|
||||
import type { HeaderInRestoringProps } from './header-in-restoring'
|
||||
import { useStore } from '../store'
|
||||
import dynamic from 'next/dynamic'
|
||||
@ -17,14 +18,17 @@ const HeaderInRestoring = dynamic(() => import('./header-in-restoring'), {
|
||||
|
||||
export type HeaderProps = {
|
||||
normal?: HeaderInNormalProps
|
||||
viewHistory?: HeaderInHistoryProps
|
||||
restoring?: HeaderInRestoringProps
|
||||
}
|
||||
const Header = ({
|
||||
normal: normalProps,
|
||||
viewHistory: viewHistoryProps,
|
||||
restoring: restoringProps,
|
||||
}: HeaderProps) => {
|
||||
const pathname = usePathname()
|
||||
const inWorkflowCanvas = pathname.endsWith('/workflow')
|
||||
const isPipelineCanvas = pathname.endsWith('/pipeline')
|
||||
const {
|
||||
normal,
|
||||
restoring,
|
||||
@ -34,9 +38,9 @@ const Header = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className='absolute left-0 top-0 z-10 flex h-14 w-full items-center justify-between bg-mask-top2bottom-gray-50-to-transparent px-3'
|
||||
className='absolute left-0 top-7 z-10 flex h-0 w-full items-center justify-between bg-mask-top2bottom-gray-50-to-transparent px-3'
|
||||
>
|
||||
{inWorkflowCanvas && maximizeCanvas && <div className='h-14 w-[52px]' />}
|
||||
{(inWorkflowCanvas || isPipelineCanvas) && maximizeCanvas && <div className='h-14 w-[52px]' />}
|
||||
{
|
||||
normal && (
|
||||
<HeaderInNormal
|
||||
@ -46,7 +50,9 @@ const Header = ({
|
||||
}
|
||||
{
|
||||
viewHistory && (
|
||||
<HeaderInHistory />
|
||||
<HeaderInHistory
|
||||
{...viewHistoryProps}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
|
||||
@ -1,127 +1,17 @@
|
||||
import type { FC } from 'react'
|
||||
import { memo, useEffect, useRef } from 'react'
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiLoader2Line,
|
||||
RiPlayLargeLine,
|
||||
} from '@remixicon/react'
|
||||
import { useStore } from '../store'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useNodesReadOnly,
|
||||
useWorkflowRun,
|
||||
useWorkflowRunValidation,
|
||||
useWorkflowStartRun,
|
||||
} from '../hooks'
|
||||
import { WorkflowRunningStatus } from '../types'
|
||||
import type { ViewHistoryProps } from './view-history'
|
||||
import ViewHistory from './view-history'
|
||||
import Checklist from './checklist'
|
||||
import TestRunMenu, { type TestRunMenuRef } from './test-run-menu'
|
||||
import type { TriggerOption } from './test-run-menu'
|
||||
import { useDynamicTestRunOptions } from '../hooks/use-dynamic-test-run-options'
|
||||
import cn from '@/utils/classnames'
|
||||
import {
|
||||
StopCircle,
|
||||
} from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
import ShortcutsName from '../shortcuts-name'
|
||||
|
||||
const RunMode = memo(() => {
|
||||
const { t } = useTranslation()
|
||||
const { handleWorkflowStartRunInWorkflow } = useWorkflowStartRun()
|
||||
const { handleStopRun } = useWorkflowRun()
|
||||
const { validateBeforeRun } = useWorkflowRunValidation()
|
||||
const workflowRunningData = useStore(s => s.workflowRunningData)
|
||||
const isRunning = workflowRunningData?.result.status === WorkflowRunningStatus.Running
|
||||
const dynamicOptions = useDynamicTestRunOptions()
|
||||
const testRunMenuRef = useRef<TestRunMenuRef>(null)
|
||||
|
||||
useEffect(() => {
|
||||
// @ts-expect-error - Dynamic property for backward compatibility with keyboard shortcuts
|
||||
window._toggleTestRunDropdown = () => {
|
||||
testRunMenuRef.current?.toggle()
|
||||
}
|
||||
return () => {
|
||||
// @ts-expect-error - Dynamic property cleanup
|
||||
delete window._toggleTestRunDropdown
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleStop = () => {
|
||||
handleStopRun(workflowRunningData?.task_id || '')
|
||||
}
|
||||
|
||||
const handleTriggerSelect = (option: TriggerOption) => {
|
||||
// Validate checklist before running any workflow
|
||||
if (!validateBeforeRun())
|
||||
return
|
||||
|
||||
if (option.type === 'user_input') {
|
||||
handleWorkflowStartRunInWorkflow()
|
||||
}
|
||||
else {
|
||||
// Placeholder for trigger-specific execution logic for schedule, webhook, plugin types
|
||||
console.log('TODO: Handle trigger execution for type:', option.type, 'nodeId:', option.nodeId)
|
||||
}
|
||||
}
|
||||
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
if (v.type === EVENT_WORKFLOW_STOP)
|
||||
handleStop()
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
isRunning
|
||||
? (
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-7 items-center rounded-md px-2.5 text-[13px] font-medium text-components-button-secondary-accent-text',
|
||||
'!cursor-not-allowed bg-state-accent-hover',
|
||||
)}
|
||||
>
|
||||
<RiLoader2Line className='mr-1 h-4 w-4 animate-spin' />
|
||||
{t('workflow.common.running')}
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<TestRunMenu
|
||||
ref={testRunMenuRef}
|
||||
options={dynamicOptions}
|
||||
onSelect={handleTriggerSelect}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-7 items-center rounded-md px-2.5 text-[13px] font-medium text-components-button-secondary-accent-text',
|
||||
'cursor-pointer hover:bg-state-accent-hover',
|
||||
)}
|
||||
style={{ userSelect: 'none' }}
|
||||
>
|
||||
<RiPlayLargeLine className='mr-1 h-4 w-4' />
|
||||
{t('workflow.common.run')}
|
||||
<ShortcutsName keys={['alt', 'r']} className="ml-1" textColor="secondary" />
|
||||
</div>
|
||||
</TestRunMenu>
|
||||
)
|
||||
}
|
||||
{
|
||||
isRunning && (
|
||||
<div
|
||||
className='ml-0.5 flex h-7 w-7 cursor-pointer items-center justify-center rounded-md hover:bg-black/5'
|
||||
onClick={handleStop}
|
||||
>
|
||||
<StopCircle className='h-4 w-4 text-components-button-ghost-text' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
})
|
||||
RunMode.displayName = 'RunMode'
|
||||
import RunMode from './run-mode'
|
||||
|
||||
const PreviewMode = memo(() => {
|
||||
const { t } = useTranslation()
|
||||
@ -140,30 +30,45 @@ const PreviewMode = memo(() => {
|
||||
</div>
|
||||
)
|
||||
})
|
||||
PreviewMode.displayName = 'PreviewMode'
|
||||
|
||||
const RunAndHistory: FC = () => {
|
||||
const { theme } = useTheme()
|
||||
const isChatMode = useIsChatMode()
|
||||
export type RunAndHistoryProps = {
|
||||
showRunButton?: boolean
|
||||
runButtonText?: string
|
||||
isRunning?: boolean
|
||||
showPreviewButton?: boolean
|
||||
viewHistoryProps?: ViewHistoryProps
|
||||
components?: {
|
||||
RunMode?: React.ComponentType<
|
||||
{
|
||||
text?: string
|
||||
}
|
||||
>
|
||||
}
|
||||
}
|
||||
const RunAndHistory = ({
|
||||
showRunButton,
|
||||
runButtonText,
|
||||
showPreviewButton,
|
||||
viewHistoryProps,
|
||||
components,
|
||||
}: RunAndHistoryProps) => {
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
const { RunMode: CustomRunMode } = components || {}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={cn(
|
||||
'flex h-8 items-center rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-0.5 shadow-xs',
|
||||
theme === 'dark' && 'rounded-lg border border-black/5 bg-white/10 backdrop-blur-sm',
|
||||
)}>
|
||||
{
|
||||
!isChatMode && <RunMode />
|
||||
}
|
||||
{
|
||||
isChatMode && <PreviewMode />
|
||||
}
|
||||
<div className='mx-0.5 h-3.5 w-[1px] bg-divider-regular'></div>
|
||||
<ViewHistory />
|
||||
<Checklist disabled={nodesReadOnly} />
|
||||
</div>
|
||||
</>
|
||||
<div className='flex h-8 items-center rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-0.5 shadow-xs'>
|
||||
{
|
||||
showRunButton && (
|
||||
CustomRunMode ? <CustomRunMode text={runButtonText} /> : <RunMode text={runButtonText} />
|
||||
)
|
||||
}
|
||||
{
|
||||
showPreviewButton && <PreviewMode />
|
||||
}
|
||||
<div className='mx-0.5 h-3.5 w-[1px] bg-divider-regular'></div>
|
||||
<ViewHistory {...viewHistoryProps} />
|
||||
<Checklist disabled={nodesReadOnly} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
146
web/app/components/workflow/header/run-mode.tsx
Normal file
146
web/app/components/workflow/header/run-mode.tsx
Normal file
@ -0,0 +1,146 @@
|
||||
import React, { useCallback } from 'react'
|
||||
// import { useEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useWorkflowRun, /* useWorkflowRunValidation, */ useWorkflowStartRun } from '@/app/components/workflow/hooks'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types'
|
||||
import { getKeyboardKeyNameBySystem } from '@/app/components/workflow/utils'
|
||||
import cn from '@/utils/classnames'
|
||||
import { RiLoader2Line, RiPlayLargeLine } from '@remixicon/react'
|
||||
import { StopCircle } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
||||
// import ShortcutsName from '../shortcuts-name'
|
||||
// import { useDynamicTestRunOptions } from '../hooks/use-dynamic-test-run-options'
|
||||
// import TestRunMenu, { type TestRunMenuRef, type TriggerOption } from './test-run-menu'
|
||||
|
||||
type RunModeProps = {
|
||||
text?: string
|
||||
}
|
||||
|
||||
const RunMode = ({
|
||||
text,
|
||||
}: RunModeProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleWorkflowStartRunInWorkflow } = useWorkflowStartRun()
|
||||
const { handleStopRun } = useWorkflowRun()
|
||||
// const { validateBeforeRun } = useWorkflowRunValidation()
|
||||
const workflowRunningData = useStore(s => s.workflowRunningData)
|
||||
|
||||
const isRunning = workflowRunningData?.result.status === WorkflowRunningStatus.Running
|
||||
|
||||
// const dynamicOptions = useDynamicTestRunOptions()
|
||||
// const testRunMenuRef = useRef<TestRunMenuRef>(null)
|
||||
|
||||
// useEffect(() => {
|
||||
// // @ts-expect-error - Dynamic property for backward compatibility with keyboard shortcuts
|
||||
// window._toggleTestRunDropdown = () => {
|
||||
// testRunMenuRef.current?.toggle()
|
||||
// }
|
||||
// return () => {
|
||||
// // @ts-expect-error - Dynamic property cleanup
|
||||
// delete window._toggleTestRunDropdown
|
||||
// }
|
||||
// }, [])
|
||||
|
||||
const handleStop = useCallback(() => {
|
||||
handleStopRun(workflowRunningData?.task_id || '')
|
||||
}, [handleStopRun, workflowRunningData?.task_id])
|
||||
|
||||
// const handleTriggerSelect = (option: TriggerOption) => {
|
||||
// // Validate checklist before running any workflow
|
||||
// if (!validateBeforeRun())
|
||||
// return
|
||||
|
||||
// if (option.type === 'user_input') {
|
||||
// handleWorkflowStartRunInWorkflow()
|
||||
// }
|
||||
// else {
|
||||
// // Placeholder for trigger-specific execution logic for schedule, webhook, plugin types
|
||||
// console.log('TODO: Handle trigger execution for type:', option.type, 'nodeId:', option.nodeId)
|
||||
// }
|
||||
// }
|
||||
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
if (v.type === EVENT_WORKFLOW_STOP)
|
||||
handleStop()
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='flex items-center gap-x-px'>
|
||||
<button
|
||||
type='button'
|
||||
className={cn(
|
||||
'system-xs-medium flex h-7 items-center gap-x-1 px-1.5 text-text-accent hover:bg-state-accent-hover',
|
||||
isRunning && 'cursor-not-allowed bg-state-accent-hover',
|
||||
isRunning ? 'rounded-l-md' : 'rounded-md',
|
||||
)}
|
||||
onClick={() => {
|
||||
handleWorkflowStartRunInWorkflow()
|
||||
}}
|
||||
disabled={isRunning}
|
||||
>
|
||||
{
|
||||
isRunning
|
||||
? (
|
||||
<>
|
||||
<RiLoader2Line className='mr-1 size-4 animate-spin' />
|
||||
{t('workflow.common.running')}
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<RiPlayLargeLine className='mr-1 size-4' />
|
||||
{text ?? t('workflow.common.run')}
|
||||
</>
|
||||
// <TestRunMenu
|
||||
// ref={testRunMenuRef}
|
||||
// options={dynamicOptions}
|
||||
// onSelect={handleTriggerSelect}
|
||||
// >
|
||||
// <div
|
||||
// className={cn(
|
||||
// 'flex h-7 items-center rounded-md px-2.5 text-[13px] font-medium text-components-button-secondary-accent-text',
|
||||
// 'cursor-pointer hover:bg-state-accent-hover',
|
||||
// )}
|
||||
// style={{ userSelect: 'none' }}
|
||||
// >
|
||||
// <RiPlayLargeLine className='mr-1 size-4' />
|
||||
// {text ?? t('workflow.common.run')}
|
||||
// <ShortcutsName keys={['alt', 'r']} className="ml-1" textColor="secondary" />
|
||||
// </div>
|
||||
// </TestRunMenu>
|
||||
)
|
||||
}
|
||||
{
|
||||
!isRunning && (
|
||||
<div className='system-kbd flex items-center gap-x-0.5 text-text-tertiary'>
|
||||
<div className='flex size-4 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray'>
|
||||
{getKeyboardKeyNameBySystem('alt')}
|
||||
</div>
|
||||
<div className='flex size-4 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray'>
|
||||
R
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</button>
|
||||
{
|
||||
isRunning && (
|
||||
<button
|
||||
type='button'
|
||||
className={cn(
|
||||
'flex size-7 items-center justify-center rounded-r-md bg-state-accent-active',
|
||||
)}
|
||||
onClick={handleStop}
|
||||
>
|
||||
<StopCircle className='size-4 text-text-accent' />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(RunMode)
|
||||
@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useKeyPress } from 'ahooks'
|
||||
import Button from '../../base/button'
|
||||
import Tooltip from '../../base/tooltip'
|
||||
import { getKeyboardKeyCodeBySystem } from '../utils'
|
||||
import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '../utils'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
@ -12,7 +12,7 @@ type VersionHistoryButtonProps = {
|
||||
onClick: () => Promise<unknown> | unknown
|
||||
}
|
||||
|
||||
const VERSION_HISTORY_SHORTCUT = ['⌘', '⇧', 'H']
|
||||
const VERSION_HISTORY_SHORTCUT = ['ctrl', '⇧', 'H']
|
||||
|
||||
const PopupContent = React.memo(() => {
|
||||
const { t } = useTranslation()
|
||||
@ -27,7 +27,7 @@ const PopupContent = React.memo(() => {
|
||||
key={key}
|
||||
className='system-kbd rounded-[4px] bg-components-kbd-bg-white px-[1px] text-text-tertiary'
|
||||
>
|
||||
{key}
|
||||
{getKeyboardKeyNameBySystem(key)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
@ -48,8 +48,7 @@ const VersionHistoryButton: FC<VersionHistoryButtonProps> = ({
|
||||
useKeyPress(`${getKeyboardKeyCodeBySystem('ctrl')}.shift.h`, (e) => {
|
||||
e.preventDefault()
|
||||
handleViewVersionHistory()
|
||||
},
|
||||
{ exactMatch: true, useCapture: true })
|
||||
}, { exactMatch: true, useCapture: true })
|
||||
|
||||
return <Tooltip
|
||||
popupContent={<PopupContent />}
|
||||
|
||||
@ -2,9 +2,10 @@ import {
|
||||
memo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import type { Fetcher } from 'swr'
|
||||
import useSWR from 'swr'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { noop } from 'lodash-es'
|
||||
import {
|
||||
RiCheckboxCircleLine,
|
||||
RiCloseLine,
|
||||
@ -26,27 +27,30 @@ import {
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import {
|
||||
ClockPlay,
|
||||
ClockPlaySlim,
|
||||
} from '@/app/components/base/icons/src/vender/line/time'
|
||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
|
||||
import {
|
||||
fetchChatRunHistory,
|
||||
fetchWorkflowRunHistory,
|
||||
} from '@/service/workflow'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import {
|
||||
useStore,
|
||||
useWorkflowStore,
|
||||
} from '@/app/components/workflow/store'
|
||||
import type { WorkflowRunHistoryResponse } from '@/types/workflow'
|
||||
import { useInputFieldPanel } from '@/app/components/rag-pipeline/hooks'
|
||||
|
||||
type ViewHistoryProps = {
|
||||
export type ViewHistoryProps = {
|
||||
withText?: boolean
|
||||
onClearLogAndMessageModal?: () => void
|
||||
historyUrl?: string
|
||||
historyFetcher?: Fetcher<WorkflowRunHistoryResponse, string>
|
||||
}
|
||||
const ViewHistory = ({
|
||||
withText,
|
||||
onClearLogAndMessageModal,
|
||||
historyUrl,
|
||||
historyFetcher,
|
||||
}: ViewHistoryProps) => {
|
||||
const { t } = useTranslation()
|
||||
const isChatMode = useIsChatMode()
|
||||
@ -60,18 +64,15 @@ const ViewHistory = ({
|
||||
} = useWorkflowInteractions()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const setControlMode = useStore(s => s.setControlMode)
|
||||
const { appDetail, setCurrentLogItem, setShowMessageLogModal } = useAppStore(useShallow(state => ({
|
||||
appDetail: state.appDetail,
|
||||
setCurrentLogItem: state.setCurrentLogItem,
|
||||
setShowMessageLogModal: state.setShowMessageLogModal,
|
||||
})))
|
||||
const historyWorkflowData = useStore(s => s.historyWorkflowData)
|
||||
const { handleBackupDraft } = useWorkflowRun()
|
||||
const { data: runList, isLoading: runListLoading } = useSWR((appDetail && !isChatMode && open) ? `/apps/${appDetail.id}/workflow-runs` : null, fetchWorkflowRunHistory)
|
||||
const { data: chatList, isLoading: chatListLoading } = useSWR((appDetail && isChatMode && open) ? `/apps/${appDetail.id}/advanced-chat/workflow-runs` : null, fetchChatRunHistory)
|
||||
const { closeAllInputFieldPanels } = useInputFieldPanel()
|
||||
|
||||
const data = isChatMode ? chatList : runList
|
||||
const isLoading = isChatMode ? chatListLoading : runListLoading
|
||||
const fetcher = historyFetcher ?? (noop as Fetcher<WorkflowRunHistoryResponse, string>)
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
} = useSWR((open && historyUrl && historyFetcher) ? historyUrl : null, fetcher)
|
||||
|
||||
return (
|
||||
(
|
||||
@ -107,8 +108,7 @@ const ViewHistory = ({
|
||||
<div
|
||||
className={cn('group flex h-7 w-7 cursor-pointer items-center justify-center rounded-md hover:bg-state-accent-hover', open && 'bg-state-accent-hover')}
|
||||
onClick={() => {
|
||||
setCurrentLogItem()
|
||||
setShowMessageLogModal(false)
|
||||
onClearLogAndMessageModal?.()
|
||||
}}
|
||||
>
|
||||
<ClockPlay className={cn('h-4 w-4 group-hover:text-components-button-secondary-accent-text', open ? 'text-components-button-secondary-accent-text' : 'text-components-button-ghost-text')} />
|
||||
@ -129,8 +129,7 @@ const ViewHistory = ({
|
||||
<div
|
||||
className='flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center'
|
||||
onClick={() => {
|
||||
setCurrentLogItem()
|
||||
setShowMessageLogModal(false)
|
||||
onClearLogAndMessageModal?.()
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
@ -171,6 +170,7 @@ const ViewHistory = ({
|
||||
showInputsPanel: false,
|
||||
showEnvPanel: false,
|
||||
})
|
||||
closeAllInputFieldPanels()
|
||||
handleBackupDraft()
|
||||
setOpen(false)
|
||||
handleNodesCancelSelected()
|
||||
|
||||
@ -89,10 +89,19 @@ const ViewWorkflowHistory = () => {
|
||||
|
||||
const calculateChangeList: ChangeHistoryList = useMemo(() => {
|
||||
const filterList = (list: any, startIndex = 0, reverse = false) => list.map((state: Partial<WorkflowHistoryState>, index: number) => {
|
||||
const nodes = (state.nodes || store.getState().nodes) || []
|
||||
const nodeId = state?.workflowHistoryEventMeta?.nodeId
|
||||
const targetTitle = nodes.find(n => n.id === nodeId)?.data?.title ?? ''
|
||||
return {
|
||||
label: state.workflowHistoryEvent && getHistoryLabel(state.workflowHistoryEvent),
|
||||
index: reverse ? list.length - 1 - index - startIndex : index - startIndex,
|
||||
state,
|
||||
state: {
|
||||
...state,
|
||||
workflowHistoryEventMeta: state.workflowHistoryEventMeta ? {
|
||||
...state.workflowHistoryEventMeta,
|
||||
nodeTitle: state.workflowHistoryEventMeta.nodeTitle || targetTitle,
|
||||
} : undefined,
|
||||
},
|
||||
}
|
||||
}).filter(Boolean)
|
||||
|
||||
@ -110,6 +119,12 @@ const ViewWorkflowHistory = () => {
|
||||
}
|
||||
}, [futureStates, getHistoryLabel, pastStates, store])
|
||||
|
||||
const composeHistoryItemLabel = useCallback((nodeTitle: string | undefined, baseLabel: string) => {
|
||||
if (!nodeTitle)
|
||||
return baseLabel
|
||||
return `${nodeTitle} ${baseLabel}`
|
||||
}, [])
|
||||
|
||||
return (
|
||||
(
|
||||
<PortalToFollowElem
|
||||
@ -197,7 +212,10 @@ const ViewWorkflowHistory = () => {
|
||||
'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')})
|
||||
{composeHistoryItemLabel(
|
||||
item?.state?.workflowHistoryEventMeta?.nodeTitle,
|
||||
item?.label || t('workflow.changeHistory.sessionStart'),
|
||||
)} ({calculateStepLabel(item?.index)}{item?.index === currentHistoryStateIndex && t('workflow.changeHistory.currentState')})
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -222,7 +240,10 @@ const ViewWorkflowHistory = () => {
|
||||
'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary',
|
||||
)}
|
||||
>
|
||||
{item?.label || t('workflow.changeHistory.sessionStart')} ({calculateStepLabel(item?.index)})
|
||||
{composeHistoryItemLabel(
|
||||
item?.state?.workflowHistoryEventMeta?.nodeTitle,
|
||||
item?.label || t('workflow.changeHistory.sessionStart'),
|
||||
)} ({calculateStepLabel(item?.index)})
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user