wwf
3 天以前 a430284aa21e3ae1f0d5654e55b2ad2852519cc2
app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx
@@ -1,6 +1,6 @@
'use client'
import type { FC } from 'react'
import React, { useEffect, useMemo, useRef, useState } from 'react'
import React, { useEffect, useRef, useState } from 'react'
import { useHover } from 'ahooks'
import { useTranslation } from 'react-i18next'
import cn from '@/utils/classnames'
@@ -15,14 +15,7 @@
import Input from '@/app/components/base/input'
import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
import { checkKeys } from '@/utils/var'
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 { varTypeToStructType } from './utils'
import type { Field } from '@/app/components/workflow/nodes/llm/types'
import { FILE_STRUCT } from '@/app/components/workflow/constants'
import { Loop } from '@/app/components/base/icons/src/vender/workflow'
import { noop } from 'lodash-es'
type ObjectChildrenProps = {
  nodeId: string
@@ -45,10 +38,7 @@
  itemWidth?: number
  isSupportFileVar?: boolean
  isException?: boolean
  isLoopVar?: boolean
}
const objVarTypes = [VarType.object, VarType.file]
const Item: FC<ItemProps> = ({
  nodeId,
@@ -57,50 +47,24 @@
  itemData,
  onChange,
  onHovering,
  itemWidth,
  isSupportFileVar,
  isException,
  isLoopVar,
}) => {
  const isStructureOutput = itemData.type === VarType.object && (itemData.children as StructuredOutput)?.schema?.properties
  const isFile = itemData.type === VarType.file && !isStructureOutput
  const isObj = ([VarType.object, VarType.file].includes(itemData.type) && itemData.children && (itemData.children as Var[]).length > 0)
  const isFile = itemData.type === VarType.file
  const isObj = ([VarType.object, VarType.file].includes(itemData.type) && itemData.children && itemData.children.length > 0)
  const isSys = itemData.variable.startsWith('sys.')
  const isEnv = itemData.variable.startsWith('env.')
  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<HTMLDivElement>(null)
  const itemRef = useRef(null)
  const [isItemHovering, setIsItemHovering] = useState(false)
  useHover(itemRef, {
  const _ = useHover(itemRef, {
    onChange: (hovering) => {
      if (hovering) {
        setIsItemHovering(true)
      }
      else {
        if (isObj || isStructureOutput) {
        if (isObj) {
          setTimeout(() => {
            setIsItemHovering(false)
          }, 100)
@@ -113,7 +77,7 @@
  })
  const [isChildrenHovering, setIsChildrenHovering] = useState(false)
  const isHovering = isItemHovering || isChildrenHovering
  const open = (isObj || isStructureOutput) && isHovering
  const open = isObj && isHovering
  useEffect(() => {
    onHovering && onHovering(isHovering)
    // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -133,58 +97,70 @@
  return (
    <PortalToFollowElem
      open={open}
      onOpenChange={noop}
      onOpenChange={() => { }}
      placement='left-start'
    >
      <PortalToFollowElemTrigger className='w-full'>
        <div
          ref={itemRef}
          className={cn(
            (isObj || isStructureOutput) ? ' pr-1' : 'pr-[18px]',
            isHovering && ((isObj || isStructureOutput) ? 'bg-primary-50' : 'bg-state-base-hover'),
            'relative flex h-6 w-full cursor-pointer items-center  rounded-md pl-3')
            isObj ? ' pr-1' : 'pr-[18px]',
            isHovering && (isObj ? 'bg-primary-50' : 'bg-state-base-hover'),
            'relative w-full flex items-center h-6 pl-3  rounded-md cursor-pointer')
          }
          onClick={handleChosen}
          onMouseDown={e => e.preventDefault()}
        >
          <div className='flex w-0 grow items-center'>
            {!isEnv && !isChatVar && !isLoopVar && <Variable02 className={cn('h-3.5 w-3.5 shrink-0 text-text-accent', isException && 'text-text-warning')} />}
            {isEnv && <Env className='h-3.5 w-3.5 shrink-0 text-util-colors-violet-violet-600' />}
            {isChatVar && <BubbleX className='h-3.5 w-3.5 shrink-0 text-util-colors-teal-teal-700' />}
            {isLoopVar && <Loop className='h-3.5 w-3.5 shrink-0 text-util-colors-cyan-cyan-500' />}
          <div className='flex items-center w-0 grow'>
            {!isEnv && !isChatVar && <Variable02 className={cn('shrink-0 w-3.5 h-3.5 text-text-accent', isException && 'text-text-warning')} />}
            {isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
            {isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
            {!isEnv && !isChatVar && (
              <div title={itemData.variable} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.variable}</div>
              <div title={itemData.variable} className='ml-1 w-0 grow truncate text-text-secondary system-sm-medium'>{itemData.variable}</div>
            )}
            {isEnv && (
              <div title={itemData.variable} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.variable.replace('env.', '')}</div>
              <div title={itemData.variable} className='ml-1 w-0 grow truncate text-text-secondary system-sm-medium'>{itemData.variable.replace('env.', '')}</div>
            )}
            {isChatVar && (
              <div title={itemData.des} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.variable.replace('conversation.', '')}</div>
              <div title={itemData.des} className='ml-1 w-0 grow truncate text-text-secondary system-sm-medium'>{itemData.variable.replace('conversation.', '')}</div>
            )}
          </div>
          <div className='ml-1 shrink-0 text-xs font-normal capitalize text-text-tertiary'>{itemData.type}</div>
          {
            (isObj || isStructureOutput) && (
              <ChevronRight className={cn('ml-0.5 h-3 w-3 text-text-quaternary', isHovering && 'text-text-tertiary')} />
            )
          }
        </div >
      </PortalToFollowElemTrigger >
          <div className='ml-1 shrink-0 text-xs font-normal text-text-tertiary capitalize'>{itemData.type}</div>
          {isObj && (
            <ChevronRight className={cn('ml-0.5 w-3 h-3 text-text-quaternary', isHovering && 'text-text-tertiary')} />
          )}
        </div>
      </PortalToFollowElemTrigger>
      <PortalToFollowElemContent style={{
        zIndex: 100,
      }}>
        {(isStructureOutput || isObj) && (
          <PickerStructurePanel
            root={{ nodeId, nodeName: title, attrName: itemData.variable }}
            payload={structuredOutput!}
        {(isObj && !isFile) && (
          // eslint-disable-next-line @typescript-eslint/no-use-before-define
          <ObjectChildren
            nodeId={nodeId}
            title={title}
            objPath={[...objPath, itemData.variable]}
            data={itemData.children as Var[]}
            onChange={onChange}
            onHovering={setIsChildrenHovering}
            onSelect={(valueSelector) => {
              onChange(valueSelector, itemData)
            }}
            itemWidth={itemWidth}
            isSupportFileVar={isSupportFileVar}
          />
        )}
        {isFile && (
          // eslint-disable-next-line @typescript-eslint/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>
    </PortalToFollowElem >
    </PortalToFollowElem>
  )
}
@@ -199,9 +175,9 @@
  isSupportFileVar,
}) => {
  const currObjPath = objPath
  const itemRef = useRef<HTMLDivElement>(null)
  const itemRef = useRef(null)
  const [isItemHovering, setIsItemHovering] = useState(false)
  useHover(itemRef, {
  const _ = useHover(itemRef, {
    onChange: (hovering) => {
      if (hovering) {
        setIsItemHovering(true)
@@ -225,11 +201,11 @@
  }, [isItemHovering])
  // absolute top-[-2px]
  return (
    <div ref={itemRef} className=' space-y-1 rounded-lg border border-gray-200 bg-white shadow-lg' style={{
    <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 h-[22px] items-center px-3 text-xs font-normal text-gray-700'><span className='text-gray-500'>{title}.</span>{currObjPath.join('.')}</div>
      <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) => (
@@ -258,8 +234,6 @@
  onChange: (value: ValueSelector, item: Var) => void
  itemWidth?: number
  maxHeightClass?: string
  onClose?: () => void
  onBlur?: () => void
}
const VarReferenceVars: FC<Props> = ({
  hideSearch,
@@ -269,18 +243,9 @@
  onChange,
  itemWidth,
  maxHeightClass,
  onClose,
  onBlur,
}) => {
  const { t } = useTranslation()
  const [searchText, setSearchText] = useState('')
  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === 'Escape') {
      e.preventDefault()
      onClose?.()
    }
  }
  const filteredVars = vars.filter((v) => {
    const children = v.vars.filter(v => checkKeys([v.variable], false).isValid || v.variable.startsWith('sys.') || v.variable.startsWith('env.') || v.variable.startsWith('conversation.'))
@@ -312,21 +277,18 @@
      {
        !hideSearch && (
          <>
            <div className={cn('var-search-input-wrapper mx-2 mb-1 mt-2', searchBoxClassName)} onClick={e => e.stopPropagation()}>
            <div className={cn('mb-1 mx-2 mt-2', searchBoxClassName)} onClick={e => e.stopPropagation()}>
              <Input
                className='var-search-input'
                showLeftIcon
                showClearIcon
                value={searchText}
                placeholder={t('workflow.common.searchVar') || ''}
                onChange={e => setSearchText(e.target.value)}
                onKeyDown={handleKeyDown}
                onClear={() => setSearchText('')}
                onBlur={onBlur}
                autoFocus
              />
            </div>
            <div className='relative left-[-4px] h-[0.5px] bg-black/5' style={{
            <div className='h-[0.5px] bg-black/5 relative left-[-4px]' style={{
              width: 'calc(100% + 8px)',
            }}></div>
          </>
@@ -340,7 +302,7 @@
            filteredVars.map((item, i) => (
              <div key={i}>
                <div
                  className='system-xs-medium-uppercase truncate px-3 leading-[22px] text-text-tertiary'
                  className='leading-[22px] px-3 text-text-tertiary system-xs-medium-uppercase truncate'
                  title={item.title}
                >{item.title}</div>
                {item.vars.map((v, j) => (
@@ -354,14 +316,13 @@
                    itemWidth={itemWidth}
                    isSupportFileVar={isSupportFileVar}
                    isException={v.isException}
                    isLoopVar={item.isLoop}
                  />
                ))}
              </div>))
          }
        </div>
        : <div className='pl-3 text-xs font-medium uppercase leading-[18px] text-gray-500'>{t('workflow.common.noVar')}</div>}
    </>
        : <div className='pl-3 leading-[18px] text-xs font-medium text-gray-500 uppercase'>{t('workflow.common.noVar')}</div>}
    </ >
  )
}
export default React.memo(VarReferenceVars)