mirror of
https://github.com/langgenius/dify.git
synced 2026-04-28 14:38:06 +08:00
Made-with: Cursor # Conflicts: # api/controllers/console/app/workflow_draft_variable.py # api/core/agent/cot_agent_runner.py # api/core/agent/cot_chat_agent_runner.py # api/core/agent/cot_completion_agent_runner.py # api/core/agent/fc_agent_runner.py # api/core/app/apps/advanced_chat/app_generator.py # api/core/app/apps/advanced_chat/app_runner.py # api/core/app/apps/agent_chat/app_runner.py # api/core/app/apps/workflow/app_generator.py # api/core/app/apps/workflow/app_runner.py # api/core/app/entities/app_invoke_entities.py # api/core/app/entities/queue_entities.py # api/core/llm_generator/output_parser/structured_output.py # api/core/workflow/workflow_entry.py # api/dify_graph/context/__init__.py # api/dify_graph/entities/tool_entities.py # api/dify_graph/file/file_manager.py # api/dify_graph/graph_engine/response_coordinator/coordinator.py # api/dify_graph/graph_events/node.py # api/dify_graph/node_events/node.py # api/dify_graph/nodes/agent/agent_node.py # api/dify_graph/nodes/llm/entities.py # api/dify_graph/nodes/llm/llm_utils.py # api/dify_graph/nodes/llm/node.py # api/dify_graph/nodes/question_classifier/question_classifier_node.py # api/dify_graph/runtime/graph_runtime_state.py # api/dify_graph/variables/segments.py # api/factories/variable_factory.py # api/services/variable_truncator.py # api/tests/unit_tests/utils/structured_output_parser/test_structured_output_parser.py # api/uv.lock # web/app/components/app-sidebar/app-info.tsx # web/app/components/app-sidebar/app-sidebar-dropdown.tsx # web/app/components/app/create-app-modal/index.spec.tsx # web/app/components/apps/__tests__/list.spec.tsx # web/app/components/apps/app-card.tsx # web/app/components/apps/list.tsx # web/app/components/header/account-dropdown/compliance.tsx # web/app/components/header/account-dropdown/index.tsx # web/app/components/header/account-dropdown/support.tsx # web/app/components/workflow-app/components/workflow-onboarding-modal/index.tsx # web/app/components/workflow/panel/debug-and-preview/hooks.ts # web/contract/console/apps.ts # web/contract/router.ts # web/eslint-suppressions.json # web/next.config.ts # web/pnpm-lock.yaml
162 lines
6.2 KiB
TypeScript
162 lines
6.2 KiB
TypeScript
import type { TestRunMenuRef, TriggerOption } from './test-run-menu'
|
|
import { RiLoader2Line, RiPlayLargeLine } from '@remixicon/react'
|
|
import * as React from 'react'
|
|
import { useCallback, useEffect, useRef } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { trackEvent } from '@/app/components/base/amplitude'
|
|
import { StopCircle } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
|
import { useToastContext } from '@/app/components/base/toast/context'
|
|
import { useWorkflowRun, useWorkflowRunValidation, useWorkflowStartRun } from '@/app/components/workflow/hooks'
|
|
import ShortcutsName from '@/app/components/workflow/shortcuts-name'
|
|
import { useStore } from '@/app/components/workflow/store'
|
|
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
|
import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types'
|
|
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
|
import { cn } from '@/utils/classnames'
|
|
import { useDynamicTestRunOptions } from '../hooks/use-dynamic-test-run-options'
|
|
import TestRunMenu, { TriggerType } from './test-run-menu'
|
|
|
|
type RunModeProps = {
|
|
text?: string
|
|
}
|
|
|
|
const RunMode = ({
|
|
text,
|
|
}: RunModeProps) => {
|
|
const { t } = useTranslation()
|
|
const {
|
|
handleWorkflowStartRunInWorkflow,
|
|
handleWorkflowTriggerScheduleRunInWorkflow,
|
|
handleWorkflowTriggerWebhookRunInWorkflow,
|
|
handleWorkflowTriggerPluginRunInWorkflow,
|
|
handleWorkflowRunAllTriggersInWorkflow,
|
|
} = useWorkflowStartRun()
|
|
const { handleStopRun } = useWorkflowRun()
|
|
const { warningNodes } = useWorkflowRunValidation()
|
|
const workflowRunningData = useStore(s => s.workflowRunningData)
|
|
const isListening = useStore(s => s.isListening)
|
|
|
|
const status = workflowRunningData?.result.status
|
|
const isRunning = status === WorkflowRunningStatus.Running || isListening
|
|
|
|
const dynamicOptions = useDynamicTestRunOptions()
|
|
const testRunMenuRef = useRef<TestRunMenuRef>(null)
|
|
const { notify } = useToastContext()
|
|
|
|
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 = useCallback((option: TriggerOption) => {
|
|
// Validate checklist before running any workflow
|
|
let isValid: boolean = true
|
|
warningNodes.forEach((node) => {
|
|
if (node.id === option.nodeId)
|
|
isValid = false
|
|
})
|
|
if (!isValid) {
|
|
notify({ type: 'error', message: t('panel.checklistTip', { ns: 'workflow' }) })
|
|
return
|
|
}
|
|
|
|
if (option.type === TriggerType.UserInput) {
|
|
handleWorkflowStartRunInWorkflow()
|
|
trackEvent('app_start_action_time', { action_type: 'user_input' })
|
|
}
|
|
else if (option.type === TriggerType.Schedule) {
|
|
handleWorkflowTriggerScheduleRunInWorkflow(option.nodeId)
|
|
trackEvent('app_start_action_time', { action_type: 'schedule' })
|
|
}
|
|
else if (option.type === TriggerType.Webhook) {
|
|
if (option.nodeId)
|
|
handleWorkflowTriggerWebhookRunInWorkflow({ nodeId: option.nodeId })
|
|
trackEvent('app_start_action_time', { action_type: 'webhook' })
|
|
}
|
|
else if (option.type === TriggerType.Plugin) {
|
|
if (option.nodeId)
|
|
handleWorkflowTriggerPluginRunInWorkflow(option.nodeId)
|
|
trackEvent('app_start_action_time', { action_type: 'plugin' })
|
|
}
|
|
else if (option.type === TriggerType.All) {
|
|
const targetNodeIds = option.relatedNodeIds?.filter(Boolean)
|
|
if (targetNodeIds && targetNodeIds.length > 0)
|
|
handleWorkflowRunAllTriggersInWorkflow(targetNodeIds)
|
|
trackEvent('app_start_action_time', { action_type: 'all' })
|
|
}
|
|
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)
|
|
}
|
|
}, [warningNodes, notify, t, handleWorkflowStartRunInWorkflow, handleWorkflowTriggerScheduleRunInWorkflow, handleWorkflowTriggerWebhookRunInWorkflow, handleWorkflowTriggerPluginRunInWorkflow, handleWorkflowRunAllTriggersInWorkflow])
|
|
|
|
const { eventEmitter } = useEventEmitterContextContext()
|
|
eventEmitter?.useSubscription((v: any) => {
|
|
if (v.type === EVENT_WORKFLOW_STOP)
|
|
handleStop()
|
|
})
|
|
|
|
return (
|
|
<div className="flex items-center gap-x-px">
|
|
{
|
|
isRunning
|
|
? (
|
|
<button
|
|
type="button"
|
|
className={cn(
|
|
'flex h-7 cursor-not-allowed items-center gap-x-1 rounded-l-md bg-state-accent-hover px-1.5 text-text-accent system-xs-medium',
|
|
)}
|
|
disabled={true}
|
|
>
|
|
<RiLoader2Line className="mr-1 size-4 animate-spin" />
|
|
{isListening ? t('common.listening', { ns: 'workflow' }) : t('common.running', { ns: 'workflow' })}
|
|
</button>
|
|
)
|
|
: (
|
|
<TestRunMenu
|
|
ref={testRunMenuRef}
|
|
options={dynamicOptions}
|
|
onSelect={handleTriggerSelect}
|
|
>
|
|
<div
|
|
className={cn(
|
|
'flex h-7 cursor-pointer items-center gap-x-1 rounded-md px-1.5 text-text-accent system-xs-medium hover:bg-state-accent-hover',
|
|
)}
|
|
style={{ userSelect: 'none' }}
|
|
>
|
|
<RiPlayLargeLine className="mr-1 size-4" />
|
|
{text ?? t('common.run', { ns: 'workflow' })}
|
|
<ShortcutsName keys={['alt', 'R']} textColor="secondary" />
|
|
</div>
|
|
</TestRunMenu>
|
|
)
|
|
}
|
|
{
|
|
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)
|