wwf
2025-05-20 938c3e5a587ce950a94964ea509b9e7f8834dfae
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import { useState } from 'react'
import type { ChangeEvent, FC, KeyboardEvent } from 'react'
import { } from 'use-context-selector'
import { useTranslation } from 'react-i18next'
import AutosizeInput from 'react-18-input-autosize'
import { RiAddLine, RiCloseLine } from '@remixicon/react'
import cn from '@/utils/classnames'
import { useToastContext } from '@/app/components/base/toast'
 
type TagInputProps = {
  items: string[]
  onChange: (items: string[]) => void
  disableRemove?: boolean
  disableAdd?: boolean
  customizedConfirmKey?: 'Enter' | 'Tab'
  isInWorkflow?: boolean
  placeholder?: string
}
 
const TagInput: FC<TagInputProps> = ({
  items,
  onChange,
  disableAdd,
  disableRemove,
  customizedConfirmKey = 'Enter',
  isInWorkflow,
  placeholder,
}) => {
  const { t } = useTranslation()
  const { notify } = useToastContext()
  const [value, setValue] = useState('')
  const [focused, setFocused] = useState(false)
 
  const isSpecialMode = customizedConfirmKey === 'Tab'
 
  const handleRemove = (index: number) => {
    const copyItems = [...items]
    copyItems.splice(index, 1)
 
    onChange(copyItems)
  }
 
  const handleKeyDown = (e: KeyboardEvent) => {
    if (isSpecialMode && e.key === 'Enter')
      setValue(`${value}↵`)
 
    if (e.key === customizedConfirmKey) {
      if (isSpecialMode)
        e.preventDefault()
 
      const valueTrimmed = value.trim()
      if (!valueTrimmed || (items.find(item => item === valueTrimmed)))
        return
 
      if (valueTrimmed.length > 20) {
        notify({ type: 'error', message: t('datasetDocuments.segment.keywordError') })
        return
      }
 
      onChange([...items, valueTrimmed])
      setTimeout(() => {
        setValue('')
      })
    }
  }
 
  const handleBlur = () => {
    setValue('')
    setFocused(false)
  }
 
  return (
    <div className={cn('flex flex-wrap', !isInWorkflow && 'min-w-[200px]', isSpecialMode ? 'rounded-lg bg-components-input-bg-normal pb-1 pl-1' : '')}>
      {
        (items || []).map((item, index) => (
          <div
            key={item}
            className={cn('system-xs-regular mr-1 mt-1 flex items-center rounded-md border border-divider-deep bg-components-badge-white-to-dark py-1 pl-1.5 pr-1 text-text-secondary')}
          >
            {item}
            {
              !disableRemove && (
                <div className='flex h-4 w-4 cursor-pointer items-center justify-center' onClick={() => handleRemove(index)}>
                  <RiCloseLine className='ml-0.5 h-3.5 w-3.5 text-text-tertiary' />
                </div>
              )
            }
          </div>
        ))
      }
      {
        !disableAdd && (
          <div className={cn('group/tag-add mt-1 flex items-center gap-x-0.5', !isSpecialMode ? 'rounded-md border border-dashed border-divider-deep px-1.5' : '')}>
            {!isSpecialMode && !focused && <RiAddLine className='h-3.5 w-3.5 text-text-placeholder group-hover/tag-add:text-text-secondary' />}
            <AutosizeInput
              inputClassName={cn('appearance-none caret-[#295EFF] outline-none placeholder:text-text-placeholder group-hover/tag-add:placeholder:text-text-secondary', isSpecialMode ? 'bg-transparent' : '')}
              className={cn(
                !isInWorkflow && 'max-w-[300px]',
                isInWorkflow && 'max-w-[146px]',
                `
                system-xs-regular overflow-hidden rounded-md py-1
                ${isSpecialMode && 'border border-transparent px-1.5'}
                ${focused && isSpecialMode && 'border-dashed border-divider-deep'}
              `)}
              onFocus={() => setFocused(true)}
              onBlur={handleBlur}
              value={value}
              onChange={(e: ChangeEvent<HTMLInputElement>) => {
                setValue(e.target.value)
              }}
              onKeyDown={handleKeyDown}
              placeholder={t(placeholder || (isSpecialMode ? 'common.model.params.stop_sequencesPlaceholder' : 'datasetDocuments.segment.addKeyWord'))}
            />
          </div>
        )
      }
    </div>
  )
}
 
export default TagInput