merge feat/plugins

pull/12372/head
zxhlyh 1 year ago
commit bc78803171

@ -17,6 +17,7 @@ export enum FormTypeEnum {
file = 'file', file = 'file',
modelSelector = 'model-selector', modelSelector = 'model-selector',
toolSelector = 'tool-selector', toolSelector = 'tool-selector',
multiToolSelector = 'array[tools]',
appSelector = 'app-selector', appSelector = 'app-selector',
} }

@ -19,6 +19,7 @@ import Tooltip from '@/app/components/base/tooltip'
import Radio from '@/app/components/base/radio' import Radio from '@/app/components/base/radio'
import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector' import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector'
import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector' import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector'
import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector'
import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector'
import RadioE from '@/app/components/base/radio/ui' import RadioE from '@/app/components/base/radio/ui'
@ -328,7 +329,35 @@ function Form<
scope={scope} scope={scope}
disabled={readonly} disabled={readonly}
value={value[variable]} value={value[variable]}
onSelect={item => handleFormChange(variable, item as any)} /> onSelect={item => handleFormChange(variable, item as any)}
onDelete={() => handleFormChange(variable, null as any)}
/>
{fieldMoreInfo?.(formSchema)}
{validating && changeKey === variable && <ValidatingTip />}
</div>
)
}
if (formSchema.type === FormTypeEnum.multiToolSelector) {
const {
variable,
label,
tooltip,
required,
scope,
} = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput)
return (
<div key={variable} className={cn(itemClassName, 'py-3')}>
<MultipleToolSelector
disabled={readonly}
scope={scope}
label={label[language] || label.en_US}
required={required}
tooltip={tooltip?.[language] || tooltip?.en_US}
value={value[variable]}
onChange={item => handleFormChange(variable, item as any)}
/>
{fieldMoreInfo?.(formSchema)} {fieldMoreInfo?.(formSchema)}
{validating && changeKey === variable && <ValidatingTip />} {validating && changeKey === variable && <ValidatingTip />}
</div> </div>

@ -7,7 +7,7 @@ import ActionList from './action-list'
import ModelList from './model-list' import ModelList from './model-list'
import AgentStrategyList from './agent-strategy-list' import AgentStrategyList from './agent-strategy-list'
import Drawer from '@/app/components/base/drawer' import Drawer from '@/app/components/base/drawer'
import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector' import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector'
import type { PluginDetail } from '@/app/components/plugins/types' import type { PluginDetail } from '@/app/components/plugins/types'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
@ -33,9 +33,6 @@ const PluginDetailPanel: FC<Props> = ({
console.log('tool change', val) console.log('tool change', val)
setValue(val) setValue(val)
} }
const testDelete = () => {
setValue(undefined)
}
if (!detail) if (!detail)
return null return null
@ -64,10 +61,10 @@ const PluginDetailPanel: FC<Props> = ({
{!!detail.declaration.model && <ModelList detail={detail} />} {!!detail.declaration.model && <ModelList detail={detail} />}
{false && ( {false && (
<div className='px-4 py-2'> <div className='px-4 py-2'>
<ToolSelector <MultipleToolSelector
value={value} value={value || []}
onSelect={item => testChange(item)} label='TOOLS'
onDelete={testDelete} onChange={testChange}
/> />
</div> </div>
)} )}

@ -1,12 +1,148 @@
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next'
import {
RiAddLine,
RiArrowDropDownLine,
RiQuestionLine,
} from '@remixicon/react'
import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector'
import ActionButton from '@/app/components/base/action-button'
import Tooltip from '@/app/components/base/tooltip'
import Divider from '@/app/components/base/divider'
import type { ToolValue } from '@/app/components/plugins/plugin-detail-panel/tool-selector'
import cn from '@/utils/classnames'
type Props = { type Props = {
value: any[] disabled?: boolean
value: ToolValue[]
label: string
required?: boolean
tooltip?: any
supportCollapse?: boolean
scope?: string
onChange: (value: ToolValue[]) => void
} }
const MultipleToolSelector = ({ value }: Props) => { const MultipleToolSelector = ({
disabled,
value,
label,
required,
tooltip,
supportCollapse,
scope,
onChange,
}: Props) => {
const { t } = useTranslation()
const enabledCount = value.filter(item => item.enabled).length
// collapse control
const [collapse, setCollapse] = React.useState(false)
const handleCollapse = () => {
if (supportCollapse)
setCollapse(!collapse)
}
// add tool
const [open, setOpen] = React.useState(false)
const handleAdd = (val: ToolValue) => {
const newValue = [...value, val]
// deduplication
const deduplication = newValue.reduce((acc, cur) => {
if (!acc.find(item => item.provider_name === cur.provider_name && item.tool_name === cur.tool_name))
acc.push(cur)
return acc
}, [] as ToolValue[])
// update value
onChange(deduplication)
setOpen(false)
}
// delete tool
const handleDelete = (index: number) => {
const newValue = [...value]
newValue.splice(index, 1)
onChange(newValue)
}
// configure tool
const handleConfigure = (val: ToolValue, index: number) => {
const newValue = [...value]
newValue[index] = val
onChange(newValue)
}
return ( return (
<div></div> <>
<div className='flex items-center mb-1'>
<div
className={cn('relative grow flex items-center gap-0.5', supportCollapse && 'cursor-pointer')}
onClick={handleCollapse}
>
<div className='h-6 flex items-center text-text-secondary system-sm-semibold-uppercase'>{label}</div>
{required && <div className='text-error-main'>*</div>}
{tooltip && (
<Tooltip
popupContent={tooltip}
needsDelay
>
<div><RiQuestionLine className='w-3.5 h-3.5 text-text-quaternary hover:text-text-tertiary'/></div>
</Tooltip>
)}
{supportCollapse && (
<div className='absolute -left-4 top-1'>
<RiArrowDropDownLine
className={cn(
'w-4 h-4 text-text-tertiary',
collapse && 'transform -rotate-90',
)}
/>
</div>
)}
</div>
{value.length > 0 && (
<>
<div className='flex items-center gap-1 text-text-tertiary system-xs-medium'>
<span>{`${enabledCount}/${value.length}`}</span>
<span>{t('appDebug.agent.tools.enabled')}</span>
</div>
<Divider type='vertical' className='ml-3 mr-1 h-3' />
</>
)}
{!disabled && (
<ActionButton className='mx-1' onClick={() => setOpen(!open)}>
<RiAddLine className='w-4 h-4' />
</ActionButton>
)}
</div>
{!collapse && (
<>
<ToolSelector
scope={scope}
value={undefined}
onSelect={handleAdd}
controlledState={open}
onControlledStateChange={setOpen}
trigger={
<div className=''></div>
}
/>
{value.length === 0 && (
<div className='p-3 flex justify-center rounded-[10px] bg-background-section text-text-tertiary system-xs-regular'>{t('plugin.detailPanel.toolSelector.empty')}</div>
)}
{value.length > 0 && value.map((item, index) => (
<div className='mb-1' key={index}>
<ToolSelector
scope={scope}
value={item}
onSelect={item => handleConfigure(item, index)}
onDelete={() => handleDelete(index)}
supportEnableSwitch
/>
</div>
))}
</>
)}
</>
) )
} }

@ -40,16 +40,20 @@ import type {
} from '@floating-ui/react' } from '@floating-ui/react'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
export type ToolValue = {
provider_name: string
tool_name: string
parameters?: Record<string, any>
enabled?: boolean
extra?: Record<string, any>
}
type Props = { type Props = {
value?: {
provider_name: string
tool_name: string
parameters?: Record<string, any>
extra?: Record<string, any>
}
disabled?: boolean disabled?: boolean
placement?: Placement placement?: Placement
offset?: OffsetOptions offset?: OffsetOptions
scope?: string
value?: ToolValue
onSelect: (tool: { onSelect: (tool: {
provider_name: string provider_name: string
tool_name: string tool_name: string
@ -57,8 +61,11 @@ type Props = {
extra?: Record<string, any> extra?: Record<string, any>
}) => void }) => void
onDelete?: () => void onDelete?: () => void
supportEnableSwitch?: boolean
supportAddCustomTool?: boolean supportAddCustomTool?: boolean
scope?: string trigger?: React.ReactNode
controlledState?: boolean
onControlledStateChange?: (state: boolean) => void
} }
const ToolSelector: FC<Props> = ({ const ToolSelector: FC<Props> = ({
value, value,
@ -68,6 +75,10 @@ const ToolSelector: FC<Props> = ({
onSelect, onSelect,
onDelete, onDelete,
scope, scope,
supportEnableSwitch,
trigger,
controlledState,
onControlledStateChange,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const [isShow, onShowChange] = useState(false) const [isShow, onShowChange] = useState(false)
@ -95,14 +106,13 @@ const ToolSelector: FC<Props> = ({
provider_name: tool.provider_id, provider_name: tool.provider_id,
tool_name: tool.tool_name, tool_name: tool.tool_name,
parameters: paramValues, parameters: paramValues,
enabled: tool.is_team_authorization,
extra: { extra: {
description: '', description: '',
}, },
} }
onSelect(toolValue) onSelect(toolValue)
setIsShowChooseTool(false) setIsShowChooseTool(false)
// if (tool.provider_type === CollectionType.builtIn && tool.is_team_authorization)
// onShowChange(false)
} }
const handleDescriptionChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { const handleDescriptionChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
@ -130,6 +140,13 @@ const ToolSelector: FC<Props> = ({
onSelect(toolValue as any) onSelect(toolValue as any)
} }
const handleEnabledChange = (state: boolean) => {
onSelect({
...value,
enabled: state,
} as any)
}
// authorization // authorization
const { isCurrentWorkspaceManager } = useAppContext() const { isCurrentWorkspaceManager } = useAppContext()
const [isShowSettingAuth, setShowSettingAuth] = useState(false) const [isShowSettingAuth, setShowSettingAuth] = useState(false)
@ -152,14 +169,15 @@ const ToolSelector: FC<Props> = ({
<PortalToFollowElem <PortalToFollowElem
placement={placement} placement={placement}
offset={offset} offset={offset}
open={isShow} open={trigger ? controlledState : isShow}
onOpenChange={onShowChange} onOpenChange={trigger ? onControlledStateChange : onShowChange}
> >
<PortalToFollowElemTrigger <PortalToFollowElemTrigger
className='w-full' className='w-full'
onClick={handleTriggerClick} onClick={handleTriggerClick}
> >
{!value?.provider_name && ( {trigger}
{!trigger && !value?.provider_name && (
<ToolTrigger <ToolTrigger
isConfigure isConfigure
open={isShow} open={isShow}
@ -167,16 +185,20 @@ const ToolSelector: FC<Props> = ({
provider={currentProvider} provider={currentProvider}
/> />
)} )}
{value?.provider_name && ( {!trigger && value?.provider_name && (
<ToolItem <ToolItem
open={isShow} open={isShow}
icon={currentProvider?.icon} icon={currentProvider?.icon}
providerName={value.provider_name} providerName={value.provider_name}
toolName={value.tool_name} toolName={value.tool_name}
showSwitch={supportEnableSwitch}
switchValue={value.enabled}
onSwitchChange={handleEnabledChange}
onDelete={onDelete} onDelete={onDelete}
noAuth={currentProvider && !currentProvider.is_team_authorization} noAuth={currentProvider && !currentProvider.is_team_authorization}
onAuth={() => setShowSettingAuth(true)} onAuth={() => setShowSettingAuth(true)}
// uninstalled // uninstalled TODO
// isError TODO
errorTip={<div className='space-y-1 text-xs'> errorTip={<div className='space-y-1 text-xs'>
<h3 className='text-text-primary font-semibold'>{t('workflow.nodes.agent.pluginNotInstalled')}</h3> <h3 className='text-text-primary font-semibold'>{t('workflow.nodes.agent.pluginNotInstalled')}</h3>
<p className='text-text-secondary tracking-tight'>{t('workflow.nodes.agent.pluginNotInstalledDesc')}</p> <p className='text-text-secondary tracking-tight'>{t('workflow.nodes.agent.pluginNotInstalledDesc')}</p>

@ -77,7 +77,10 @@ const ToolItem = ({
)} )}
<div <div
className='p-1 rounded-md text-text-tertiary cursor-pointer hover:text-text-destructive' className='p-1 rounded-md text-text-tertiary cursor-pointer hover:text-text-destructive'
onClick={onDelete} onClick={(e) => {
e.stopPropagation()
onDelete?.()
}}
onMouseOver={() => setIsDeleting(true)} onMouseOver={() => setIsDeleting(true)}
onMouseLeave={() => setIsDeleting(false)} onMouseLeave={() => setIsDeleting(false)}
> >
@ -85,11 +88,13 @@ const ToolItem = ({
</div> </div>
</div> </div>
{!isError && !uninstalled && !noAuth && showSwitch && ( {!isError && !uninstalled && !noAuth && showSwitch && (
<Switch <div className='mr-1' onClick={e => e.stopPropagation()}>
className='mr-1' <Switch
size='md' size='md'
defaultValue={switchValue} defaultValue={switchValue}
onChange={onSwitchChange} /> onChange={onSwitchChange}
/>
</div>
)} )}
{!isError && !uninstalled && noAuth && ( {!isError && !uninstalled && noAuth && (
<Button variant='secondary' size='small' onClick={onAuth}> <Button variant='secondary' size='small' onClick={onAuth}>

@ -10,6 +10,7 @@ import { Agent } from '@/app/components/base/icons/src/vender/workflow'
import { InputNumber } from '@/app/components/base/input-number' import { InputNumber } from '@/app/components/base/input-number'
import Slider from '@/app/components/base/slider' import Slider from '@/app/components/base/slider'
import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector' import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector'
import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector'
import Field from './field' import Field from './field'
import type { ComponentProps } from 'react' import type { ComponentProps } from 'react'
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
@ -160,18 +161,31 @@ export const AgentStrategy = (props: AgentStrategyProps) => {
props.onChange({ ...props.value, [schema.variable]: value }) props.onChange({ ...props.value, [schema.variable]: value })
} }
return ( return (
<Field title={'tool selector'} tooltip={'tool selector'}> <Field title={schema.label[language]} tooltip={schema.tooltip?.[language]}>
<ToolSelector <ToolSelector
scope={schema.scope}
value={value} value={value}
onSelect={item => onChange(item)} onSelect={item => onChange(item)}
onDelete={() => onChange(null)}
/> />
</Field> </Field>
) )
} }
case 'array[tools]': { case 'array[tools]': {
return <Field title={'tool selector'} tooltip={'tool selector'}> const value = props.value[schema.variable]
multiple tool selector TODO const onChange = (value: any) => {
</Field> props.onChange({ ...props.value, [schema.variable]: value })
}
return (
<MultipleToolSelector
scope={schema.scope}
value={value}
label={schema.label[language]}
tooltip={schema.tooltip?.[language]}
onChange={onChange}
supportCollapse
/>
)
} }
} }
} }

@ -17,10 +17,10 @@ import ResultPanel from '@/app/components/workflow/run/result-panel'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import { TransferMethod } from '@/types/app' import { TransferMethod } from '@/types/app'
import { getProcessedFiles } from '@/app/components/base/file-uploader/utils' import { getProcessedFiles } from '@/app/components/base/file-uploader/utils'
import type { NodeTracing } from '@/types/workflow'
import { RetryResultPanel } from '@/app/components/workflow/run/retry-log'
import type { BlockEnum } from '@/app/components/workflow/types' import type { BlockEnum } from '@/app/components/workflow/types'
import type { Emoji } from '@/app/components/tools/types' import type { Emoji } from '@/app/components/tools/types'
import type { SpecialResultPanelProps } from '@/app/components/workflow/run/special-result-panel'
import SpecialResultPanel from '@/app/components/workflow/run/special-result-panel'
const i18nPrefix = 'workflow.singleRun' const i18nPrefix = 'workflow.singleRun'
@ -34,9 +34,8 @@ type BeforeRunFormProps = {
runningStatus: NodeRunningStatus runningStatus: NodeRunningStatus
result?: JSX.Element result?: JSX.Element
forms: FormProps[] forms: FormProps[]
retryDetails?: NodeTracing[] showSpecialResultPanel?: boolean
onRetryDetailBack?: any } & Partial<SpecialResultPanelProps>
}
function formatValue(value: string | any, type: InputVarType) { function formatValue(value: string | any, type: InputVarType) {
if (type === InputVarType.number) if (type === InputVarType.number)
@ -66,8 +65,8 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
runningStatus, runningStatus,
result, result,
forms, forms,
retryDetails, showSpecialResultPanel,
onRetryDetailBack = () => { }, ...restResultPanelParams
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
@ -141,24 +140,14 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
</div> </div>
</div> </div>
{ {
retryDetails?.length && ( showSpecialResultPanel && (
<div className='h-0 grow overflow-y-auto pb-4'> <div className='h-0 grow overflow-y-auto pb-4'>
<RetryResultPanel <SpecialResultPanel {...restResultPanelParams} />
list={retryDetails.map((item, index) => ({
...item,
title: `${t('workflow.nodes.common.retry.retry')} ${index + 1}`,
node_type: nodeType!,
extras: {
icon: toolIcon!,
},
}))}
onBack={onRetryDetailBack}
/>
</div> </div>
) )
} }
{ {
!retryDetails?.length && ( !showSpecialResultPanel && (
<div className='h-0 grow overflow-y-auto pb-4'> <div className='h-0 grow overflow-y-auto pb-4'>
<div className='mt-3 px-4 space-y-4'> <div className='mt-3 px-4 space-y-4'>
{forms.map((form, index) => ( {forms.map((form, index) => (

@ -142,7 +142,7 @@ const useOneStepRun = <T>({
const { handleNodeDataUpdate }: { handleNodeDataUpdate: (data: any) => void } = useNodeDataUpdate() const { handleNodeDataUpdate }: { handleNodeDataUpdate: (data: any) => void } = useNodeDataUpdate()
const [canShowSingleRun, setCanShowSingleRun] = useState(false) const [canShowSingleRun, setCanShowSingleRun] = useState(false)
const isShowSingleRun = data._isSingleRun && canShowSingleRun const isShowSingleRun = data._isSingleRun && canShowSingleRun
const [iterationRunResult, setIterationRunResult] = useState<NodeTracing[][]>([]) const [iterationRunResult, setIterationRunResult] = useState<NodeTracing[]>([])
useEffect(() => { useEffect(() => {
if (!checkValid) { if (!checkValid) {
@ -173,7 +173,7 @@ const useOneStepRun = <T>({
const workflowStore = useWorkflowStore() const workflowStore = useWorkflowStore()
useEffect(() => { useEffect(() => {
workflowStore.getState().setShowSingleRunPanel(!!isShowSingleRun) workflowStore.getState().setShowSingleRunPanel(!!isShowSingleRun)
}, [isShowSingleRun]) }, [isShowSingleRun, workflowStore])
const hideSingleRun = () => { const hideSingleRun = () => {
handleNodeDataUpdate({ handleNodeDataUpdate({
@ -211,7 +211,7 @@ const useOneStepRun = <T>({
} }
else { else {
setIterationRunResult([]) setIterationRunResult([])
let _iterationResult: NodeTracing[][] = [] let _iterationResult: NodeTracing[] = []
let _runResult: any = null let _runResult: any = null
ssePost( ssePost(
getIterationSingleNodeRunUrl(isChatMode, appId!, id), getIterationSingleNodeRunUrl(isChatMode, appId!, id),
@ -231,27 +231,43 @@ const useOneStepRun = <T>({
_runResult.created_by = iterationData.created_by.name _runResult.created_by = iterationData.created_by.name
setRunResult(_runResult) setRunResult(_runResult)
}, },
onIterationNext: () => { onIterationStart: (params) => {
// iteration next trigger time is triggered one more time than iterationTimes
if (_iterationResult.length >= iterationTimes!)
return
const newIterationRunResult = produce(_iterationResult, (draft) => { const newIterationRunResult = produce(_iterationResult, (draft) => {
draft.push([]) draft.push({
...params.data,
status: NodeRunningStatus.Running,
})
}) })
_iterationResult = newIterationRunResult _iterationResult = newIterationRunResult
setIterationRunResult(newIterationRunResult) setIterationRunResult(newIterationRunResult)
}, },
onIterationNext: () => {
// iteration next trigger time is triggered one more time than iterationTimes
if (_iterationResult.length >= iterationTimes!)
return _iterationResult.length >= iterationTimes!
},
onIterationFinish: (params) => { onIterationFinish: (params) => {
_runResult = params.data _runResult = params.data
setRunResult(_runResult) setRunResult(_runResult)
const iterationRunResult = _iterationResult
const currentIndex = iterationRunResult.findIndex(trace => trace.id === params.data.id)
const newIterationRunResult = produce(iterationRunResult, (draft) => {
if (currentIndex > -1) {
draft[currentIndex] = {
...draft[currentIndex],
...data,
}
}
})
_iterationResult = newIterationRunResult
setIterationRunResult(newIterationRunResult)
}, },
onNodeStarted: (params) => { onNodeStarted: (params) => {
const newIterationRunResult = produce(_iterationResult, (draft) => { const newIterationRunResult = produce(_iterationResult, (draft) => {
draft[draft.length - 1].push({ draft.push({
...params.data, ...params.data,
status: NodeRunningStatus.Running, status: NodeRunningStatus.Running,
} as NodeTracing) })
}) })
_iterationResult = newIterationRunResult _iterationResult = newIterationRunResult
setIterationRunResult(newIterationRunResult) setIterationRunResult(newIterationRunResult)
@ -260,18 +276,25 @@ const useOneStepRun = <T>({
const iterationRunResult = _iterationResult const iterationRunResult = _iterationResult
const { data } = params const { data } = params
const currentIndex = iterationRunResult[iterationRunResult.length - 1].findIndex(trace => trace.node_id === data.node_id) const currentIndex = iterationRunResult.findIndex(trace => trace.id === data.id)
const newIterationRunResult = produce(iterationRunResult, (draft) => { const newIterationRunResult = produce(iterationRunResult, (draft) => {
if (currentIndex > -1) { if (currentIndex > -1) {
draft[draft.length - 1][currentIndex] = { draft[currentIndex] = {
...draft[currentIndex],
...data, ...data,
status: NodeRunningStatus.Succeeded, }
} as NodeTracing
} }
}) })
_iterationResult = newIterationRunResult _iterationResult = newIterationRunResult
setIterationRunResult(newIterationRunResult) setIterationRunResult(newIterationRunResult)
}, },
onNodeRetry: (params) => {
const newIterationRunResult = produce(_iterationResult, (draft) => {
draft.push(params.data)
})
_iterationResult = newIterationRunResult
setIterationRunResult(newIterationRunResult)
},
onError: () => { onError: () => {
handleNodeDataUpdate({ handleNodeDataUpdate({
id, id,

@ -9,6 +9,7 @@ import { ToolIcon } from './components/tool-icon'
import useConfig from './use-config' import useConfig from './use-config'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useInstalledPluginList } from '@/service/use-plugins' import { useInstalledPluginList } from '@/service/use-plugins'
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
const AgentNode: FC<NodeProps<AgentNodeType>> = (props) => { const AgentNode: FC<NodeProps<AgentNodeType>> = (props) => {
const { inputs, currentStrategy } = useConfig(props.id, props.data) const { inputs, currentStrategy } = useConfig(props.id, props.data)
@ -16,11 +17,26 @@ const AgentNode: FC<NodeProps<AgentNodeType>> = (props) => {
const pluginList = useInstalledPluginList() const pluginList = useInstalledPluginList()
// TODO: Implement models // TODO: Implement models
const models = useMemo(() => { const models = useMemo(() => {
const models = [] if (!inputs) return []
// if selected, show in node // if selected, show in node
// if required and not selected, show empty selector // if required and not selected, show empty selector
// if not required and not selected, show nothing // if not required and not selected, show nothing
}, [currentStrategy, inputs.agent_parameters]) const models = currentStrategy?.parameters
.filter(param => param.type === FormTypeEnum.modelSelector)
.reduce((acc, param) => {
const item = inputs.agent_parameters?.[param.name]
if (!item) {
if (param.required) {
acc.push({ param: param.name })
return acc
}
else { return acc }
}
acc.push({ provider: item.provider, model: item.model, param: param.name })
return acc
}, [] as Array<{ param: string } | { provider: string, model: string, param: string }>) || []
return models
}, [currentStrategy, inputs])
const tools = useMemo(() => { const tools = useMemo(() => {
const tools: Array<ToolIconProps> = [] const tools: Array<ToolIconProps> = []
@ -49,24 +65,26 @@ const AgentNode: FC<NodeProps<AgentNodeType>> = (props) => {
{inputs.agent_strategy_label} {inputs.agent_strategy_label}
</SettingItem> </SettingItem>
: <SettingItem label={t('workflow.nodes.agent.strategyNotSet')} />} : <SettingItem label={t('workflow.nodes.agent.strategyNotSet')} />}
<Group {models.length && <Group
label={<GroupLabel className='mt-1'> label={<GroupLabel className='mt-1'>
{t('workflow.nodes.agent.model')} {t('workflow.nodes.agent.model')}
</GroupLabel>} </GroupLabel>}
> >
<ModelSelector {models.map((model) => {
modelList={[]} return <ModelSelector
readonly key={model.param}
/> modelList={[]}
<ModelSelector defaultModel={
modelList={[]} 'provider' in model
readonly ? {
/> provider: model.provider,
<ModelSelector model: model.model,
modelList={[]} }
readonly : undefined}
/> readonly
</Group> />
})}
</Group>}
<Group label={<GroupLabel className='mt-1'> <Group label={<GroupLabel className='mt-1'>
{t('workflow.nodes.agent.toolbox')} {t('workflow.nodes.agent.toolbox')}
</GroupLabel>}> </GroupLabel>}>

@ -5,7 +5,7 @@ export type AgentNodeType = CommonNodeType & {
agent_strategy_provider_name?: string agent_strategy_provider_name?: string
agent_strategy_name?: string agent_strategy_name?: string
agent_strategy_label?: string agent_strategy_label?: string
agent_parameters?: ToolVarInputs, agent_parameters?: Record<string, any>
agent_configurations?: Record<string, ToolVarInputs> agent_configurations?: Record<string, ToolVarInputs>
output_schema: Record<string, any> output_schema: Record<string, any>
} }

@ -18,7 +18,6 @@ import { FileArrow01 } from '@/app/components/base/icons/src/vender/line/files'
import type { NodePanelProps } from '@/app/components/workflow/types' import type { NodePanelProps } from '@/app/components/workflow/types'
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form' import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
import ResultPanel from '@/app/components/workflow/run/result-panel' import ResultPanel from '@/app/components/workflow/run/result-panel'
import { useRetryDetailShowInSingleRun } from '@/app/components/workflow/nodes/_base/components/retry/hooks'
const i18nPrefix = 'workflow.nodes.http' const i18nPrefix = 'workflow.nodes.http'
@ -61,10 +60,6 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
hideCurlPanel, hideCurlPanel,
handleCurlImport, handleCurlImport,
} = useConfig(id, data) } = useConfig(id, data)
const {
retryDetails,
handleRetryDetailsChange,
} = useRetryDetailShowInSingleRun()
// To prevent prompt editor in body not update data. // To prevent prompt editor in body not update data.
if (!isDataReady) if (!isDataReady)
return null return null
@ -198,9 +193,7 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
runningStatus={runningStatus} runningStatus={runningStatus}
onRun={handleRun} onRun={handleRun}
onStop={handleStop} onStop={handleStop}
retryDetails={retryDetails} result={<ResultPanel {...runResult} showSteps={false} />}
onRetryDetailBack={handleRetryDetailsChange}
result={<ResultPanel {...runResult} showSteps={false} onShowRetryDetail={handleRetryDetailsChange} />}
/> />
)} )}
{(isShowCurlPanel && !readOnly) && ( {(isShowCurlPanel && !readOnly) && (

@ -1,13 +1,9 @@
import type { FC } from 'react' import type { FC } from 'react'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import {
RiArrowRightSLine,
} from '@remixicon/react'
import VarReferencePicker from '../_base/components/variable/var-reference-picker' import VarReferencePicker from '../_base/components/variable/var-reference-picker'
import Split from '../_base/components/split' import Split from '../_base/components/split'
import ResultPanel from '../../run/result-panel' import ResultPanel from '../../run/result-panel'
import { IterationResultPanel } from '../../run/iteration-log'
import { MAX_ITERATION_PARALLEL_NUM, MIN_ITERATION_PARALLEL_NUM } from '../../constants' import { MAX_ITERATION_PARALLEL_NUM, MIN_ITERATION_PARALLEL_NUM } from '../../constants'
import type { IterationNodeType } from './types' import type { IterationNodeType } from './types'
import useConfig from './use-config' import useConfig from './use-config'
@ -18,6 +14,9 @@ import Switch from '@/app/components/base/switch'
import Select from '@/app/components/base/select' import Select from '@/app/components/base/select'
import Slider from '@/app/components/base/slider' import Slider from '@/app/components/base/slider'
import Input from '@/app/components/base/input' import Input from '@/app/components/base/input'
import formatTracing from '@/app/components/workflow/run/utils/format-log'
import { useLogs } from '@/app/components/workflow/run/hooks'
const i18nPrefix = 'workflow.nodes.iteration' const i18nPrefix = 'workflow.nodes.iteration'
@ -50,9 +49,6 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
handleOutputVarChange, handleOutputVarChange,
isShowSingleRun, isShowSingleRun,
hideSingleRun, hideSingleRun,
isShowIterationDetail,
backToSingleRun,
showIterationDetail,
runningStatus, runningStatus,
handleRun, handleRun,
handleStop, handleStop,
@ -69,6 +65,9 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
changeParallelNums, changeParallelNums,
} = useConfig(id, data) } = useConfig(id, data)
const nodeInfo = formatTracing(iterationRunResult, t)[0]
const logsParams = useLogs()
return ( return (
<div className='pt-2 pb-2'> <div className='pt-2 pb-2'>
<div className='px-4 pb-4 space-y-4'> <div className='px-4 pb-4 space-y-4'>
@ -163,26 +162,12 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
runningStatus={runningStatus} runningStatus={runningStatus}
onRun={handleRun} onRun={handleRun}
onStop={handleStop} onStop={handleStop}
{...logsParams}
result={ result={
<div className='mt-3'> <ResultPanel {...runResult} showSteps={false} nodeInfo={nodeInfo} {...logsParams} />
<div className='px-4'>
<div className='flex items-center h-[34px] justify-between px-3 bg-gray-100 border-[0.5px] border-gray-200 rounded-lg cursor-pointer' onClick={showIterationDetail}>
<div className='leading-[18px] text-[13px] font-medium text-gray-700'>{t(`${i18nPrefix}.iteration`, { count: iterationRunResult.length })}</div>
<RiArrowRightSLine className='w-3.5 h-3.5 text-gray-500' />
</div>
<Split className='mt-3' />
</div>
<ResultPanel {...runResult} showSteps={false} />
</div>
} }
/> />
)} )}
{isShowIterationDetail && (
<IterationResultPanel
onBack={backToSingleRun}
list={iterationRunResult}
/>
)}
</div> </div>
) )
} }

@ -19,7 +19,6 @@ import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/c
import ResultPanel from '@/app/components/workflow/run/result-panel' import ResultPanel from '@/app/components/workflow/run/result-panel'
import Tooltip from '@/app/components/base/tooltip' import Tooltip from '@/app/components/base/tooltip'
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
import { useRetryDetailShowInSingleRun } from '@/app/components/workflow/nodes/_base/components/retry/hooks'
const i18nPrefix = 'workflow.nodes.llm' const i18nPrefix = 'workflow.nodes.llm'
@ -70,10 +69,6 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
runResult, runResult,
filterJinjia2InputVar, filterJinjia2InputVar,
} = useConfig(id, data) } = useConfig(id, data)
const {
retryDetails,
handleRetryDetailsChange,
} = useRetryDetailShowInSingleRun()
const model = inputs.model const model = inputs.model
@ -293,9 +288,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
runningStatus={runningStatus} runningStatus={runningStatus}
onRun={handleRun} onRun={handleRun}
onStop={handleStop} onStop={handleStop}
retryDetails={retryDetails} result={<ResultPanel {...runResult} showSteps={false} />}
onRetryDetailBack={handleRetryDetailsChange}
result={<ResultPanel {...runResult} showSteps={false} onShowRetryDetail={handleRetryDetailsChange} />}
/> />
)} )}
</div> </div>

@ -14,7 +14,6 @@ import Loading from '@/app/components/base/loading'
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form' import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
import ResultPanel from '@/app/components/workflow/run/result-panel' import ResultPanel from '@/app/components/workflow/run/result-panel'
import { useRetryDetailShowInSingleRun } from '@/app/components/workflow/nodes/_base/components/retry/hooks'
import { useToolIcon } from '@/app/components/workflow/hooks' import { useToolIcon } from '@/app/components/workflow/hooks'
const i18nPrefix = 'workflow.nodes.tool' const i18nPrefix = 'workflow.nodes.tool'
@ -52,10 +51,6 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
outputSchema, outputSchema,
} = useConfig(id, data) } = useConfig(id, data)
const toolIcon = useToolIcon(data) const toolIcon = useToolIcon(data)
const {
retryDetails,
handleRetryDetailsChange,
} = useRetryDetailShowInSingleRun()
if (isLoading) { if (isLoading) {
return <div className='flex h-[200px] items-center justify-center'> return <div className='flex h-[200px] items-center justify-center'>
@ -166,9 +161,7 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
runningStatus={runningStatus} runningStatus={runningStatus}
onRun={handleRun} onRun={handleRun}
onStop={handleStop} onStop={handleStop}
retryDetails={retryDetails} result={<ResultPanel {...runResult} showSteps={false} />}
onRetryDetailBack={handleRetryDetailsChange}
result={<ResultPanel {...runResult} showSteps={false} onShowRetryDetail={handleRetryDetailsChange} />}
/> />
)} )}
</div> </div>

@ -6,17 +6,14 @@ import type {
NodeTracing, NodeTracing,
} from '@/types/workflow' } from '@/types/workflow'
import { Iteration } from '@/app/components/base/icons/src/vender/workflow' import { Iteration } from '@/app/components/base/icons/src/vender/workflow'
import Split from '@/app/components/workflow/nodes/_base/components/split'
type IterationLogTriggerProps = { type IterationLogTriggerProps = {
nodeInfo: NodeTracing nodeInfo: NodeTracing
onShowIterationResultList: (iterationResultList: NodeTracing[][], iterationResultDurationMap: IterationDurationMap) => void onShowIterationResultList: (iterationResultList: NodeTracing[][], iterationResultDurationMap: IterationDurationMap) => void
justShowIterationNavArrow?: boolean
} }
const IterationLogTrigger = ({ const IterationLogTrigger = ({
nodeInfo, nodeInfo,
onShowIterationResultList, onShowIterationResultList,
justShowIterationNavArrow,
}: IterationLogTriggerProps) => { }: IterationLogTriggerProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const getErrorCount = (details: NodeTracing[][] | undefined) => { const getErrorCount = (details: NodeTracing[][] | undefined) => {
@ -41,31 +38,19 @@ const IterationLogTrigger = ({
onShowIterationResultList(nodeInfo.details || [], nodeInfo?.iterDurationMap || nodeInfo.execution_metadata?.iteration_duration_map || {}) onShowIterationResultList(nodeInfo.details || [], nodeInfo?.iterDurationMap || nodeInfo.execution_metadata?.iteration_duration_map || {})
} }
return ( return (
<div className='mt-2 mb-1 !px-2'> <Button
<Button className='flex items-center w-full self-stretch gap-2 px-3 py-2 bg-components-button-tertiary-bg-hover hover:bg-components-button-tertiary-bg-hover rounded-lg cursor-pointer border-none'
className='flex items-center w-full self-stretch gap-2 px-3 py-2 bg-components-button-tertiary-bg-hover hover:bg-components-button-tertiary-bg-hover rounded-lg cursor-pointer border-none' onClick={handleOnShowIterationDetail}
onClick={handleOnShowIterationDetail} >
> <Iteration className='w-4 h-4 text-components-button-tertiary-text shrink-0' />
<Iteration className='w-4 h-4 text-components-button-tertiary-text shrink-0' /> <div className='flex-1 text-left system-sm-medium text-components-button-tertiary-text'>{t('workflow.nodes.iteration.iteration', { count: getCount(nodeInfo.details?.length, nodeInfo.metadata?.iterator_length) })}{getErrorCount(nodeInfo.details) > 0 && (
<div className='flex-1 text-left system-sm-medium text-components-button-tertiary-text'>{t('workflow.nodes.iteration.iteration', { count: getCount(nodeInfo.details?.length, nodeInfo.metadata?.iterator_length) })}{getErrorCount(nodeInfo.details) > 0 && ( <>
<> {t('workflow.nodes.iteration.comma')}
{t('workflow.nodes.iteration.comma')} {t('workflow.nodes.iteration.error', { count: getErrorCount(nodeInfo.details) })}
{t('workflow.nodes.iteration.error', { count: getErrorCount(nodeInfo.details) })} </>
</> )}</div>
)}</div> <RiArrowRightSLine className='w-4 h-4 text-components-button-tertiary-text shrink-0' />
{justShowIterationNavArrow </Button>
? (
<RiArrowRightSLine className='w-4 h-4 text-components-button-tertiary-text shrink-0' />
)
: (
<div className='flex items-center space-x-1 text-[#155EEF]'>
<div className='text-[13px] font-normal '>{t('workflow.common.viewDetailInTracingPanel')}</div>
<RiArrowRightSLine className='w-4 h-4 text-components-button-tertiary-text shrink-0' />
</div>
)}
</Button>
<Split className='mt-2' />
</div>
) )
} }

@ -36,8 +36,6 @@ type Props = {
onShowRetryDetail?: (detail: NodeTracing[]) => void onShowRetryDetail?: (detail: NodeTracing[]) => void
onShowAgentResultList?: (detail: AgentLogItemWithChildren[]) => void onShowAgentResultList?: (detail: AgentLogItemWithChildren[]) => void
notShowIterationNav?: boolean notShowIterationNav?: boolean
justShowIterationNavArrow?: boolean
justShowRetryNavArrow?: boolean
} }
const NodePanel: FC<Props> = ({ const NodePanel: FC<Props> = ({
@ -50,7 +48,6 @@ const NodePanel: FC<Props> = ({
onShowRetryDetail, onShowRetryDetail,
onShowAgentResultList, onShowAgentResultList,
notShowIterationNav, notShowIterationNav,
justShowIterationNavArrow,
}) => { }) => {
const [collapseState, doSetCollapseState] = useState<boolean>(true) const [collapseState, doSetCollapseState] = useState<boolean>(true)
const setCollapseState = useCallback((state: boolean) => { const setCollapseState = useCallback((state: boolean) => {
@ -138,7 +135,6 @@ const NodePanel: FC<Props> = ({
<IterationLogTrigger <IterationLogTrigger
nodeInfo={nodeInfo} nodeInfo={nodeInfo}
onShowIterationResultList={onShowIterationDetail} onShowIterationResultList={onShowIterationDetail}
justShowIterationNavArrow={justShowIterationNavArrow}
/> />
)} )}
{isRetryNode && onShowRetryDetail && ( {isRetryNode && onShowRetryDetail && (

@ -1,19 +1,20 @@
'use client' 'use client'
import type { FC } from 'react' import type { FC } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import {
RiArrowRightSLine,
RiRestartFill,
} from '@remixicon/react'
import StatusPanel from './status' import StatusPanel from './status'
import MetaData from './meta' import MetaData from './meta'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip' import ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip'
import type { NodeTracing } from '@/types/workflow' import type { NodeTracing } from '@/types/workflow'
import Button from '@/app/components/base/button' import { BlockEnum } from '@/app/components/workflow/types'
import { hasRetryNode } from '@/app/components/workflow/utils'
import { IterationLogTrigger } from '@/app/components/workflow/run/iteration-log'
import { RetryLogTrigger } from '@/app/components/workflow/run/retry-log'
import { AgentLogTrigger } from '@/app/components/workflow/run/agent-log'
type ResultPanelProps = { type ResultPanelProps = {
nodeInfo?: NodeTracing
inputs?: string inputs?: string
process_data?: string process_data?: string
outputs?: string outputs?: string
@ -28,11 +29,13 @@ type ResultPanelProps = {
showSteps?: boolean showSteps?: boolean
exceptionCounts?: number exceptionCounts?: number
execution_metadata?: any execution_metadata?: any
retry_events?: NodeTracing[] handleShowIterationResultList?: (detail: NodeTracing[][], iterDurationMap: any) => void
onShowRetryDetail?: (retries: NodeTracing[]) => void onShowRetryDetail?: (detail: NodeTracing[]) => void
onShowAgentResultList?: () => void
} }
const ResultPanel: FC<ResultPanelProps> = ({ const ResultPanel: FC<ResultPanelProps> = ({
nodeInfo,
inputs, inputs,
process_data, process_data,
outputs, outputs,
@ -46,10 +49,14 @@ const ResultPanel: FC<ResultPanelProps> = ({
showSteps, showSteps,
exceptionCounts, exceptionCounts,
execution_metadata, execution_metadata,
retry_events, handleShowIterationResultList,
onShowRetryDetail, onShowRetryDetail,
onShowAgentResultList,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const isIterationNode = nodeInfo?.node_type === BlockEnum.Iteration
const isRetryNode = hasRetryNode(nodeInfo?.node_type) && nodeInfo?.retryDetail
const isAgentNode = nodeInfo?.node_type === BlockEnum.Agent
return ( return (
<div className='bg-components-panel-bg py-2'> <div className='bg-components-panel-bg py-2'>
@ -62,23 +69,32 @@ const ResultPanel: FC<ResultPanelProps> = ({
exceptionCounts={exceptionCounts} exceptionCounts={exceptionCounts}
/> />
</div> </div>
{ <div className='px-4'>
retry_events?.length && onShowRetryDetail && ( {
<div className='px-4'> isIterationNode && handleShowIterationResultList && (
<Button <IterationLogTrigger
className='flex items-center justify-between w-full' nodeInfo={nodeInfo}
variant='tertiary' onShowIterationResultList={handleShowIterationResultList}
onClick={() => onShowRetryDetail(retry_events)} />
> )
<div className='flex items-center'> }
<RiRestartFill className='mr-0.5 w-4 h-4 text-components-button-tertiary-text shrink-0' /> {
{t('workflow.nodes.common.retry.retries', { num: retry_events?.length })} isRetryNode && onShowRetryDetail && (
</div> <RetryLogTrigger
<RiArrowRightSLine className='w-4 h-4 text-components-button-tertiary-text shrink-0' /> nodeInfo={nodeInfo}
</Button> onShowRetryResultList={onShowRetryDetail}
</div> />
) )
} }
{
isAgentNode && onShowAgentResultList && (
<AgentLogTrigger
nodeInfo={nodeInfo}
onShowAgentResultList={onShowAgentResultList}
/>
)
}
</div>
<div className='px-4 py-2 flex flex-col gap-2'> <div className='px-4 py-2 flex flex-col gap-2'>
<CodeEditor <CodeEditor
readOnly readOnly

@ -7,18 +7,18 @@ import type {
NodeTracing, NodeTracing,
} from '@/types/workflow' } from '@/types/workflow'
type SpecialResultPanelProps = { export type SpecialResultPanelProps = {
showRetryDetail: boolean showRetryDetail?: boolean
setShowRetryDetailFalse: () => void setShowRetryDetailFalse?: () => void
retryResultList: NodeTracing[] retryResultList?: NodeTracing[]
showIteratingDetail: boolean showIteratingDetail?: boolean
setShowIteratingDetailFalse: () => void setShowIteratingDetailFalse?: () => void
iterationResultList: NodeTracing[][] iterationResultList?: NodeTracing[][]
iterationResultDurationMap: IterationDurationMap iterationResultDurationMap?: IterationDurationMap
agentResultList: AgentLogItemWithChildren[] agentResultList?: AgentLogItemWithChildren[]
setAgentResultList: (list: AgentLogItemWithChildren[]) => void setAgentResultList?: (list: AgentLogItemWithChildren[]) => void
} }
const SpecialResultPanel = ({ const SpecialResultPanel = ({
showRetryDetail, showRetryDetail,
@ -36,7 +36,7 @@ const SpecialResultPanel = ({
return ( return (
<> <>
{ {
showRetryDetail && ( !!showRetryDetail && !!retryResultList?.length && setShowRetryDetailFalse && (
<RetryResultPanel <RetryResultPanel
list={retryResultList} list={retryResultList}
onBack={setShowRetryDetailFalse} onBack={setShowRetryDetailFalse}
@ -44,7 +44,7 @@ const SpecialResultPanel = ({
) )
} }
{ {
showIteratingDetail && ( showIteratingDetail && !!iterationResultList?.length && setShowIteratingDetailFalse && (
<IterationResultPanel <IterationResultPanel
list={iterationResultList} list={iterationResultList}
onBack={setShowIteratingDetailFalse} onBack={setShowIteratingDetailFalse}
@ -53,7 +53,7 @@ const SpecialResultPanel = ({
) )
} }
{ {
!!agentResultList.length && ( !!agentResultList?.length && setAgentResultList && (
<AgentResultPanel <AgentResultPanel
list={agentResultList} list={agentResultList}
setAgentResultList={setAgentResultList} setAgentResultList={setAgentResultList}

@ -137,8 +137,6 @@ const TracingPanel: FC<TracingPanelProps> = ({
onShowIterationDetail={handleShowIterationResultList} onShowIterationDetail={handleShowIterationResultList}
onShowRetryDetail={handleShowRetryResultList} onShowRetryDetail={handleShowRetryResultList}
onShowAgentResultList={setAgentResultList} onShowAgentResultList={setAgentResultList}
justShowIterationNavArrow={true}
justShowRetryNavArrow={true}
hideInfo={hideNodeInfo} hideInfo={hideNodeInfo}
hideProcessDetail={hideNodeProcessDetail} hideProcessDetail={hideNodeProcessDetail}
/> />

@ -1,6 +1,8 @@
import { BlockEnum } from '@/app/components/workflow/types' import { BlockEnum } from '@/app/components/workflow/types'
import type { AgentLogItem, AgentLogItemWithChildren, NodeTracing } from '@/types/workflow' import type { AgentLogItem, AgentLogItemWithChildren, NodeTracing } from '@/types/workflow'
const supportedAgentLogNodes = [BlockEnum.Agent, BlockEnum.Tool]
const listToTree = (logs: AgentLogItem[]) => { const listToTree = (logs: AgentLogItem[]) => {
if (!logs || logs.length === 0) if (!logs || logs.length === 0)
return [] return []
@ -24,7 +26,7 @@ const listToTree = (logs: AgentLogItem[]) => {
} }
const format = (list: NodeTracing[]): NodeTracing[] => { const format = (list: NodeTracing[]): NodeTracing[] => {
const result: NodeTracing[] = list.map((item) => { const result: NodeTracing[] = list.map((item) => {
if (item.node_type === BlockEnum.Agent && item.execution_metadata?.agent_log && item.execution_metadata?.agent_log.length > 0) if (supportedAgentLogNodes.includes(item.node_type) && item.execution_metadata?.agent_log && item.execution_metadata?.agent_log.length > 0)
item.agentLog = listToTree(item.execution_metadata.agent_log) item.agentLog = listToTree(item.execution_metadata.agent_log)
return item return item

@ -1,22 +1,25 @@
import { BlockEnum } from '@/app/components/workflow/types' import { BlockEnum } from '@/app/components/workflow/types'
import type { NodeTracing } from '@/types/workflow' import type { NodeTracing } from '@/types/workflow'
function printNodeStructure(node: NodeTracing, level: number) { function printNodeStructure(node: NodeTracing, depth: number) {
const indent = ' '.repeat(level) const indent = ' '.repeat(depth)
console.log(`${indent}${node.title}`) console.log(`${indent}${node.title}`)
if (node.parallelDetail?.children) { if (node.parallelDetail?.children) {
node.parallelDetail.children.forEach((child) => { node.parallelDetail.children.forEach((child) => {
printNodeStructure(child, level + 1) printNodeStructure(child, depth + 1)
}) })
} }
} }
function addTitle({ function addTitle({
list, level, parallelNumRecord, list, depth, belongParallelIndexInfo,
}: { }: {
list: NodeTracing[], level: number, parallelNumRecord: Record<string, number> list: NodeTracing[],
depth: number,
belongParallelIndexInfo?: string,
}, t: any) { }, t: any) {
let branchIndex = 0 let branchIndex = 0
const hasMoreThanOneParallel = list.filter(node => node.parallelDetail?.isParallelStartNode).length > 1
list.forEach((node) => { list.forEach((node) => {
const parallel_id = node.parallel_id ?? node.execution_metadata?.parallel_id ?? null const parallel_id = node.parallel_id ?? node.execution_metadata?.parallel_id ?? null
const parallel_start_node_id = node.parallel_start_node_id ?? node.execution_metadata?.parallel_start_node_id ?? null const parallel_start_node_id = node.parallel_start_node_id ?? node.execution_metadata?.parallel_start_node_id ?? null
@ -26,15 +29,20 @@ function addTitle({
return return
const isParallelStartNode = node.parallelDetail?.isParallelStartNode const isParallelStartNode = node.parallelDetail?.isParallelStartNode
if (isParallelStartNode)
parallelNumRecord.num++
const letter = parallelNumRecord.num > 1 ? String.fromCharCode(64 + level) : '' const parallelIndexLetter = (() => {
const parallelLevelInfo = `${parallelNumRecord.num}${letter}` if (!isParallelStartNode || !hasMoreThanOneParallel)
return ''
const index = 1 + list.filter(node => node.parallelDetail?.isParallelStartNode).findIndex(item => item.node_id === node.node_id)
return String.fromCharCode(64 + index)
})()
const parallelIndexInfo = `${depth}${parallelIndexLetter}`
if (isParallelStartNode) { if (isParallelStartNode) {
node.parallelDetail!.isParallelStartNode = true node.parallelDetail!.isParallelStartNode = true
node.parallelDetail!.parallelTitle = `${t('workflow.common.parallel')}-${parallelLevelInfo}` node.parallelDetail!.parallelTitle = `${t('workflow.common.parallel')}-${parallelIndexInfo}`
} }
const isBrachStartNode = parallel_start_node_id === node.node_id const isBrachStartNode = parallel_start_node_id === node.node_id
@ -47,14 +55,14 @@ function addTitle({
} }
} }
node.parallelDetail!.branchTitle = `${t('workflow.common.branch')}-${parallelLevelInfo}-${branchLetter}` node.parallelDetail!.branchTitle = `${t('workflow.common.branch')}-${belongParallelIndexInfo}-${branchLetter}`
} }
if (node.parallelDetail?.children && node.parallelDetail.children.length > 0) { if (node.parallelDetail?.children && node.parallelDetail.children.length > 0) {
addTitle({ addTitle({
list: node.parallelDetail.children, list: node.parallelDetail.children,
level: level + 1, depth: depth + 1,
parallelNumRecord, belongParallelIndexInfo: parallelIndexInfo,
}, t) }, t)
} }
}) })
@ -70,7 +78,7 @@ const format = (list: NodeTracing[], t: any): NodeTracing[] => {
const parallel_id = node.parallel_id ?? node.execution_metadata?.parallel_id ?? null const parallel_id = node.parallel_id ?? node.execution_metadata?.parallel_id ?? null
const parent_parallel_id = node.parent_parallel_id ?? node.execution_metadata?.parent_parallel_id ?? null const parent_parallel_id = node.parent_parallel_id ?? node.execution_metadata?.parent_parallel_id ?? null
const branchStartNodeId = node.parallel_start_node_id ?? node.execution_metadata?.parallel_start_node_id ?? null const branchStartNodeId = node.parallel_start_node_id ?? node.execution_metadata?.parallel_start_node_id ?? null
const parent_parallel_start_node_id = node.parent_parallel_start_node_id ?? node.execution_metadata?.parent_parallel_start_node_id ?? null const parentParallelBranchStartNodeId = node.parent_parallel_start_node_id ?? node.execution_metadata?.parent_parallel_start_node_id ?? null
const isNotInParallel = !parallel_id || node.node_type === BlockEnum.End const isNotInParallel = !parallel_id || node.node_type === BlockEnum.End
if (isNotInParallel) if (isNotInParallel)
return return
@ -87,16 +95,24 @@ const format = (list: NodeTracing[], t: any): NodeTracing[] => {
if (isRootLevel) if (isRootLevel)
return return
const parentParallelStartNode = result.find(item => item.node_id === parent_parallel_start_node_id) const parentParallelStartNode = result.find(item => item.node_id === parentParallelBranchStartNodeId)
// append to parent parallel start node // append to parent parallel start node and after the same branch
if (parentParallelStartNode) { if (parentParallelStartNode) {
if (!parentParallelStartNode?.parallelDetail) { if (!parentParallelStartNode?.parallelDetail) {
parentParallelStartNode!.parallelDetail = { parentParallelStartNode!.parallelDetail = {
children: [], children: [],
} }
} }
if (parentParallelStartNode!.parallelDetail.children) if (parentParallelStartNode!.parallelDetail.children) {
parentParallelStartNode!.parallelDetail.children.push(node) const sameBranchNodesLastIndex = parentParallelStartNode.parallelDetail.children.findLastIndex((node) => {
const currStartNodeId = node.parallel_start_node_id ?? node.execution_metadata?.parallel_start_node_id ?? null
return currStartNodeId === parentParallelBranchStartNodeId
})
if (sameBranchNodesLastIndex !== -1)
parentParallelStartNode!.parallelDetail.children.splice(sameBranchNodesLastIndex + 1, 0, node)
else
parentParallelStartNode!.parallelDetail.children.push(node)
}
} }
return return
} }
@ -144,14 +160,9 @@ const format = (list: NodeTracing[], t: any): NodeTracing[] => {
// console.log(`----- p: ${now} end -----`) // console.log(`----- p: ${now} end -----`)
// }) // })
const parallelNumRecord: Record<string, number> = {
num: 0,
}
addTitle({ addTitle({
list: filteredInParallelSubNodes, list: filteredInParallelSubNodes,
level: 1, depth: 1,
parallelNumRecord,
}, t) }, t)
return filteredInParallelSubNodes return filteredInParallelSubNodes

@ -73,6 +73,7 @@ const translation = {
placeholder: 'Select a tool...', placeholder: 'Select a tool...',
auth: 'AUTHORIZATION', auth: 'AUTHORIZATION',
settings: 'TOOL SETTINGS', settings: 'TOOL SETTINGS',
empty: 'Click the \'+\' button to add tools. You can add multiple tools.',
}, },
configureApp: 'Configure App', configureApp: 'Configure App',
configureModel: 'Configure model', configureModel: 'Configure model',

@ -73,6 +73,7 @@ const translation = {
placeholder: '选择工具', placeholder: '选择工具',
auth: '授权', auth: '授权',
settings: '工具设置', settings: '工具设置',
empty: '点击 "+" 按钮添加工具。您可以添加多个工具。',
}, },
configureApp: '应用设置', configureApp: '应用设置',
configureModel: '模型设置', configureModel: '模型设置',

Loading…
Cancel
Save