mirror of https://github.com/langgenius/dify.git
Fix test run shortcut consistency and improve dropdown styling (#24849)
This commit is contained in:
parent
adc7134af5
commit
6d307cc9fc
|
|
@ -3,24 +3,20 @@ import {
|
|||
useCallback,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useEdges, useNodes, useStore as useReactflowStore } from 'reactflow'
|
||||
import { useStore as useReactflowStore } from 'reactflow'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
useStore,
|
||||
useWorkflowStore,
|
||||
} from '@/app/components/workflow/store'
|
||||
import {
|
||||
useChecklist,
|
||||
useChecklistBeforePublish,
|
||||
useNodesReadOnly,
|
||||
useNodesSyncDraft,
|
||||
useWorkflowRunValidation,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import AppPublisher from '@/app/components/app/app-publisher'
|
||||
import { useFeatures } from '@/app/components/base/features/hooks'
|
||||
import type {
|
||||
CommonEdgeType,
|
||||
CommonNodeType,
|
||||
} from '@/app/components/workflow/types'
|
||||
import {
|
||||
BlockEnum,
|
||||
InputVarType,
|
||||
|
|
@ -81,17 +77,13 @@ const AppPublisherTrigger = () => {
|
|||
}, [appID, setAppDetail])
|
||||
|
||||
const { mutateAsync: publishWorkflow } = usePublishWorkflow(appID!)
|
||||
const nodes = useNodes<CommonNodeType>()
|
||||
const edges = useEdges<CommonEdgeType>()
|
||||
const needWarningNodes = useChecklist(nodes, edges)
|
||||
const { validateBeforeRun } = useWorkflowRunValidation()
|
||||
|
||||
const updatePublishedWorkflow = useInvalidateAppWorkflow()
|
||||
const onPublish = useCallback(async (params?: PublishWorkflowParams) => {
|
||||
// First check if there are any items in the checklist
|
||||
if (needWarningNodes.length > 0) {
|
||||
notify({ type: 'error', message: t('workflow.panel.checklistTip') })
|
||||
if (!validateBeforeRun())
|
||||
throw new Error('Checklist has unresolved items')
|
||||
}
|
||||
|
||||
// Then perform the detailed validation
|
||||
if (await handleCheckBeforePublish()) {
|
||||
|
|
@ -112,7 +104,7 @@ const AppPublisherTrigger = () => {
|
|||
else {
|
||||
throw new Error('Checklist failed')
|
||||
}
|
||||
}, [needWarningNodes, handleCheckBeforePublish, publishWorkflow, notify, t, updatePublishedWorkflow, appID, updateAppDetail, invalidateAppTriggers, workflowStore, resetWorkflowVersionHistory])
|
||||
}, [validateBeforeRun, handleCheckBeforePublish, publishWorkflow, updatePublishedWorkflow, appID, updateAppDetail, invalidateAppTriggers, workflowStore, resetWorkflowVersionHistory])
|
||||
|
||||
const onPublisherToggle = useCallback((state: boolean) => {
|
||||
if (state)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { FC } from 'react'
|
||||
import { memo } from 'react'
|
||||
import { memo, useEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiLoader2Line,
|
||||
|
|
@ -10,12 +10,13 @@ import {
|
|||
useIsChatMode,
|
||||
useNodesReadOnly,
|
||||
useWorkflowRun,
|
||||
useWorkflowRunValidation,
|
||||
useWorkflowStartRun,
|
||||
} from '../hooks'
|
||||
import { WorkflowRunningStatus } from '../types'
|
||||
import ViewHistory from './view-history'
|
||||
import Checklist from './checklist'
|
||||
import TestRunDropdown from './test-run-dropdown'
|
||||
import TestRunDropdown, { type TestRunDropdownRef } from './test-run-dropdown'
|
||||
import type { TriggerOption } from './test-run-dropdown'
|
||||
import { useDynamicTestRunOptions } from '../hooks/use-dynamic-test-run-options'
|
||||
import cn from '@/utils/classnames'
|
||||
|
|
@ -31,20 +32,37 @@ 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 testRunDropdownRef = useRef<TestRunDropdownRef>(null)
|
||||
|
||||
useEffect(() => {
|
||||
// @ts-expect-error - Dynamic property for backward compatibility with keyboard shortcuts
|
||||
window._toggleTestRunDropdown = () => {
|
||||
testRunDropdownRef.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 {
|
||||
// TODO: Implement trigger-specific execution logic for schedule, webhook, plugin types
|
||||
// Placeholder for trigger-specific execution logic for schedule, webhook, plugin types
|
||||
console.log('TODO: Handle trigger execution for type:', option.type, 'nodeId:', option.nodeId)
|
||||
}
|
||||
}
|
||||
|
|
@ -71,7 +89,11 @@ const RunMode = memo(() => {
|
|||
</div>
|
||||
)
|
||||
: (
|
||||
<TestRunDropdown options={dynamicOptions} onSelect={handleTriggerSelect}>
|
||||
<TestRunDropdown
|
||||
ref={testRunDropdownRef}
|
||||
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',
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import type { FC } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import ShortcutsName from '../shortcuts-name'
|
||||
|
||||
export type TriggerOption = {
|
||||
id: string
|
||||
|
|
@ -28,20 +28,28 @@ type TestRunDropdownProps = {
|
|||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const TestRunDropdown: FC<TestRunDropdownProps> = ({
|
||||
export type TestRunDropdownRef = {
|
||||
toggle: () => void
|
||||
}
|
||||
|
||||
const TestRunDropdown = forwardRef<TestRunDropdownRef, TestRunDropdownProps>(({
|
||||
options,
|
||||
onSelect,
|
||||
children,
|
||||
}) => {
|
||||
}, ref) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
toggle: () => setOpen(prev => !prev),
|
||||
}))
|
||||
|
||||
const handleSelect = (option: TriggerOption) => {
|
||||
onSelect(option)
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const renderOption = (option: TriggerOption, numberDisplay: string) => (
|
||||
const renderOption = (option: TriggerOption, shortcutKey: string) => (
|
||||
<div
|
||||
key={option.id}
|
||||
className='system-md-regular flex cursor-pointer items-center rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'
|
||||
|
|
@ -53,9 +61,7 @@ const TestRunDropdown: FC<TestRunDropdownProps> = ({
|
|||
</div>
|
||||
<span className='ml-2 truncate'>{option.name}</span>
|
||||
</div>
|
||||
<div className='ml-2 flex h-4 w-4 shrink-0 items-center justify-center rounded bg-state-base-hover-alt text-xs font-medium text-text-tertiary'>
|
||||
{numberDisplay}
|
||||
</div>
|
||||
<ShortcutsName keys={[shortcutKey]} className="ml-2" textColor="secondary" />
|
||||
</div>
|
||||
)
|
||||
|
||||
|
|
@ -99,6 +105,8 @@ const TestRunDropdown: FC<TestRunDropdownProps> = ({
|
|||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
TestRunDropdown.displayName = 'TestRunDropdown'
|
||||
|
||||
export default TestRunDropdown
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ import {
|
|||
useRef,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import { useEdges, useNodes, useStoreApi } from 'reactflow'
|
||||
import type {
|
||||
CommonEdgeType,
|
||||
CommonNodeType,
|
||||
Edge,
|
||||
Node,
|
||||
|
|
@ -326,3 +327,24 @@ export const useChecklistBeforePublish = () => {
|
|||
handleCheckBeforePublish,
|
||||
}
|
||||
}
|
||||
|
||||
export const useWorkflowRunValidation = () => {
|
||||
const { t } = useTranslation()
|
||||
const nodes = useNodes<CommonNodeType>()
|
||||
const edges = useEdges<CommonEdgeType>()
|
||||
const needWarningNodes = useChecklist(nodes, edges)
|
||||
const { notify } = useToastContext()
|
||||
|
||||
const validateBeforeRun = useCallback(() => {
|
||||
if (needWarningNodes.length > 0) {
|
||||
notify({ type: 'error', message: t('workflow.panel.checklistTip') })
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, [needWarningNodes, notify, t])
|
||||
|
||||
return {
|
||||
validateBeforeRun,
|
||||
hasValidationErrors: needWarningNodes.length > 0,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import {
|
|||
useWorkflowCanvasMaximize,
|
||||
useWorkflowMoveMode,
|
||||
useWorkflowOrganize,
|
||||
useWorkflowStartRun,
|
||||
} from '.'
|
||||
|
||||
export const useShortcuts = (): void => {
|
||||
|
|
@ -28,7 +27,6 @@ export const useShortcuts = (): void => {
|
|||
dimOtherNodes,
|
||||
undimAllNodes,
|
||||
} = useNodesInteractions()
|
||||
const { handleStartWorkflowRun } = useWorkflowStartRun()
|
||||
const { shortcutsEnabled: workflowHistoryShortcutsEnabled } = useWorkflowHistoryStore()
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const { handleEdgeDelete } = useEdgesInteractions()
|
||||
|
|
@ -61,9 +59,8 @@ export const useShortcuts = (): void => {
|
|||
}
|
||||
|
||||
const shouldHandleShortcut = useCallback((e: KeyboardEvent) => {
|
||||
const { showFeaturesPanel } = workflowStore.getState()
|
||||
return !showFeaturesPanel && !isEventTargetInputArea(e.target as HTMLElement)
|
||||
}, [workflowStore])
|
||||
return !isEventTargetInputArea(e.target as HTMLElement)
|
||||
}, [])
|
||||
|
||||
useKeyPress(['delete', 'backspace'], (e) => {
|
||||
if (shouldHandleShortcut(e)) {
|
||||
|
|
@ -99,7 +96,11 @@ export const useShortcuts = (): void => {
|
|||
useKeyPress(`${getKeyboardKeyCodeBySystem('alt')}.r`, (e) => {
|
||||
if (shouldHandleShortcut(e)) {
|
||||
e.preventDefault()
|
||||
handleStartWorkflowRun()
|
||||
// @ts-expect-error - Dynamic property added by run-and-history component
|
||||
if (window._toggleTestRunDropdown) {
|
||||
// @ts-expect-error - Dynamic property added by run-and-history component
|
||||
window._toggleTestRunDropdown()
|
||||
}
|
||||
}
|
||||
}, { exactMatch: true, useCapture: true })
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue