mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-16 15:46:00 +08:00
feat: handle tool output ui
This commit is contained in:
parent
1193ab12fc
commit
722d35e9e7
@ -3,6 +3,8 @@ import type { FC, ReactNode } from 'react'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
|
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
|
||||||
|
import TreeIndentLine from './variable/object-child-tree-panel/tree-indent-line'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
className?: string
|
className?: string
|
||||||
@ -41,6 +43,7 @@ type VarItemProps = {
|
|||||||
type: string
|
type: string
|
||||||
description: string
|
description: string
|
||||||
}[]
|
}[]
|
||||||
|
isIndent?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VarItem: FC<VarItemProps> = ({
|
export const VarItem: FC<VarItemProps> = ({
|
||||||
@ -48,29 +51,33 @@ export const VarItem: FC<VarItemProps> = ({
|
|||||||
type,
|
type,
|
||||||
description,
|
description,
|
||||||
subItems,
|
subItems,
|
||||||
|
isIndent,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className='py-1'>
|
<div className={cn('flex', isIndent && 'relative left-[-7px]')}>
|
||||||
<div className='flex justify-between'>
|
{isIndent && <TreeIndentLine depth={1} />}
|
||||||
<div className='flex items-center leading-[18px]'>
|
<div className='py-1'>
|
||||||
<div className='code-sm-semibold text-text-secondary'>{name}</div>
|
<div className='flex'>
|
||||||
<div className='system-xs-regular ml-2 text-text-tertiary'>{type}</div>
|
<div className='flex items-center leading-[18px]'>
|
||||||
</div>
|
<div className='code-sm-semibold text-text-secondary'>{name}</div>
|
||||||
</div>
|
<div className='system-xs-regular ml-2 text-text-tertiary'>{type}</div>
|
||||||
<div className='system-xs-regular mt-0.5 text-text-tertiary'>
|
|
||||||
{description}
|
|
||||||
{subItems && (
|
|
||||||
<div className='ml-2 border-l border-gray-200 pl-2'>
|
|
||||||
{subItems.map((item, index) => (
|
|
||||||
<VarItem
|
|
||||||
key={index}
|
|
||||||
name={item.name}
|
|
||||||
type={item.type}
|
|
||||||
description={item.description}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
<div className='system-xs-regular mt-0.5 text-text-tertiary'>
|
||||||
|
{description}
|
||||||
|
{subItems && (
|
||||||
|
<div className='ml-2 border-l border-gray-200 pl-2'>
|
||||||
|
{subItems.map((item, index) => (
|
||||||
|
<VarItem
|
||||||
|
key={index}
|
||||||
|
name={item.name}
|
||||||
|
type={item.type}
|
||||||
|
description={item.description}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -15,6 +15,7 @@ type Props = {
|
|||||||
payload: FieldType,
|
payload: FieldType,
|
||||||
required: boolean,
|
required: boolean,
|
||||||
depth?: number,
|
depth?: number,
|
||||||
|
rootClassName?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const Field: FC<Props> = ({
|
const Field: FC<Props> = ({
|
||||||
@ -22,8 +23,10 @@ const Field: FC<Props> = ({
|
|||||||
payload,
|
payload,
|
||||||
depth = 1,
|
depth = 1,
|
||||||
required,
|
required,
|
||||||
|
rootClassName,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const isRoot = depth === 1
|
||||||
const hasChildren = payload.type === Type.object && payload.properties
|
const hasChildren = payload.type === Type.object && payload.properties
|
||||||
const [fold, {
|
const [fold, {
|
||||||
toggle: toggleFold,
|
toggle: toggleFold,
|
||||||
@ -40,7 +43,7 @@ const Field: FC<Props> = ({
|
|||||||
onClick={toggleFold}
|
onClick={toggleFold}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className='system-sm-medium h-6 truncate leading-6 text-text-secondary'>{name}</div>
|
<div className={cn('system-sm-medium h-6 truncate leading-6 text-text-secondary', isRoot && rootClassName)}>{name}</div>
|
||||||
<div className='system-xs-regular ml-3 shrink-0 leading-6 text-text-tertiary'>{getFieldType(payload)}</div>
|
<div className='system-xs-regular ml-3 shrink-0 leading-6 text-text-tertiary'>{getFieldType(payload)}</div>
|
||||||
{required && <div className='system-2xs-medium-uppercase ml-3 leading-6 text-text-warning'>{t('app.structOutput.required')}</div>}
|
{required && <div className='system-2xs-medium-uppercase ml-3 leading-6 text-text-warning'>{t('app.structOutput.required')}</div>}
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,10 +7,12 @@ import { useTranslation } from 'react-i18next'
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
payload: StructuredOutput
|
payload: StructuredOutput
|
||||||
|
rootClassName?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const ShowPanel: FC<Props> = ({
|
const ShowPanel: FC<Props> = ({
|
||||||
payload,
|
payload,
|
||||||
|
rootClassName,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const schema = {
|
const schema = {
|
||||||
@ -28,6 +30,7 @@ const ShowPanel: FC<Props> = ({
|
|||||||
name={name}
|
name={name}
|
||||||
payload={schema.schema.properties![name]}
|
payload={schema.schema.properties![name]}
|
||||||
required={!!schema.schema.required?.includes(name)}
|
required={!!schema.schema.required?.includes(name)}
|
||||||
|
rootClassName={rootClassName}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,15 +4,17 @@ import React from 'react'
|
|||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
depth?: number
|
depth?: number,
|
||||||
|
className?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const TreeIndentLine: FC<Props> = ({
|
const TreeIndentLine: FC<Props> = ({
|
||||||
depth = 1,
|
depth = 1,
|
||||||
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
const depthArray = Array.from({ length: depth }, (_, index) => index)
|
const depthArray = Array.from({ length: depth }, (_, index) => index)
|
||||||
return (
|
return (
|
||||||
<div className='ml-2.5 mr-2.5 flex space-x-[12px]'>
|
<div className={cn('ml-2.5 mr-2.5 flex space-x-[12px]', className)}>
|
||||||
{depthArray.map(d => (
|
{depthArray.map(d => (
|
||||||
<div key={d} className={cn('w-px bg-divider-regular')}></div>
|
<div key={d} className={cn('w-px bg-divider-regular')}></div>
|
||||||
))}
|
))}
|
||||||
|
@ -18,6 +18,7 @@ import { useToolIcon } from '@/app/components/workflow/hooks'
|
|||||||
import { useLogs } from '@/app/components/workflow/run/hooks'
|
import { useLogs } from '@/app/components/workflow/run/hooks'
|
||||||
import formatToTracingNodeList from '@/app/components/workflow/run/utils/format-log'
|
import formatToTracingNodeList from '@/app/components/workflow/run/utils/format-log'
|
||||||
import StructureOutputItem from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show'
|
import StructureOutputItem from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show'
|
||||||
|
import { Type } from '../llm/types'
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.tool'
|
const i18nPrefix = 'workflow.nodes.tool'
|
||||||
|
|
||||||
@ -52,6 +53,7 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
|||||||
handleStop,
|
handleStop,
|
||||||
runResult,
|
runResult,
|
||||||
outputSchema,
|
outputSchema,
|
||||||
|
hasObjectOutput,
|
||||||
} = useConfig(id, data)
|
} = useConfig(id, data)
|
||||||
const toolIcon = useToolIcon(data)
|
const toolIcon = useToolIcon(data)
|
||||||
const logsParams = useLogs()
|
const logsParams = useLogs()
|
||||||
@ -135,27 +137,45 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
|||||||
<>
|
<>
|
||||||
<VarItem
|
<VarItem
|
||||||
name='text'
|
name='text'
|
||||||
type='String'
|
type='string'
|
||||||
description={t(`${i18nPrefix}.outputVars.text`)}
|
description={t(`${i18nPrefix}.outputVars.text`)}
|
||||||
|
isIndent={hasObjectOutput}
|
||||||
/>
|
/>
|
||||||
<VarItem
|
<VarItem
|
||||||
name='files'
|
name='files'
|
||||||
type='Array[File]'
|
type='array[file]'
|
||||||
description={t(`${i18nPrefix}.outputVars.files.title`)}
|
description={t(`${i18nPrefix}.outputVars.files.title`)}
|
||||||
|
isIndent={hasObjectOutput}
|
||||||
/>
|
/>
|
||||||
<VarItem
|
<VarItem
|
||||||
name='json'
|
name='json'
|
||||||
type='Array[Object]'
|
type='array[object]'
|
||||||
description={t(`${i18nPrefix}.outputVars.json`)}
|
description={t(`${i18nPrefix}.outputVars.json`)}
|
||||||
|
isIndent={hasObjectOutput}
|
||||||
/>
|
/>
|
||||||
{outputSchema.map(outputItem => (
|
{outputSchema.map(outputItem => (
|
||||||
// <VarItem
|
<div key={outputItem.name}>
|
||||||
// key={outputItem.name}
|
{outputItem.value?.type === 'object' ? (
|
||||||
// name={outputItem.name}
|
<StructureOutputItem
|
||||||
// type={outputItem.type}
|
rootClassName='code-sm-semibold text-text-secondary'
|
||||||
// description={outputItem.description}
|
payload={{
|
||||||
// />
|
schema: {
|
||||||
<StructureOutputItem payload={outputItem} />
|
type: Type.object,
|
||||||
|
properties: {
|
||||||
|
[outputItem.name]: outputItem.value,
|
||||||
|
},
|
||||||
|
additionalProperties: false,
|
||||||
|
},
|
||||||
|
}} />
|
||||||
|
) : (
|
||||||
|
<VarItem
|
||||||
|
name={outputItem.name}
|
||||||
|
type={outputItem.type.toLocaleLowerCase()}
|
||||||
|
description={outputItem.description}
|
||||||
|
isIndent={hasObjectOutput}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
</OutputVars>
|
</OutputVars>
|
||||||
|
@ -262,17 +262,33 @@ const useConfig = (id: string, payload: ToolNodeType) => {
|
|||||||
return []
|
return []
|
||||||
Object.keys(output_schema.properties).forEach((outputKey) => {
|
Object.keys(output_schema.properties).forEach((outputKey) => {
|
||||||
const output = output_schema.properties[outputKey]
|
const output = output_schema.properties[outputKey]
|
||||||
res.push({
|
const type = output.type
|
||||||
name: outputKey,
|
if (type === 'object') {
|
||||||
type: output.type === 'array'
|
res.push({
|
||||||
? `Array[${output.items?.type.slice(0, 1).toLocaleUpperCase()}${output.items?.type.slice(1)}]`
|
name: outputKey,
|
||||||
: `${output.type.slice(0, 1).toLocaleUpperCase()}${output.type.slice(1)}`,
|
value: output,
|
||||||
description: output.description,
|
})
|
||||||
})
|
}
|
||||||
|
else {
|
||||||
|
res.push({
|
||||||
|
name: outputKey,
|
||||||
|
type: output.type === 'array'
|
||||||
|
? `Array[${output.items?.type.slice(0, 1).toLocaleUpperCase()}${output.items?.type.slice(1)}]`
|
||||||
|
: `${output.type.slice(0, 1).toLocaleUpperCase()}${output.type.slice(1)}`,
|
||||||
|
description: output.description,
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
return res
|
return res
|
||||||
}, [output_schema])
|
}, [output_schema])
|
||||||
|
|
||||||
|
const hasObjectOutput = useMemo(() => {
|
||||||
|
if (!output_schema)
|
||||||
|
return false
|
||||||
|
const properties = output_schema.properties
|
||||||
|
return Object.keys(properties).some(key => properties[key].type === 'object')
|
||||||
|
}, [output_schema])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
readOnly,
|
readOnly,
|
||||||
inputs,
|
inputs,
|
||||||
@ -302,6 +318,7 @@ const useConfig = (id: string, payload: ToolNodeType) => {
|
|||||||
handleStop,
|
handleStop,
|
||||||
runResult,
|
runResult,
|
||||||
outputSchema,
|
outputSchema,
|
||||||
|
hasObjectOutput,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user