mirror of https://github.com/langgenius/dify.git
modify test run panel
This commit is contained in:
parent
5adbcacc52
commit
36c3774fac
|
|
@ -44,7 +44,6 @@ const RunMode = memo(() => {
|
|||
|
||||
const handleClick = useCallback(async () => {
|
||||
const {
|
||||
setShowInputsPanel,
|
||||
workflowRunningData,
|
||||
} = workflowStore.getState()
|
||||
|
||||
|
|
@ -63,7 +62,10 @@ const RunMode = memo(() => {
|
|||
handleRun({ inputs: {}, files: [] })
|
||||
}
|
||||
else {
|
||||
setShowInputsPanel(true)
|
||||
workflowStore.setState({
|
||||
historyWorkflowData: undefined,
|
||||
showInputsPanel: true,
|
||||
})
|
||||
handleSyncWorkflowDraft(true)
|
||||
}
|
||||
}, [
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ const ViewHistory = () => {
|
|||
onClick={() => {
|
||||
workflowStore.setState({
|
||||
historyWorkflowData: item,
|
||||
showInputsPanel: false,
|
||||
})
|
||||
handleBackupDraft()
|
||||
setOpen(false)
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ export const useWorkflowRun = () => {
|
|||
workflowStore.setState({
|
||||
workflowRunningData: undefined,
|
||||
historyWorkflowData: undefined,
|
||||
showInputsPanel: false,
|
||||
})
|
||||
}
|
||||
else {
|
||||
|
|
@ -155,6 +156,17 @@ export const useWorkflowRun = () => {
|
|||
|
||||
let prevNodeId = ''
|
||||
|
||||
const {
|
||||
workflowRunningData,
|
||||
setWorkflowRunningData,
|
||||
} = workflowStore.getState()
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
draft.result = {
|
||||
...draft?.result,
|
||||
status: WorkflowRunningStatus.Running,
|
||||
}
|
||||
}))
|
||||
|
||||
ssePost(
|
||||
url,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import { useStore } from '../store'
|
|||
import { useIsChatMode } from '../hooks'
|
||||
import DebugAndPreview from './debug-and-preview'
|
||||
import Record from './record'
|
||||
import InputsPanel from './inputs-panel'
|
||||
import WorkflowPreview from './workflow-preview'
|
||||
import ChatRecord from './chat-record'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
|
|
@ -30,11 +29,12 @@ const Panel: FC = () => {
|
|||
showWorkflowPreview,
|
||||
} = useMemo(() => {
|
||||
return {
|
||||
showNodePanel: !!selectedNode && !workflowRunningData && !historyWorkflowData,
|
||||
showNodePanel: !!selectedNode && !workflowRunningData && !historyWorkflowData && !showInputsPanel,
|
||||
showDebugAndPreviewPanel: isChatMode && workflowRunningData && !historyWorkflowData,
|
||||
showWorkflowPreview: !isChatMode && workflowRunningData && !historyWorkflowData,
|
||||
showWorkflowPreview: !isChatMode && !historyWorkflowData && (workflowRunningData || showInputsPanel),
|
||||
}
|
||||
}, [
|
||||
showInputsPanel,
|
||||
selectedNode,
|
||||
isChatMode,
|
||||
workflowRunningData,
|
||||
|
|
@ -71,11 +71,6 @@ const Panel: FC = () => {
|
|||
<DebugAndPreview />
|
||||
)
|
||||
}
|
||||
{
|
||||
showInputsPanel && (
|
||||
<InputsPanel />
|
||||
)
|
||||
}
|
||||
{
|
||||
showWorkflowPreview && (
|
||||
<WorkflowPreview />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
|
@ -9,6 +8,7 @@ import FormItem from '../nodes/_base/components/before-run-form/form-item'
|
|||
import {
|
||||
BlockEnum,
|
||||
InputVarType,
|
||||
WorkflowRunningStatus,
|
||||
} from '../types'
|
||||
import {
|
||||
useStore,
|
||||
|
|
@ -19,13 +19,18 @@ import type { StartNodeType } from '../nodes/start/types'
|
|||
import Button from '@/app/components/base/button'
|
||||
import { useFeatures } from '@/app/components/base/features/hooks'
|
||||
|
||||
const InputsPanel = () => {
|
||||
type Props = {
|
||||
onRun: () => void
|
||||
}
|
||||
|
||||
const InputsPanel = ({ onRun }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const fileSettings = useFeatures(s => s.features.file)
|
||||
const nodes = useNodes<StartNodeType>()
|
||||
const inputs = useStore(s => s.inputs)
|
||||
const files = useStore(s => s.files)
|
||||
const workflowRunningData = useStore(s => s.workflowRunningData)
|
||||
const {
|
||||
handleRun,
|
||||
handleRunSetting,
|
||||
|
|
@ -64,56 +69,42 @@ const InputsPanel = () => {
|
|||
}
|
||||
}
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
workflowStore.setState({ showInputsPanel: false })
|
||||
}, [workflowStore])
|
||||
|
||||
const doRun = () => {
|
||||
handleCancel()
|
||||
onRun()
|
||||
handleRunSetting()
|
||||
handleRun({ inputs, files })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='absolute top-0 right-2 w-[420px] h-full z-[11] overflow-y-auto'>
|
||||
<div className='pb-2 rounded-2xl border-[0.5px] border-gray-200 bg-white shadow-xl'>
|
||||
<div className='flex items-center pt-3 px-4 h-[44px] text-base font-semibold text-gray-900'>
|
||||
{t('workflow.singleRun.testRun')}
|
||||
</div>
|
||||
<div className='px-4 pb-2'>
|
||||
{
|
||||
variables.map(variable => (
|
||||
<div
|
||||
key={variable.variable}
|
||||
className='mb-2 last-of-type:mb-0'
|
||||
>
|
||||
<FormItem
|
||||
className='!block'
|
||||
payload={variable}
|
||||
value={inputs[variable.variable]}
|
||||
onChange={v => handleValueChange(variable.variable, v)}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<div className='flex items-center justify-between px-4 py-2'>
|
||||
<Button
|
||||
className='py-0 w-[190px] h-8 rounded-lg border-[0.5px] border-gray-200 shadow-xs text-[13px] font-medium text-gray-700'
|
||||
onClick={handleCancel}
|
||||
>
|
||||
{t('common.operation.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type='primary'
|
||||
className='py-0 w-[190px] h-8 rounded-lg text-[13px] font-medium'
|
||||
onClick={doRun}
|
||||
>
|
||||
{t('workflow.singleRun.startRun')}
|
||||
</Button>
|
||||
</div>
|
||||
<>
|
||||
<div className='px-4 pb-2'>
|
||||
{
|
||||
variables.map(variable => (
|
||||
<div
|
||||
key={variable.variable}
|
||||
className='mb-2 last-of-type:mb-0'
|
||||
>
|
||||
<FormItem
|
||||
className='!block'
|
||||
payload={variable}
|
||||
value={inputs[variable.variable]}
|
||||
onChange={v => handleValueChange(variable.variable, v)}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-center justify-between px-4 py-2'>
|
||||
<Button
|
||||
type='primary'
|
||||
disabled={workflowRunningData?.result?.status === WorkflowRunningStatus.Running}
|
||||
className='py-0 w-full h-8 rounded-lg text-[13px] font-medium'
|
||||
onClick={doRun}
|
||||
>
|
||||
{t('workflow.singleRun.startRun')}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,13 +7,20 @@ import { useTranslation } from 'react-i18next'
|
|||
import OutputPanel from '../run/output-panel'
|
||||
import ResultPanel from '../run/result-panel'
|
||||
import TracingPanel from '../run/tracing-panel'
|
||||
import { useStore } from '../store'
|
||||
import { useStore, useWorkflowStore } from '../store'
|
||||
import {
|
||||
WorkflowRunningStatus,
|
||||
} from '../types'
|
||||
import InputsPanel from './inputs-panel'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { XClose } from '@/app/components/base/icons/src/vender/line/general'
|
||||
|
||||
const WorkflowPreview = () => {
|
||||
const { t } = useTranslation()
|
||||
const [currentTab, setCurrentTab] = useState<string>('TRACING')
|
||||
const workflowStore = useWorkflowStore()
|
||||
const showInputsPanel = useStore(s => s.showInputsPanel)
|
||||
const workflowRunningData = useStore(s => s.workflowRunningData)
|
||||
const [currentTab, setCurrentTab] = useState<string>(showInputsPanel ? 'INPUT' : 'TRACING')
|
||||
|
||||
const switchTab = async (tab: string) => {
|
||||
setCurrentTab(tab)
|
||||
|
|
@ -24,44 +31,80 @@ const WorkflowPreview = () => {
|
|||
flex flex-col w-[420px] h-full rounded-2xl border-[0.5px] border-gray-200 shadow-xl bg-white
|
||||
`}>
|
||||
<div className='flex items-center justify-between p-4 pb-1 text-base font-semibold text-gray-900'>
|
||||
Test Run#{workflowRunningData?.result.sequence_number}
|
||||
{`Test Run${!workflowRunningData?.result.sequence_number ? '' : `#${workflowRunningData?.result.sequence_number}`}`}
|
||||
{showInputsPanel && workflowRunningData?.result?.status !== WorkflowRunningStatus.Running && (
|
||||
<div className='p-1 cursor-pointer' onClick={() => {
|
||||
workflowStore.setState({
|
||||
showInputsPanel: false,
|
||||
workflowRunningData: undefined,
|
||||
})
|
||||
}}>
|
||||
<XClose className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='grow relative flex flex-col'>
|
||||
<div className='shrink-0 flex items-center px-4 border-b-[0.5px] border-[rgba(0,0,0,0.05)]'>
|
||||
{showInputsPanel && (
|
||||
<div
|
||||
className={cn(
|
||||
'mr-6 py-3 border-b-2 border-transparent text-[13px] font-semibold leading-[18px] text-gray-400 cursor-pointer',
|
||||
currentTab === 'INPUT' && '!border-[rgb(21,94,239)] text-gray-700',
|
||||
)}
|
||||
onClick={() => switchTab('INPUT')}
|
||||
>{t('runLog.input')}</div>
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
'mr-6 py-3 border-b-2 border-transparent text-[13px] font-semibold leading-[18px] text-gray-400 cursor-pointer',
|
||||
currentTab === 'RESULT' && '!border-[rgb(21,94,239)] text-gray-700',
|
||||
!workflowRunningData && 'opacity-30 !cursor-not-allowed',
|
||||
)}
|
||||
onClick={() => switchTab('RESULT')}
|
||||
onClick={() => {
|
||||
if (!workflowRunningData)
|
||||
return
|
||||
switchTab('RESULT')
|
||||
}}
|
||||
>{t('runLog.result')}</div>
|
||||
<div
|
||||
className={cn(
|
||||
'mr-6 py-3 border-b-2 border-transparent text-[13px] font-semibold leading-[18px] text-gray-400 cursor-pointer',
|
||||
currentTab === 'DETAIL' && '!border-[rgb(21,94,239)] text-gray-700',
|
||||
!workflowRunningData && 'opacity-30 !cursor-not-allowed',
|
||||
)}
|
||||
onClick={() => switchTab('DETAIL')}
|
||||
onClick={() => {
|
||||
if (!workflowRunningData)
|
||||
return
|
||||
switchTab('DETAIL')
|
||||
}}
|
||||
>{t('runLog.detail')}</div>
|
||||
<div
|
||||
className={cn(
|
||||
'mr-6 py-3 border-b-2 border-transparent text-[13px] font-semibold leading-[18px] text-gray-400 cursor-pointer',
|
||||
currentTab === 'TRACING' && '!border-[rgb(21,94,239)] text-gray-700',
|
||||
!workflowRunningData && 'opacity-30 !cursor-not-allowed',
|
||||
)}
|
||||
onClick={() => switchTab('TRACING')}
|
||||
onClick={() => {
|
||||
if (!workflowRunningData)
|
||||
return
|
||||
switchTab('TRACING')
|
||||
}}
|
||||
>{t('runLog.tracing')}</div>
|
||||
</div>
|
||||
<div className={cn('grow bg-white h-0 overflow-y-auto rounded-b-2xl', currentTab !== 'DETAIL' && '!bg-gray-50')}>
|
||||
<div className={cn(
|
||||
'grow bg-white h-0 overflow-y-auto rounded-b-2xl',
|
||||
(currentTab === 'RESULT' || currentTab === 'TRACING') && '!bg-gray-50',
|
||||
)}>
|
||||
{currentTab === 'INPUT' && (
|
||||
<InputsPanel onRun={() => switchTab('RESULT')} />
|
||||
)}
|
||||
{currentTab === 'RESULT' && (
|
||||
<OutputPanel
|
||||
isRunning={workflowRunningData?.result?.status === WorkflowRunningStatus.Running || !workflowRunningData?.result}
|
||||
outputs={workflowRunningData?.result?.outputs}
|
||||
error={workflowRunningData?.result?.error}
|
||||
/>
|
||||
)}
|
||||
{currentTab === 'RESULT' && !workflowRunningData?.result && (
|
||||
<div className='flex h-full items-center justify-center bg-white'>
|
||||
<Loading />
|
||||
</div>
|
||||
)}
|
||||
{currentTab === 'DETAIL' && (
|
||||
<ResultPanel
|
||||
inputs={workflowRunningData?.result?.inputs}
|
||||
|
|
|
|||
|
|
@ -3,24 +3,34 @@ import type { FC } from 'react'
|
|||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||
import { Markdown } from '@/app/components/base/markdown'
|
||||
import LoadingAnim from '@/app/components/app/chat/loading-anim'
|
||||
|
||||
type OutputPanelProps = {
|
||||
isRunning?: boolean
|
||||
outputs?: any
|
||||
error?: string
|
||||
}
|
||||
|
||||
const OutputPanel: FC<OutputPanelProps> = ({
|
||||
isRunning,
|
||||
outputs,
|
||||
error,
|
||||
}) => {
|
||||
return (
|
||||
<div className='bg-gray-50 py-2'>
|
||||
{error && (
|
||||
<div className='px-3 py-[10px] rounded-lg !bg-[#fef3f2] border-[0.5px] border-[rbga(0,0,0,0.05)] shadow-xs'>
|
||||
<div className='text-xs leading-[18px] text-[#d92d20]'>{error}</div>
|
||||
{isRunning && (
|
||||
<div className='pt-4 pl-[26px]'>
|
||||
<LoadingAnim type='text' />
|
||||
</div>
|
||||
)}
|
||||
{!outputs && (
|
||||
{!isRunning && error && (
|
||||
<div className='px-4'>
|
||||
<div className='px-3 py-[10px] rounded-lg !bg-[#fef3f2] border-[0.5px] border-[rbga(0,0,0,0.05)] shadow-xs'>
|
||||
<div className='text-xs leading-[18px] text-[#d92d20]'>{error}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!isRunning && !outputs && (
|
||||
<div className='px-4 py-2'>
|
||||
<Markdown content='No Output' />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const translation = {
|
||||
input: 'INPUT',
|
||||
result: 'RESULT',
|
||||
detail: 'DETAIL',
|
||||
tracing: 'TRACING',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const translation = {
|
||||
input: '输入',
|
||||
result: '结果',
|
||||
detail: '详情',
|
||||
tracing: '追踪',
|
||||
|
|
|
|||
Loading…
Reference in New Issue