feat(workflow): workflow as tool output schema (#26241)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>
Co-authored-by: Novice <novice12185727@gmail.com>
This commit is contained in:
CrabSAMA
2025-11-27 16:50:48 +08:00
committed by GitHub
parent 299bd351fd
commit 820925a866
21 changed files with 438 additions and 34 deletions

View File

@@ -11,8 +11,8 @@ import WorkflowToolModal from '@/app/components/tools/workflow-tool'
import Loading from '@/app/components/base/loading'
import Toast from '@/app/components/base/toast'
import { createWorkflowToolProvider, fetchWorkflowToolDetailByAppID, saveWorkflowToolProvider } from '@/service/tools'
import type { Emoji, WorkflowToolProviderParameter, WorkflowToolProviderRequest, WorkflowToolProviderResponse } from '@/app/components/tools/types'
import type { InputVar } from '@/app/components/workflow/types'
import type { Emoji, WorkflowToolProviderOutputParameter, WorkflowToolProviderParameter, WorkflowToolProviderRequest, WorkflowToolProviderResponse } from '@/app/components/tools/types'
import type { InputVar, Variable } from '@/app/components/workflow/types'
import type { PublishWorkflowParams } from '@/types/workflow'
import { useAppContext } from '@/context/app-context'
import { useInvalidateAllWorkflowTools } from '@/service/use-tools'
@@ -26,6 +26,7 @@ type Props = {
name: string
description: string
inputs?: InputVar[]
outputs?: Variable[]
handlePublish: (params?: PublishWorkflowParams) => Promise<void>
onRefreshData?: () => void
disabledReason?: string
@@ -40,6 +41,7 @@ const WorkflowToolConfigureButton = ({
name,
description,
inputs,
outputs,
handlePublish,
onRefreshData,
disabledReason,
@@ -80,6 +82,8 @@ const WorkflowToolConfigureButton = ({
const payload = useMemo(() => {
let parameters: WorkflowToolProviderParameter[] = []
let outputParameters: WorkflowToolProviderOutputParameter[] = []
if (!published) {
parameters = (inputs || []).map((item) => {
return {
@@ -90,6 +94,13 @@ const WorkflowToolConfigureButton = ({
type: item.type,
}
})
outputParameters = (outputs || []).map((item) => {
return {
name: item.variable,
description: '',
type: item.value_type,
}
})
}
else if (detail && detail.tool) {
parameters = (inputs || []).map((item) => {
@@ -101,6 +112,14 @@ const WorkflowToolConfigureButton = ({
form: detail.tool.parameters.find(param => param.name === item.variable)?.form || 'llm',
}
})
outputParameters = (outputs || []).map((item) => {
const found = detail.tool.output_schema?.properties?.[item.variable]
return {
name: item.variable,
description: found ? found.description : '',
type: item.value_type,
}
})
}
return {
icon: detail?.icon || icon,
@@ -108,6 +127,7 @@ const WorkflowToolConfigureButton = ({
name: detail?.name || '',
description: detail?.description || description,
parameters,
outputParameters,
labels: detail?.tool?.labels || [],
privacy_policy: detail?.privacy_policy || '',
...(published

View File

@@ -1,9 +1,9 @@
'use client'
import type { FC } from 'react'
import React, { useState } from 'react'
import React, { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { produce } from 'immer'
import type { Emoji, WorkflowToolProviderParameter, WorkflowToolProviderRequest } from '../types'
import type { Emoji, WorkflowToolProviderOutputParameter, WorkflowToolProviderParameter, WorkflowToolProviderRequest } from '../types'
import cn from '@/utils/classnames'
import Drawer from '@/app/components/base/drawer-plus'
import Input from '@/app/components/base/input'
@@ -16,6 +16,8 @@ import MethodSelector from '@/app/components/tools/workflow-tool/method-selector
import LabelSelector from '@/app/components/tools/labels/selector'
import ConfirmModal from '@/app/components/tools/workflow-tool/confirm-modal'
import Tooltip from '@/app/components/base/tooltip'
import { VarType } from '@/app/components/workflow/types'
import { RiErrorWarningLine } from '@remixicon/react'
type Props = {
isAdd?: boolean
@@ -45,7 +47,29 @@ const WorkflowToolAsModal: FC<Props> = ({
const [name, setName] = useState(payload.name)
const [description, setDescription] = useState(payload.description)
const [parameters, setParameters] = useState<WorkflowToolProviderParameter[]>(payload.parameters)
const handleParameterChange = (key: string, value: string, index: number) => {
const outputParameters = useMemo<WorkflowToolProviderOutputParameter[]>(() => payload.outputParameters, [payload.outputParameters])
const reservedOutputParameters: WorkflowToolProviderOutputParameter[] = [
{
name: 'text',
description: t('workflow.nodes.tool.outputVars.text'),
type: VarType.string,
reserved: true,
},
{
name: 'files',
description: t('workflow.nodes.tool.outputVars.files.title'),
type: VarType.arrayFile,
reserved: true,
},
{
name: 'json',
description: t('workflow.nodes.tool.outputVars.json'),
type: VarType.arrayObject,
reserved: true,
},
]
const handleParameterChange = (key: string, value: any, index: number) => {
const newData = produce(parameters, (draft: WorkflowToolProviderParameter[]) => {
if (key === 'description')
draft[index].description = value
@@ -69,6 +93,10 @@ const WorkflowToolAsModal: FC<Props> = ({
return /^\w+$/.test(name)
}
const isOutputParameterReserved = (name: string) => {
return reservedOutputParameters.find(p => p.name === name)
}
const onConfirm = () => {
let errorMessage = ''
if (!label)
@@ -225,6 +253,51 @@ const WorkflowToolAsModal: FC<Props> = ({
</table>
</div>
</div>
{/* Tool Output */}
<div>
<div className='system-sm-medium py-2 text-text-primary'>{t('tools.createTool.toolOutput.title')}</div>
<div className='w-full overflow-x-auto rounded-lg border border-divider-regular'>
<table className='w-full text-xs font-normal leading-[18px] text-text-secondary'>
<thead className='uppercase text-text-tertiary'>
<tr className='border-b border-divider-regular'>
<th className="w-[156px] p-2 pl-3 font-medium">{t('tools.createTool.name')}</th>
<th className="p-2 pl-3 font-medium">{t('tools.createTool.toolOutput.description')}</th>
</tr>
</thead>
<tbody>
{[...reservedOutputParameters, ...outputParameters].map((item, index) => (
<tr key={index} className='border-b border-divider-regular last:border-0'>
<td className="max-w-[156px] p-2 pl-3">
<div className='text-[13px] leading-[18px]'>
<div title={item.name} className='flex items-center'>
<span className='truncate font-medium text-text-primary'>{item.name}</span>
<span className='shrink-0 pl-1 text-xs leading-[18px] text-[#ec4a0a]'>{item.reserved ? t('tools.createTool.toolOutput.reserved') : ''}</span>
{
!item.reserved && isOutputParameterReserved(item.name) ? (
<Tooltip
popupContent={
<div className='w-[180px]'>
{t('tools.createTool.toolOutput.reservedParameterDuplicateTip')}
</div>
}
>
<RiErrorWarningLine className='h-3 w-3 text-text-warning-secondary' />
</Tooltip>
) : null
}
</div>
<div className='text-text-tertiary'>{item.type}</div>
</div>
</td>
<td className="w-[236px] p-2 pl-3 text-text-tertiary">
<span className='text-[13px] font-normal leading-[18px] text-text-secondary'>{item.description}</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Tags */}
<div>
<div className='system-sm-medium py-2 text-text-primary'>{t('tools.createTool.toolInput.label')}</div>