mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-19 20:29:10 +08:00
Merge branch 'feat/llm-struct-output' of https://github.com/langgenius/dify into feat/llm-struct-output
This commit is contained in:
commit
a07831bc05
@ -82,7 +82,7 @@ const Panel: FC = () => {
|
|||||||
? LangfuseIcon
|
? LangfuseIcon
|
||||||
: inUseTracingProvider === TracingProvider.opik
|
: inUseTracingProvider === TracingProvider.opik
|
||||||
? OpikIcon
|
? OpikIcon
|
||||||
: null
|
: LangsmithIcon
|
||||||
|
|
||||||
const [langSmithConfig, setLangSmithConfig] = useState<LangSmithConfig | null>(null)
|
const [langSmithConfig, setLangSmithConfig] = useState<LangSmithConfig | null>(null)
|
||||||
const [langFuseConfig, setLangFuseConfig] = useState<LangFuseConfig | null>(null)
|
const [langFuseConfig, setLangFuseConfig] = useState<LangFuseConfig | null>(null)
|
||||||
|
@ -11,6 +11,7 @@ import { mergeRegister } from '@lexical/utils'
|
|||||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||||
import {
|
import {
|
||||||
RiErrorWarningFill,
|
RiErrorWarningFill,
|
||||||
|
RiMoreLine,
|
||||||
} from '@remixicon/react'
|
} from '@remixicon/react'
|
||||||
import { useSelectOrDelete } from '../../hooks'
|
import { useSelectOrDelete } from '../../hooks'
|
||||||
import type { WorkflowNodesMap } from './node'
|
import type { WorkflowNodesMap } from './node'
|
||||||
@ -27,6 +28,8 @@ import { Line3 } from '@/app/components/base/icons/src/public/common'
|
|||||||
import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import { isExceptionVariable } from '@/app/components/workflow/utils'
|
import { isExceptionVariable } from '@/app/components/workflow/utils'
|
||||||
|
import VarFullPathPanel from '@/app/components/workflow/nodes/_base/components/variable/var-full-path-panel'
|
||||||
|
import { Type } from '@/app/components/workflow/nodes/llm/types'
|
||||||
|
|
||||||
type WorkflowVariableBlockComponentProps = {
|
type WorkflowVariableBlockComponentProps = {
|
||||||
nodeKey: string
|
nodeKey: string
|
||||||
@ -43,10 +46,11 @@ const WorkflowVariableBlockComponent = ({
|
|||||||
const [editor] = useLexicalComposerContext()
|
const [editor] = useLexicalComposerContext()
|
||||||
const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND)
|
const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND)
|
||||||
const variablesLength = variables.length
|
const variablesLength = variables.length
|
||||||
|
const isShowAPart = variablesLength > 2
|
||||||
const varName = (
|
const varName = (
|
||||||
() => {
|
() => {
|
||||||
const isSystem = isSystemVar(variables)
|
const isSystem = isSystemVar(variables)
|
||||||
const varName = variablesLength >= 3 ? (variables).slice(-2).join('.') : variables[variablesLength - 1]
|
const varName = variables[variablesLength - 1]
|
||||||
return `${isSystem ? 'sys.' : ''}${varName}`
|
return `${isSystem ? 'sys.' : ''}${varName}`
|
||||||
}
|
}
|
||||||
)()
|
)()
|
||||||
@ -76,7 +80,7 @@ const WorkflowVariableBlockComponent = ({
|
|||||||
const Item = (
|
const Item = (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'mx-0.5 relative group/wrap flex items-center h-[18px] pl-0.5 pr-[3px] rounded-[5px] border select-none',
|
'mx-0.5 relative group/wrap flex items-center h-[18px] pl-0.5 pr-[3px] rounded-[5px] border select-none hover:border-state-accent-solid hover:bg-state-accent-hover',
|
||||||
isSelected ? ' border-state-accent-solid bg-state-accent-hover' : ' border-components-panel-border-subtle bg-components-badge-white-to-dark',
|
isSelected ? ' border-state-accent-solid bg-state-accent-hover' : ' border-components-panel-border-subtle bg-components-badge-white-to-dark',
|
||||||
!node && !isEnv && !isChatVar && '!border-state-destructive-solid !bg-state-destructive-hover',
|
!node && !isEnv && !isChatVar && '!border-state-destructive-solid !bg-state-destructive-hover',
|
||||||
)}
|
)}
|
||||||
@ -99,6 +103,13 @@ const WorkflowVariableBlockComponent = ({
|
|||||||
<Line3 className='mr-0.5 text-divider-deep'></Line3>
|
<Line3 className='mr-0.5 text-divider-deep'></Line3>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{isShowAPart && (
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<RiMoreLine className='w-3 h-3 text-text-secondary' />
|
||||||
|
<Line3 className='mr-0.5 text-divider-deep'></Line3>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className='flex items-center text-text-accent'>
|
<div className='flex items-center text-text-accent'>
|
||||||
{!isEnv && !isChatVar && <Variable02 className={cn('shrink-0 w-3.5 h-3.5', isException && 'text-text-warning')} />}
|
{!isEnv && !isChatVar && <Variable02 className={cn('shrink-0 w-3.5 h-3.5', isException && 'text-text-warning')} />}
|
||||||
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||||
@ -126,7 +137,21 @@ const WorkflowVariableBlockComponent = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Item
|
return (
|
||||||
|
<Tooltip
|
||||||
|
noDecoration
|
||||||
|
popupContent={
|
||||||
|
<VarFullPathPanel
|
||||||
|
nodeName={node.title}
|
||||||
|
path={variables.slice(1)}
|
||||||
|
varType={Type.string}
|
||||||
|
nodeType={node?.type}
|
||||||
|
/>}
|
||||||
|
disabled={!isShowAPart}
|
||||||
|
>
|
||||||
|
{Item}
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(WorkflowVariableBlockComponent)
|
export default memo(WorkflowVariableBlockComponent)
|
||||||
|
@ -58,11 +58,11 @@ const Field: FC<Props> = ({
|
|||||||
|
|
||||||
{depth <= MAX_DEPTH && payload.type === Type.object && payload.properties && (
|
{depth <= MAX_DEPTH && payload.type === Type.object && payload.properties && (
|
||||||
<div>
|
<div>
|
||||||
{Object.keys(payload.properties).map(name => (
|
{Object.keys(payload.properties).map(propName => (
|
||||||
<Field
|
<Field
|
||||||
key={name}
|
key={propName}
|
||||||
name={name}
|
name={propName}
|
||||||
payload={payload.properties?.[name] as FieldType}
|
payload={payload.properties?.[propName] as FieldType}
|
||||||
depth={depth + 1}
|
depth={depth + 1}
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
valueSelector={[...valueSelector, name]}
|
valueSelector={[...valueSelector, name]}
|
||||||
|
@ -67,6 +67,16 @@ const structTypeToVarType = (type: Type): VarType => {
|
|||||||
} as any)[type] || VarType.string
|
} as any)[type] || VarType.string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const varTypeToStructType = (type: VarType): Type => {
|
||||||
|
return ({
|
||||||
|
[VarType.string]: Type.string,
|
||||||
|
[VarType.number]: Type.number,
|
||||||
|
[VarType.boolean]: Type.boolean,
|
||||||
|
[VarType.object]: Type.object,
|
||||||
|
[VarType.array]: Type.array,
|
||||||
|
} as any)[type] || Type.string
|
||||||
|
}
|
||||||
|
|
||||||
const findExceptVarInStructuredProperties = (properties: Record<string, StructField>, filterVar: (payload: Var, selector: ValueSelector) => boolean): Record<string, StructField> => {
|
const findExceptVarInStructuredProperties = (properties: Record<string, StructField>, filterVar: (payload: Var, selector: ValueSelector) => boolean): Record<string, StructField> => {
|
||||||
const res = produce(properties, (draft) => {
|
const res = produce(properties, (draft) => {
|
||||||
Object.keys(properties).forEach((key) => {
|
Object.keys(properties).forEach((key) => {
|
||||||
@ -646,10 +656,30 @@ export const getVarType = ({
|
|||||||
|
|
||||||
let type: VarType = VarType.string
|
let type: VarType = VarType.string
|
||||||
let curr: any = targetVar.vars
|
let curr: any = targetVar.vars
|
||||||
|
|
||||||
if (isSystem || isEnv || isChatVar) {
|
if (isSystem || isEnv || isChatVar) {
|
||||||
return curr.find((v: any) => v.variable === (valueSelector as ValueSelector).join('.'))?.type
|
return curr.find((v: any) => v.variable === (valueSelector as ValueSelector).join('.'))?.type
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
const targetVar = curr.find((v: any) => v.variable === valueSelector[1])
|
||||||
|
if (!targetVar)
|
||||||
|
return VarType.string
|
||||||
|
|
||||||
|
const isStructuredOutputVar = !!targetVar.children.schema?.properties
|
||||||
|
if (isStructuredOutputVar) {
|
||||||
|
let currProperties = targetVar.children.schema;
|
||||||
|
(valueSelector as ValueSelector).slice(2).forEach((key, i) => {
|
||||||
|
const isLast = i === valueSelector.length - 3
|
||||||
|
if (!currProperties)
|
||||||
|
return
|
||||||
|
|
||||||
|
currProperties = currProperties.properties[key]
|
||||||
|
if (isLast)
|
||||||
|
type = structTypeToVarType(currProperties?.type)
|
||||||
|
})
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
|
||||||
(valueSelector as ValueSelector).slice(1).forEach((key, i) => {
|
(valueSelector as ValueSelector).slice(1).forEach((key, i) => {
|
||||||
const isLast = i === valueSelector.length - 2
|
const isLast = i === valueSelector.length - 2
|
||||||
if (Array.isArray(curr))
|
if (Array.isArray(curr))
|
||||||
|
@ -6,16 +6,19 @@ import { Type } from '../../../llm/types'
|
|||||||
import { PickerPanelMain as Panel } from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker'
|
import { PickerPanelMain as Panel } from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker'
|
||||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||||
import { BlockEnum } from '@/app/components/workflow/types'
|
import { BlockEnum } from '@/app/components/workflow/types'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
nodeName: string
|
nodeName: string
|
||||||
path: string[]
|
path: string[]
|
||||||
varType: TypeWithArray
|
varType: TypeWithArray
|
||||||
|
nodeType?: BlockEnum
|
||||||
}
|
}
|
||||||
|
|
||||||
const VarFullPathPanel: FC<Props> = ({
|
const VarFullPathPanel: FC<Props> = ({
|
||||||
nodeName,
|
nodeName,
|
||||||
path,
|
path,
|
||||||
varType,
|
varType,
|
||||||
|
nodeType = BlockEnum.LLM,
|
||||||
}) => {
|
}) => {
|
||||||
const schema: StructuredOutput = (() => {
|
const schema: StructuredOutput = (() => {
|
||||||
const schema: StructuredOutput['schema'] = {
|
const schema: StructuredOutput['schema'] = {
|
||||||
@ -25,7 +28,7 @@ const VarFullPathPanel: FC<Props> = ({
|
|||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
}
|
}
|
||||||
let current = schema
|
let current = schema
|
||||||
for (let i = 0; i < path.length; i++) {
|
for (let i = 1; i < path.length; i++) {
|
||||||
const isLast = i === path.length - 1
|
const isLast = i === path.length - 1
|
||||||
const name = path[i]
|
const name = path[i]
|
||||||
current.properties[name] = {
|
current.properties[name] = {
|
||||||
@ -41,12 +44,12 @@ const VarFullPathPanel: FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<div className='w-[280px] pb-0 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-[5px]'>
|
<div className='w-[280px] pb-0 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-[5px]'>
|
||||||
<div className='flex p-3 pb-2 border-b-[0.5px] border-divider-subtle space-x-1 '>
|
<div className='flex p-3 pb-2 border-b-[0.5px] border-divider-subtle space-x-1 '>
|
||||||
<BlockIcon size='xs' type={BlockEnum.LLM} />
|
<BlockIcon size='xs' type={nodeType} />
|
||||||
<div className='w-0 grow system-xs-medium text-text-secondary truncate'>{nodeName}</div>
|
<div className='w-0 grow system-xs-medium text-text-secondary truncate'>{nodeName}</div>
|
||||||
</div>
|
</div>
|
||||||
<Panel
|
<Panel
|
||||||
className='pt-2 pb-3 px-1'
|
className='pt-2 pb-3 px-1'
|
||||||
root={{ attrName: 'structured_output' }} // now only LLM support this, so hard code the root
|
root={{ attrName: path[0] }}
|
||||||
payload={schema}
|
payload={schema}
|
||||||
readonly
|
readonly
|
||||||
/>
|
/>
|
||||||
|
@ -6,13 +6,14 @@ import {
|
|||||||
RiArrowDownSLine,
|
RiArrowDownSLine,
|
||||||
RiCloseLine,
|
RiCloseLine,
|
||||||
RiErrorWarningFill,
|
RiErrorWarningFill,
|
||||||
|
RiMoreLine,
|
||||||
} from '@remixicon/react'
|
} from '@remixicon/react'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import { useStoreApi } from 'reactflow'
|
import { useStoreApi } from 'reactflow'
|
||||||
import RemoveButton from '../remove-button'
|
import RemoveButton from '../remove-button'
|
||||||
import useAvailableVarList from '../../hooks/use-available-var-list'
|
import useAvailableVarList from '../../hooks/use-available-var-list'
|
||||||
import VarReferencePopup from './var-reference-popup'
|
import VarReferencePopup from './var-reference-popup'
|
||||||
import { getNodeInfoById, isConversationVar, isENV, isSystemVar } from './utils'
|
import { getNodeInfoById, isConversationVar, isENV, isSystemVar, varTypeToStructType } from './utils'
|
||||||
import ConstantField from './constant-field'
|
import ConstantField from './constant-field'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import type { Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
|
import type { Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
|
||||||
@ -37,6 +38,7 @@ import AddButton from '@/app/components/base/button/add-button'
|
|||||||
import Badge from '@/app/components/base/badge'
|
import Badge from '@/app/components/base/badge'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import { isExceptionVariable } from '@/app/components/workflow/utils'
|
import { isExceptionVariable } from '@/app/components/workflow/utils'
|
||||||
|
import VarFullPathPanel from './var-full-path-panel'
|
||||||
|
|
||||||
const TRIGGER_DEFAULT_WIDTH = 227
|
const TRIGGER_DEFAULT_WIDTH = 227
|
||||||
|
|
||||||
@ -156,16 +158,15 @@ const VarReferencePicker: FC<Props> = ({
|
|||||||
return getNodeInfoById(availableNodes, outputVarNodeId)?.data
|
return getNodeInfoById(availableNodes, outputVarNodeId)?.data
|
||||||
}, [value, hasValue, isConstant, isIterationVar, iterationNode, availableNodes, outputVarNodeId, startNode])
|
}, [value, hasValue, isConstant, isIterationVar, iterationNode, availableNodes, outputVarNodeId, startNode])
|
||||||
|
|
||||||
const varName = useMemo(() => {
|
const isShowAPart = (value as ValueSelector).length > 2
|
||||||
if (hasValue) {
|
|
||||||
const isSystem = isSystemVar(value as ValueSelector)
|
|
||||||
let varName = ''
|
|
||||||
if (Array.isArray(value))
|
|
||||||
varName = value.length >= 3 ? (value as ValueSelector).slice(-2).join('.') : value[value.length - 1]
|
|
||||||
|
|
||||||
return `${isSystem ? 'sys.' : ''}${varName}`
|
const varName = useMemo(() => {
|
||||||
}
|
if (!hasValue)
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
const isSystem = isSystemVar(value as ValueSelector)
|
||||||
|
const varName = Array.isArray(value) ? value[(value as ValueSelector).length - 1] : ''
|
||||||
|
return `${isSystem ? 'sys.' : ''}${varName}`
|
||||||
}, [hasValue, value])
|
}, [hasValue, value])
|
||||||
|
|
||||||
const varKindTypes = [
|
const varKindTypes = [
|
||||||
@ -253,6 +254,22 @@ const VarReferencePicker: FC<Props> = ({
|
|||||||
|
|
||||||
const WrapElem = isSupportConstantValue ? 'div' : PortalToFollowElemTrigger
|
const WrapElem = isSupportConstantValue ? 'div' : PortalToFollowElemTrigger
|
||||||
const VarPickerWrap = !isSupportConstantValue ? 'div' : PortalToFollowElemTrigger
|
const VarPickerWrap = !isSupportConstantValue ? 'div' : PortalToFollowElemTrigger
|
||||||
|
|
||||||
|
const tooltipPopup = useMemo(() => {
|
||||||
|
if (isValidVar && isShowAPart) {
|
||||||
|
return (
|
||||||
|
<VarFullPathPanel
|
||||||
|
nodeName={outputVarNode?.title}
|
||||||
|
path={(value as ValueSelector).slice(1)}
|
||||||
|
varType={varTypeToStructType(type)}
|
||||||
|
nodeType={outputVarNode?.type}
|
||||||
|
/>)
|
||||||
|
}
|
||||||
|
if (!isValidVar && hasValue)
|
||||||
|
return t('workflow.errorMsg.invalidVariable')
|
||||||
|
|
||||||
|
return null
|
||||||
|
}, [isValidVar, isShowAPart, hasValue, t, outputVarNode?.title, outputVarNode?.type, value, type])
|
||||||
return (
|
return (
|
||||||
<div className={cn(className, !readonly && 'cursor-pointer')}>
|
<div className={cn(className, !readonly && 'cursor-pointer')}>
|
||||||
<PortalToFollowElem
|
<PortalToFollowElem
|
||||||
@ -317,7 +334,7 @@ const VarReferencePicker: FC<Props> = ({
|
|||||||
className='grow h-full'
|
className='grow h-full'
|
||||||
>
|
>
|
||||||
<div ref={isSupportConstantValue ? triggerRef : null} className={cn('h-full', isSupportConstantValue && 'flex items-center pl-1 py-1 rounded-lg bg-gray-100')}>
|
<div ref={isSupportConstantValue ? triggerRef : null} className={cn('h-full', isSupportConstantValue && 'flex items-center pl-1 py-1 rounded-lg bg-gray-100')}>
|
||||||
<Tooltip popupContent={!isValidVar && hasValue && t('workflow.errorMsg.invalidVariable')}>
|
<Tooltip popupContent={tooltipPopup} noDecoration={isShowAPart}>
|
||||||
<div className={cn('h-full items-center px-1.5 rounded-[5px]', hasValue ? 'bg-white inline-flex' : 'flex')}>
|
<div className={cn('h-full items-center px-1.5 rounded-[5px]', hasValue ? 'bg-white inline-flex' : 'flex')}>
|
||||||
{hasValue
|
{hasValue
|
||||||
? (
|
? (
|
||||||
@ -336,6 +353,12 @@ const VarReferencePicker: FC<Props> = ({
|
|||||||
<Line3 className='mr-0.5'></Line3>
|
<Line3 className='mr-0.5'></Line3>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{isShowAPart && (
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<RiMoreLine className='w-3 h-3 text-text-secondary' />
|
||||||
|
<Line3 className='mr-0.5 text-divider-deep'></Line3>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className='flex items-center text-primary-600'>
|
<div className='flex items-center text-primary-600'>
|
||||||
{!hasValue && <Variable02 className='w-3.5 h-3.5' />}
|
{!hasValue && <Variable02 className='w-3.5 h-3.5' />}
|
||||||
{isEnv && <Env className='w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
{isEnv && <Env className='w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useHover } from 'ahooks'
|
import { useHover } from 'ahooks'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
@ -15,20 +15,12 @@ import {
|
|||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
|
import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||||
import { checkKeys } from '@/utils/var'
|
import { checkKeys } from '@/utils/var'
|
||||||
import { FILE_STRUCT } from '@/app/components/workflow/constants'
|
|
||||||
import type { StructuredOutput } from '../../../llm/types'
|
import type { StructuredOutput } from '../../../llm/types'
|
||||||
|
import { Type } from '../../../llm/types'
|
||||||
import PickerStructurePanel from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker'
|
import PickerStructurePanel from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker'
|
||||||
|
import { varTypeToStructType } from './utils'
|
||||||
type ObjectChildrenProps = {
|
import type { Field } from '@/app/components/workflow/nodes/llm/types'
|
||||||
nodeId: string
|
import { FILE_STRUCT } from '@/app/components/workflow/constants'
|
||||||
title: string
|
|
||||||
data: Var[]
|
|
||||||
objPath: string[]
|
|
||||||
onChange: (value: ValueSelector, item: Var) => void
|
|
||||||
onHovering?: (value: boolean) => void
|
|
||||||
itemWidth?: number
|
|
||||||
isSupportFileVar?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
type ItemProps = {
|
type ItemProps = {
|
||||||
nodeId: string
|
nodeId: string
|
||||||
@ -49,7 +41,6 @@ const Item: FC<ItemProps> = ({
|
|||||||
itemData,
|
itemData,
|
||||||
onChange,
|
onChange,
|
||||||
onHovering,
|
onHovering,
|
||||||
itemWidth,
|
|
||||||
isSupportFileVar,
|
isSupportFileVar,
|
||||||
isException,
|
isException,
|
||||||
}) => {
|
}) => {
|
||||||
@ -59,6 +50,31 @@ const Item: FC<ItemProps> = ({
|
|||||||
const isSys = itemData.variable.startsWith('sys.')
|
const isSys = itemData.variable.startsWith('sys.')
|
||||||
const isEnv = itemData.variable.startsWith('env.')
|
const isEnv = itemData.variable.startsWith('env.')
|
||||||
const isChatVar = itemData.variable.startsWith('conversation.')
|
const isChatVar = itemData.variable.startsWith('conversation.')
|
||||||
|
|
||||||
|
const objStructuredOutput: StructuredOutput | null = useMemo(() => {
|
||||||
|
if (!isObj) return null
|
||||||
|
const properties: Record<string, Field> = {};
|
||||||
|
(isFile ? FILE_STRUCT : (itemData.children as Var[])).forEach((c) => {
|
||||||
|
properties[c.variable] = {
|
||||||
|
type: varTypeToStructType(c.type),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
schema: {
|
||||||
|
type: Type.object,
|
||||||
|
properties,
|
||||||
|
required: [],
|
||||||
|
additionalProperties: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}, [isFile, isObj, itemData.children])
|
||||||
|
|
||||||
|
const structuredOutput = (() => {
|
||||||
|
if (isStructureOutput)
|
||||||
|
return itemData.children as StructuredOutput
|
||||||
|
return objStructuredOutput
|
||||||
|
})()
|
||||||
|
|
||||||
const itemRef = useRef(null)
|
const itemRef = useRef(null)
|
||||||
const [isItemHovering, setIsItemHovering] = useState(false)
|
const [isItemHovering, setIsItemHovering] = useState(false)
|
||||||
const _ = useHover(itemRef, {
|
const _ = useHover(itemRef, {
|
||||||
@ -136,109 +152,21 @@ const Item: FC<ItemProps> = ({
|
|||||||
<PortalToFollowElemContent style={{
|
<PortalToFollowElemContent style={{
|
||||||
zIndex: 100,
|
zIndex: 100,
|
||||||
}}>
|
}}>
|
||||||
{isStructureOutput && (
|
{(isStructureOutput || isObj) && (
|
||||||
<PickerStructurePanel
|
<PickerStructurePanel
|
||||||
root={{ nodeId, nodeName: title, attrName: itemData.variable }}
|
root={{ nodeId, nodeName: title, attrName: itemData.variable }}
|
||||||
payload={itemData.children as StructuredOutput}
|
payload={structuredOutput!}
|
||||||
onHovering={setIsChildrenHovering}
|
onHovering={setIsChildrenHovering}
|
||||||
onSelect={(valueSelector) => {
|
onSelect={(valueSelector) => {
|
||||||
onChange(valueSelector, itemData)
|
onChange(valueSelector, itemData)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(isObj && !isFile) && (
|
|
||||||
// eslint-disable-next-line ts/no-use-before-define
|
|
||||||
<ObjectChildren
|
|
||||||
nodeId={nodeId}
|
|
||||||
title={title}
|
|
||||||
objPath={[...objPath, itemData.variable]}
|
|
||||||
data={itemData.children as Var[]}
|
|
||||||
onChange={onChange}
|
|
||||||
onHovering={setIsChildrenHovering}
|
|
||||||
itemWidth={itemWidth}
|
|
||||||
isSupportFileVar={isSupportFileVar}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isFile && (
|
|
||||||
// eslint-disable-next-line ts/no-use-before-define
|
|
||||||
<ObjectChildren
|
|
||||||
nodeId={nodeId}
|
|
||||||
title={title}
|
|
||||||
objPath={[...objPath, itemData.variable]}
|
|
||||||
data={FILE_STRUCT}
|
|
||||||
onChange={onChange}
|
|
||||||
onHovering={setIsChildrenHovering}
|
|
||||||
itemWidth={itemWidth}
|
|
||||||
isSupportFileVar={isSupportFileVar}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</PortalToFollowElemContent>
|
</PortalToFollowElemContent>
|
||||||
</PortalToFollowElem>
|
</PortalToFollowElem>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ObjectChildren: FC<ObjectChildrenProps> = ({
|
|
||||||
title,
|
|
||||||
nodeId,
|
|
||||||
objPath,
|
|
||||||
data,
|
|
||||||
onChange,
|
|
||||||
onHovering,
|
|
||||||
itemWidth,
|
|
||||||
isSupportFileVar,
|
|
||||||
}) => {
|
|
||||||
const currObjPath = objPath
|
|
||||||
const itemRef = useRef(null)
|
|
||||||
const [isItemHovering, setIsItemHovering] = useState(false)
|
|
||||||
const _ = useHover(itemRef, {
|
|
||||||
onChange: (hovering) => {
|
|
||||||
if (hovering) {
|
|
||||||
setIsItemHovering(true)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setTimeout(() => {
|
|
||||||
setIsItemHovering(false)
|
|
||||||
}, 100)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const [isChildrenHovering, setIsChildrenHovering] = useState(false)
|
|
||||||
const isHovering = isItemHovering || isChildrenHovering
|
|
||||||
useEffect(() => {
|
|
||||||
onHovering && onHovering(isHovering)
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [isHovering])
|
|
||||||
useEffect(() => {
|
|
||||||
onHovering && onHovering(isItemHovering)
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [isItemHovering])
|
|
||||||
// absolute top-[-2px]
|
|
||||||
return (
|
|
||||||
<div ref={itemRef} className=' bg-white rounded-lg border border-gray-200 shadow-lg space-y-1' style={{
|
|
||||||
right: itemWidth ? itemWidth - 10 : 215,
|
|
||||||
minWidth: 252,
|
|
||||||
}}>
|
|
||||||
<div className='flex items-center h-[22px] px-3 text-xs font-normal text-gray-700'><span className='text-gray-500'>{title}.</span>{currObjPath.join('.')}</div>
|
|
||||||
{
|
|
||||||
(data && data.length > 0)
|
|
||||||
&& data.map((v, i) => (
|
|
||||||
<Item
|
|
||||||
key={i}
|
|
||||||
nodeId={nodeId}
|
|
||||||
title={title}
|
|
||||||
objPath={objPath}
|
|
||||||
itemData={v}
|
|
||||||
onChange={onChange}
|
|
||||||
onHovering={setIsChildrenHovering}
|
|
||||||
isSupportFileVar={isSupportFileVar}
|
|
||||||
isException={v.isException}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
hideSearch?: boolean
|
hideSearch?: boolean
|
||||||
searchBoxClassName?: string
|
searchBoxClassName?: string
|
||||||
|
Loading…
x
Reference in New Issue
Block a user