merge feat/plugins

This commit is contained in:
zxhlyh 2024-12-27 18:11:52 +08:00
commit bc78803171
24 changed files with 440 additions and 232 deletions

View File

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

View File

@ -19,6 +19,7 @@ import Tooltip from '@/app/components/base/tooltip'
import Radio from '@/app/components/base/radio'
import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-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 RadioE from '@/app/components/base/radio/ui'
@ -328,7 +329,35 @@ function Form<
scope={scope}
disabled={readonly}
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)}
{validating && changeKey === variable && <ValidatingTip />}
</div>

View File

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

View File

@ -1,12 +1,148 @@
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 = {
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 (
<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>
))}
</>
)}
</>
)
}

View File

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

View File

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

View File

@ -10,6 +10,7 @@ import { Agent } from '@/app/components/base/icons/src/vender/workflow'
import { InputNumber } from '@/app/components/base/input-number'
import Slider from '@/app/components/base/slider'
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 type { ComponentProps } from 'react'
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 })
}
return (
<Field title={'tool selector'} tooltip={'tool selector'}>
<Field title={schema.label[language]} tooltip={schema.tooltip?.[language]}>
<ToolSelector
scope={schema.scope}
value={value}
onSelect={item => onChange(item)}
onDelete={() => onChange(null)}
/>
</Field>
)
}
case 'array[tools]': {
return <Field title={'tool selector'} tooltip={'tool selector'}>
multiple tool selector TODO
</Field>
const value = props.value[schema.variable]
const onChange = (value: any) => {
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
/>
)
}
}
}

View File

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

View File

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

View File

@ -9,6 +9,7 @@ import { ToolIcon } from './components/tool-icon'
import useConfig from './use-config'
import { useTranslation } from 'react-i18next'
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 { inputs, currentStrategy } = useConfig(props.id, props.data)
@ -16,11 +17,26 @@ const AgentNode: FC<NodeProps<AgentNodeType>> = (props) => {
const pluginList = useInstalledPluginList()
// TODO: Implement models
const models = useMemo(() => {
const models = []
if (!inputs) return []
// if selected, show in node
// if required and not selected, show empty selector
// 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: Array<ToolIconProps> = []
@ -49,24 +65,26 @@ const AgentNode: FC<NodeProps<AgentNodeType>> = (props) => {
{inputs.agent_strategy_label}
</SettingItem>
: <SettingItem label={t('workflow.nodes.agent.strategyNotSet')} />}
<Group
{models.length && <Group
label={<GroupLabel className='mt-1'>
{t('workflow.nodes.agent.model')}
</GroupLabel>}
>
<ModelSelector
modelList={[]}
readonly
/>
<ModelSelector
modelList={[]}
readonly
/>
<ModelSelector
modelList={[]}
readonly
/>
</Group>
{models.map((model) => {
return <ModelSelector
key={model.param}
modelList={[]}
defaultModel={
'provider' in model
? {
provider: model.provider,
model: model.model,
}
: undefined}
readonly
/>
})}
</Group>}
<Group label={<GroupLabel className='mt-1'>
{t('workflow.nodes.agent.toolbox')}
</GroupLabel>}>

View File

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

View File

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

View File

@ -1,13 +1,9 @@
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import {
RiArrowRightSLine,
} from '@remixicon/react'
import VarReferencePicker from '../_base/components/variable/var-reference-picker'
import Split from '../_base/components/split'
import ResultPanel from '../../run/result-panel'
import { IterationResultPanel } from '../../run/iteration-log'
import { MAX_ITERATION_PARALLEL_NUM, MIN_ITERATION_PARALLEL_NUM } from '../../constants'
import type { IterationNodeType } from './types'
import useConfig from './use-config'
@ -18,6 +14,9 @@ import Switch from '@/app/components/base/switch'
import Select from '@/app/components/base/select'
import Slider from '@/app/components/base/slider'
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'
@ -50,9 +49,6 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
handleOutputVarChange,
isShowSingleRun,
hideSingleRun,
isShowIterationDetail,
backToSingleRun,
showIterationDetail,
runningStatus,
handleRun,
handleStop,
@ -69,6 +65,9 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
changeParallelNums,
} = useConfig(id, data)
const nodeInfo = formatTracing(iterationRunResult, t)[0]
const logsParams = useLogs()
return (
<div className='pt-2 pb-2'>
<div className='px-4 pb-4 space-y-4'>
@ -163,26 +162,12 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
runningStatus={runningStatus}
onRun={handleRun}
onStop={handleStop}
{...logsParams}
result={
<div className='mt-3'>
<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>
<ResultPanel {...runResult} showSteps={false} nodeInfo={nodeInfo} {...logsParams} />
}
/>
)}
{isShowIterationDetail && (
<IterationResultPanel
onBack={backToSingleRun}
list={iterationRunResult}
/>
)}
</div>
)
}

View File

@ -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 Tooltip from '@/app/components/base/tooltip'
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'
@ -70,10 +69,6 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
runResult,
filterJinjia2InputVar,
} = useConfig(id, data)
const {
retryDetails,
handleRetryDetailsChange,
} = useRetryDetailShowInSingleRun()
const model = inputs.model
@ -293,9 +288,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
runningStatus={runningStatus}
onRun={handleRun}
onStop={handleStop}
retryDetails={retryDetails}
onRetryDetailBack={handleRetryDetailsChange}
result={<ResultPanel {...runResult} showSteps={false} onShowRetryDetail={handleRetryDetailsChange} />}
result={<ResultPanel {...runResult} showSteps={false} />}
/>
)}
</div>

View File

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

View File

@ -6,17 +6,14 @@ import type {
NodeTracing,
} from '@/types/workflow'
import { Iteration } from '@/app/components/base/icons/src/vender/workflow'
import Split from '@/app/components/workflow/nodes/_base/components/split'
type IterationLogTriggerProps = {
nodeInfo: NodeTracing
onShowIterationResultList: (iterationResultList: NodeTracing[][], iterationResultDurationMap: IterationDurationMap) => void
justShowIterationNavArrow?: boolean
}
const IterationLogTrigger = ({
nodeInfo,
onShowIterationResultList,
justShowIterationNavArrow,
}: IterationLogTriggerProps) => {
const { t } = useTranslation()
const getErrorCount = (details: NodeTracing[][] | undefined) => {
@ -41,31 +38,19 @@ const IterationLogTrigger = ({
onShowIterationResultList(nodeInfo.details || [], nodeInfo?.iterDurationMap || nodeInfo.execution_metadata?.iteration_duration_map || {})
}
return (
<div className='mt-2 mb-1 !px-2'>
<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'
onClick={handleOnShowIterationDetail}
>
<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 && (
<>
{t('workflow.nodes.iteration.comma')}
{t('workflow.nodes.iteration.error', { count: getErrorCount(nodeInfo.details) })}
</>
)}</div>
{justShowIterationNavArrow
? (
<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>
<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'
onClick={handleOnShowIterationDetail}
>
<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 && (
<>
{t('workflow.nodes.iteration.comma')}
{t('workflow.nodes.iteration.error', { count: getErrorCount(nodeInfo.details) })}
</>
)}</div>
<RiArrowRightSLine className='w-4 h-4 text-components-button-tertiary-text shrink-0' />
</Button>
)
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,8 @@
import { BlockEnum } from '@/app/components/workflow/types'
import type { AgentLogItem, AgentLogItemWithChildren, NodeTracing } from '@/types/workflow'
const supportedAgentLogNodes = [BlockEnum.Agent, BlockEnum.Tool]
const listToTree = (logs: AgentLogItem[]) => {
if (!logs || logs.length === 0)
return []
@ -24,7 +26,7 @@ const listToTree = (logs: AgentLogItem[]) => {
}
const format = (list: NodeTracing[]): NodeTracing[] => {
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)
return item

View File

@ -1,22 +1,25 @@
import { BlockEnum } from '@/app/components/workflow/types'
import type { NodeTracing } from '@/types/workflow'
function printNodeStructure(node: NodeTracing, level: number) {
const indent = ' '.repeat(level)
function printNodeStructure(node: NodeTracing, depth: number) {
const indent = ' '.repeat(depth)
console.log(`${indent}${node.title}`)
if (node.parallelDetail?.children) {
node.parallelDetail.children.forEach((child) => {
printNodeStructure(child, level + 1)
printNodeStructure(child, depth + 1)
})
}
}
function addTitle({
list, level, parallelNumRecord,
list, depth, belongParallelIndexInfo,
}: {
list: NodeTracing[], level: number, parallelNumRecord: Record<string, number>
list: NodeTracing[],
depth: number,
belongParallelIndexInfo?: string,
}, t: any) {
let branchIndex = 0
const hasMoreThanOneParallel = list.filter(node => node.parallelDetail?.isParallelStartNode).length > 1
list.forEach((node) => {
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
@ -26,15 +29,20 @@ function addTitle({
return
const isParallelStartNode = node.parallelDetail?.isParallelStartNode
if (isParallelStartNode)
parallelNumRecord.num++
const letter = parallelNumRecord.num > 1 ? String.fromCharCode(64 + level) : ''
const parallelLevelInfo = `${parallelNumRecord.num}${letter}`
const parallelIndexLetter = (() => {
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) {
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
@ -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) {
addTitle({
list: node.parallelDetail.children,
level: level + 1,
parallelNumRecord,
depth: depth + 1,
belongParallelIndexInfo: parallelIndexInfo,
}, 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 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 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
if (isNotInParallel)
return
@ -87,16 +95,24 @@ const format = (list: NodeTracing[], t: any): NodeTracing[] => {
if (isRootLevel)
return
const parentParallelStartNode = result.find(item => item.node_id === parent_parallel_start_node_id)
// append to parent parallel start node
const parentParallelStartNode = result.find(item => item.node_id === parentParallelBranchStartNodeId)
// append to parent parallel start node and after the same branch
if (parentParallelStartNode) {
if (!parentParallelStartNode?.parallelDetail) {
parentParallelStartNode!.parallelDetail = {
children: [],
}
}
if (parentParallelStartNode!.parallelDetail.children)
parentParallelStartNode!.parallelDetail.children.push(node)
if (parentParallelStartNode!.parallelDetail.children) {
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
}
@ -144,14 +160,9 @@ const format = (list: NodeTracing[], t: any): NodeTracing[] => {
// console.log(`----- p: ${now} end -----`)
// })
const parallelNumRecord: Record<string, number> = {
num: 0,
}
addTitle({
list: filteredInParallelSubNodes,
level: 1,
parallelNumRecord,
depth: 1,
}, t)
return filteredInParallelSubNodes

View File

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

View File

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