Merge branch 'feat/plugins' of https://github.com/langgenius/dify into feat/plugins

pull/12372/head
Yi 1 year ago
commit c04a89d5b1

@ -191,7 +191,7 @@ const SettingBuiltInTool: FC<Props> = ({
<OrgInfo <OrgInfo
packageNameClassName='w-auto' packageNameClassName='w-auto'
orgName={collection.author} orgName={collection.author}
packageName={collection.name} packageName={collection.name.split('/').pop() || ''}
/> />
</div> </div>
<div className='mt-1 text-text-primary system-md-semibold'>{currTool?.label[language]}</div> <div className='mt-1 text-text-primary system-md-semibold'>{currTool?.label[language]}</div>

@ -0,0 +1,29 @@
import { RiArrowRightLine } from '@remixicon/react'
type AgentLogTriggerProps = {
onDetail?: () => void
}
const AgentLogTrigger = ({
onDetail,
}: AgentLogTriggerProps) => {
return (
<div className='bg-components-button-tertiary-bg rounded-[10px]'>
<div className='flex items-center px-3 pt-2 system-2xs-medium-uppercase text-text-tertiary'>
Agent strategy
</div>
<div className='flex items-center pl-3 pt-1 pr-2 pb-1.5'>
<div className='shrink-0 w-5 h-5'></div>
<div className='grow mx-0.5 px-1 system-xs-medium text-text-secondary'></div>
<div
className='shrink-0 flex items-center px-[1px] system-xs-regular-uppercase text-text-tertiary cursor-pointer'
onClick={onDetail}
>
Detail
<RiArrowRightLine className='ml-0.5 w-3.5 h-3.5' />
</div>
</div>
</div>
)
}
export default AgentLogTrigger

@ -0,0 +1,43 @@
import Button from '@/app/components/base/button'
import { RiArrowLeftLine } from '@remixicon/react'
import TracingPanel from '../tracing-panel'
type AgentResultPanelProps = {
onBack: () => void
}
const AgentResultPanel = ({
onBack,
}: AgentResultPanelProps) => {
return (
<div className='overflow-y-auto'>
<div className='flex items-center p-1 pr-3 h-8'>
<Button
className='shrink-0 px-[5px]'
size='small'
variant='ghost-accent'
onClick={onBack}
>
<RiArrowLeftLine className='mr-1 w-3.5 h-3.5' />
Back
</Button>
<div className='shrink-0 mx-0.5 system-xs-regular text-divider-deep'>/</div>
<div className='grow px-[5px] system-xs-medium-uppercase'>
Agent strategy
</div>
<Button
className='px-[5px]'
size='small'
variant='ghost-accent'
onClick={onBack}
>
close
</Button>
</div>
<TracingPanel
list={[]}
/>
</div>
)
}
export default AgentResultPanel

@ -0,0 +1,45 @@
import Button from '@/app/components/base/button'
import { RiArrowLeftLine } from '@remixicon/react'
import TracingPanel from '../tracing-panel'
type ToolCallResultPanelProps = {
onBack: () => void
onClose: () => void
}
const ToolCallResultPanel = ({
onBack,
onClose,
}: ToolCallResultPanelProps) => {
return (
<div className='overflow-y-auto'>
<div className='flex items-center p-1 pr-3 h-8'>
<Button
className='shrink-0 px-[5px]'
size='small'
variant='ghost-accent'
onClick={onBack}
>
<RiArrowLeftLine className='mr-1 w-3.5 h-3.5' />
Back
</Button>
<div className='shrink-0 mx-0.5 system-xs-regular text-divider-deep'>/</div>
<div className='grow px-[5px] system-xs-medium-uppercase'>
10 Logs
</div>
<Button
className='px-[5px]'
size='small'
variant='ghost-accent'
onClick={onClose}
>
close
</Button>
</div>
<TracingPanel
list={[]}
/>
</div>
)
}
export default ToolCallResultPanel

@ -0,0 +1,48 @@
import {
useCallback,
useState,
} from 'react'
import { useBoolean } from 'ahooks'
import type { IterationDurationMap, NodeTracing } from '@/types/workflow'
export const useLogs = () => {
const [showRetryDetail, {
setTrue: setShowRetryDetailTrue,
setFalse: setShowRetryDetailFalse,
}] = useBoolean(false)
const [retryResultList, setRetryResultList] = useState<NodeTracing[]>([])
const handleShowRetryResultList = useCallback((detail: NodeTracing[]) => {
setShowRetryDetailTrue()
setRetryResultList(detail)
}, [setShowRetryDetailTrue, setRetryResultList])
const [showIteratingDetail, {
setTrue: setShowIteratingDetailTrue,
setFalse: setShowIteratingDetailFalse,
}] = useBoolean(false)
const [iterationResultList, setIterationResultList] = useState<NodeTracing[][]>([])
const [iterationResultDurationMap, setIterationResultDurationMap] = useState<IterationDurationMap>({})
const handleShowIterationResultList = useCallback((detail: NodeTracing[][], iterDurationMap: IterationDurationMap) => {
setShowIteratingDetailTrue()
setIterationResultList(detail)
setIterationResultDurationMap(iterDurationMap)
}, [setShowIteratingDetailTrue, setIterationResultList, setIterationResultDurationMap])
return {
showSpecialResultPanel: !showRetryDetail && !showIteratingDetail,
showRetryDetail,
setShowRetryDetailTrue,
setShowRetryDetailFalse,
retryResultList,
setRetryResultList,
handleShowRetryResultList,
showIteratingDetail,
setShowIteratingDetailTrue,
setShowIteratingDetailFalse,
iterationResultList,
setIterationResultList,
iterationResultDurationMap,
setIterationResultDurationMap,
handleShowIterationResultList,
}
}

@ -3,17 +3,16 @@ import type { FC } from 'react'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useBoolean } from 'ahooks'
import OutputPanel from './output-panel' import OutputPanel from './output-panel'
import ResultPanel from './result-panel' import ResultPanel from './result-panel'
import TracingPanel from './tracing-panel' import TracingPanel from './tracing-panel'
import IterationResultPanel from './iteration-result-panel' import SpecialResultPanel from './special-result-panel'
import RetryResultPanel from './retry-result-panel' import { useLogs } from './hooks'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { ToastContext } from '@/app/components/base/toast' import { ToastContext } from '@/app/components/base/toast'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
import { fetchRunDetail, fetchTracingList } from '@/service/log' import { fetchRunDetail, fetchTracingList } from '@/service/log'
import type { IterationDurationMap, NodeTracing } from '@/types/workflow' import type { NodeTracing } from '@/types/workflow'
import type { WorkflowRunDetailResponse } from '@/models/log' import type { WorkflowRunDetailResponse } from '@/models/log'
import { useStore as useAppStore } from '@/app/components/app/store' import { useStore as useAppStore } from '@/app/components/app/store'
import formatNodeList from './utils/format-log' import formatNodeList from './utils/format-log'
@ -106,41 +105,18 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
adjustResultHeight() adjustResultHeight()
}, [loading]) }, [loading])
const [iterationRunResult, setIterationRunResult] = useState<NodeTracing[][]>([]) const {
const [iterDurationMap, setIterDurationMap] = useState<IterationDurationMap>({}) showRetryDetail,
const [retryRunResult, setRetryRunResult] = useState<NodeTracing[]>([]) setShowRetryDetailFalse,
const [isShowIterationDetail, { retryResultList,
setTrue: doShowIterationDetail, handleShowRetryResultList,
setFalse: doHideIterationDetail, showIteratingDetail,
}] = useBoolean(false) setShowIteratingDetailFalse,
const [isShowRetryDetail, { iterationResultList,
setTrue: doShowRetryDetail, iterationResultDurationMap,
setFalse: doHideRetryDetail, handleShowIterationResultList,
}] = useBoolean(false) showSpecialResultPanel,
} = useLogs()
const handleShowIterationDetail = useCallback((detail: NodeTracing[][], iterDurationMap: IterationDurationMap) => {
setIterationRunResult(detail)
doShowIterationDetail()
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'>
<IterationResultPanel
list={iterationRunResult}
onHide={doHideIterationDetail}
onBack={doHideIterationDetail}
iterDurationMap={iterDurationMap}
/>
</div>
)
}
return ( return (
<div className='grow relative flex flex-col'> <div className='grow relative flex flex-col'>
@ -198,19 +174,25 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
exceptionCounts={runDetail.exceptions_count} exceptionCounts={runDetail.exceptions_count}
/> />
)} )}
{!loading && currentTab === 'TRACING' && !isShowRetryDetail && ( {!loading && currentTab === 'TRACING' && !showSpecialResultPanel && (
<TracingPanel <TracingPanel
className='bg-background-section-burn' className='bg-background-section-burn'
list={list} list={list}
onShowIterationDetail={handleShowIterationDetail} onShowIterationDetail={handleShowIterationResultList}
onShowRetryDetail={handleShowRetryDetail} onShowRetryDetail={handleShowRetryResultList}
/> />
)} )}
{ {
!loading && currentTab === 'TRACING' && isShowRetryDetail && ( !loading && currentTab === 'TRACING' && showSpecialResultPanel && (
<RetryResultPanel <SpecialResultPanel
list={retryRunResult} showRetryDetail={showRetryDetail}
onBack={doHideRetryDetail} setShowRetryDetailFalse={setShowRetryDetailFalse}
retryResultList={retryResultList}
showIteratingDetail={showIteratingDetail}
setShowIteratingDetailFalse={setShowIteratingDetailFalse}
iterationResultList={iterationResultList}
iterationResultDurationMap={iterationResultDurationMap}
/> />
) )
} }

@ -187,10 +187,10 @@ const NodePanel: FC<Props> = ({
onClick={handleOnShowRetryDetail} onClick={handleOnShowRetryDetail}
> >
<div className='flex items-center'> <div className='flex items-center'>
<RiRestartFill className='mr-0.5 w-4 h-4 text-components-button-tertiary-text flex-shrink-0' /> <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 })} {t('workflow.nodes.common.retry.retries', { num: nodeInfo.retryDetail?.length })}
</div> </div>
<RiArrowRightSLine className='w-4 h-4 text-components-button-tertiary-text flex-shrink-0' /> <RiArrowRightSLine className='w-4 h-4 text-components-button-tertiary-text shrink-0' />
</Button> </Button>
)} )}
<div className={cn('mb-1', hideInfo && '!px-2 !py-0.5')}> <div className={cn('mb-1', hideInfo && '!px-2 !py-0.5')}>

@ -0,0 +1,49 @@
import RetryResultPanel from './retry-result-panel'
import IterationResultPanel from './iteration-result-panel'
import type { IterationDurationMap, NodeTracing } from '@/types/workflow'
type SpecialResultPanelProps = {
showRetryDetail: boolean
setShowRetryDetailFalse: () => void
retryResultList: NodeTracing[]
showIteratingDetail: boolean
setShowIteratingDetailFalse: () => void
iterationResultList: NodeTracing[][]
iterationResultDurationMap: IterationDurationMap
}
const SpecialResultPanel = ({
showRetryDetail,
setShowRetryDetailFalse,
retryResultList,
showIteratingDetail,
setShowIteratingDetailFalse,
iterationResultList,
iterationResultDurationMap,
}: SpecialResultPanelProps) => {
return (
<>
{
showRetryDetail && (
<RetryResultPanel
list={retryResultList}
onBack={setShowRetryDetailFalse}
/>
)
}
{
showIteratingDetail && (
<IterationResultPanel
list={iterationResultList}
onHide={setShowIteratingDetailFalse}
onBack={setShowIteratingDetailFalse}
iterDurationMap={iterationResultDurationMap}
/>
)
}
</>
)
}
export default SpecialResultPanel

@ -178,7 +178,7 @@ export const simpleIterationData = (() => {
return { return {
in: [startNode, outputArrayNode, iterationNode, ...iterations, endNode], in: [startNode, outputArrayNode, iterationNode, ...iterations, endNode],
output: [startNode, outputArrayNode, { expect: [startNode, outputArrayNode, {
...iterationNode, ...iterationNode,
details: [ details: [
[iterations[0]], [iterations[0]],

@ -1,11 +1,11 @@
import format from '.' import format from '.'
import { simpleIterationData } from './data' import { simpleIterationData } from './data'
describe('format api data to tracing panel data', () => { describe('iteration', () => {
test('result should have no nodes in iteration node', () => { test('result should have no nodes in iteration node', () => {
expect(format(simpleIterationData.in as any).find(item => !!(item as any).execution_metadata?.iteration_id)).toBeUndefined() expect(format(simpleIterationData.in as any).find(item => !!(item as any).execution_metadata?.iteration_id)).toBeUndefined()
}) })
test('iteration should put nodes in details', () => { test('iteration should put nodes in details', () => {
expect(format(simpleIterationData.in as any)).toEqual(simpleIterationData.output) expect(format(simpleIterationData.in as any)).toEqual(simpleIterationData.expect)
}) })
}) })

@ -0,0 +1,133 @@
export const simpleRetryData = (() => {
const startNode = {
id: 'f7938b2b-77cd-43f0-814c-2f0ade7cbc60',
index: 1,
predecessor_node_id: null,
node_id: '1735112903395',
node_type: 'start',
title: 'Start',
inputs: {
'sys.files': [],
'sys.user_id': '6d8ad01f-edf9-43a6-b863-a034b1828ac7',
'sys.app_id': '6180ead7-2190-4a61-975c-ec3bf29653da',
'sys.workflow_id': 'eef6da45-244b-4c79-958e-f3573f7c12bb',
'sys.workflow_run_id': 'fc8970ef-1406-484e-afde-8567dc22f34c',
},
process_data: null,
outputs: {
'sys.files': [],
'sys.user_id': '6d8ad01f-edf9-43a6-b863-a034b1828ac7',
'sys.app_id': '6180ead7-2190-4a61-975c-ec3bf29653da',
'sys.workflow_id': 'eef6da45-244b-4c79-958e-f3573f7c12bb',
'sys.workflow_run_id': 'fc8970ef-1406-484e-afde-8567dc22f34c',
},
status: 'succeeded',
error: null,
elapsed_time: 0.008715,
execution_metadata: null,
extras: {},
created_at: 1735112940,
created_by_role: 'account',
created_by_account: {
id: '6d8ad01f-edf9-43a6-b863-a034b1828ac7',
name: '九彩拼盘',
email: 'iamjoel007@gmail.com',
},
created_by_end_user: null,
finished_at: 1735112940,
}
const httpNode = {
id: '50220407-3420-4ad4-89da-c6959710d1aa',
index: 2,
predecessor_node_id: '1735112903395',
node_id: '1735112908006',
node_type: 'http-request',
title: 'HTTP Request',
inputs: null,
process_data: {
request: 'GET / HTTP/1.1\r\nHost: 404\r\n\r\n',
},
outputs: null,
status: 'failed',
error: 'timed out',
elapsed_time: 30.247757,
execution_metadata: null,
extras: {},
created_at: 1735112940,
created_by_role: 'account',
created_by_account: {
id: '6d8ad01f-edf9-43a6-b863-a034b1828ac7',
name: '九彩拼盘',
email: 'iamjoel007@gmail.com',
},
created_by_end_user: null,
finished_at: 1735112970,
}
const retry1 = {
id: 'ed352b36-27fb-49c6-9e8f-cc755bfc25fc',
index: 3,
predecessor_node_id: '1735112903395',
node_id: '1735112908006',
node_type: 'http-request',
title: 'HTTP Request',
inputs: null,
process_data: null,
outputs: null,
status: 'retry',
error: 'timed out',
elapsed_time: 10.011833,
execution_metadata: {
iteration_id: null,
parallel_mode_run_id: null,
},
extras: {},
created_at: 1735112940,
created_by_role: 'account',
created_by_account: {
id: '6d8ad01f-edf9-43a6-b863-a034b1828ac7',
name: '九彩拼盘',
email: 'iamjoel007@gmail.com',
},
created_by_end_user: null,
finished_at: 1735112950,
}
const retry2 = {
id: '74dfb3d3-dacf-44f2-8784-e36bfa2d6c4e',
index: 4,
predecessor_node_id: '1735112903395',
node_id: '1735112908006',
node_type: 'http-request',
title: 'HTTP Request',
inputs: null,
process_data: null,
outputs: null,
status: 'retry',
error: 'timed out',
elapsed_time: 10.010368,
execution_metadata: {
iteration_id: null,
parallel_mode_run_id: null,
},
extras: {},
created_at: 1735112950,
created_by_role: 'account',
created_by_account: {
id: '6d8ad01f-edf9-43a6-b863-a034b1828ac7',
name: '九彩拼盘',
email: 'iamjoel007@gmail.com',
},
created_by_end_user: null,
finished_at: 1735112960,
}
return {
in: [startNode, httpNode, retry1, retry2],
expect: [startNode, {
...httpNode,
retryDetail: [retry1, retry2],
}],
}
})()

@ -0,0 +1,11 @@
import format from '.'
import { simpleRetryData } from './data'
describe('retry', () => {
test('should have no retry status nodes', () => {
expect(format(simpleRetryData.in as any).find(item => (item as any).status === 'retry')).toBeUndefined()
})
test('should put retry nodes in retryDetail', () => {
expect(format(simpleRetryData.in as any)).toEqual(simpleRetryData.expect)
})
})

@ -1,7 +1,29 @@
import { BlockEnum } from '@/app/components/workflow/types'
import type { NodeTracing } from '@/types/workflow' import type { NodeTracing } from '@/types/workflow'
const format = (list: NodeTracing[]): NodeTracing[] => { const format = (list: NodeTracing[]): NodeTracing[] => {
return list const retryNodes = list.filter((item) => {
const { execution_metadata } = item
const isInIteration = !!execution_metadata?.iteration_id
if (isInIteration || item.node_type === BlockEnum.Iteration) return false
return item.status === 'retry'
})
const retryNodeIds = retryNodes.map(item => item.node_id)
// move retry nodes to retryDetail
const result = list.filter((item) => {
return item.status !== 'retry'
}).map((item) => {
const isRetryBelongNode = retryNodeIds.includes(item.node_id)
if (isRetryBelongNode) {
return {
...item,
retryDetail: list.filter(node => node.status === 'retry' && node.node_id === item.node_id),
}
}
return item
})
return result
} }
export default format export default format

Loading…
Cancel
Save