open={open}
onOpenChange={setOpen}
- items={items}
+ items={tagItems}
multiple
- value={selectedTags}
- onValueChange={handleValueChange}
+ value={value}
+ onValueChange={(nextValue) => onChange(nextValue)}
inputValue={inputValue}
onInputValueChange={setInputValue}
filter={tagFilterComboboxFilter}
- itemToStringLabel={tagToString}
- isItemEqualToValue={isSameTag}
>
{!value.length && placeholderLabel}
- {!!value.length && currentTagName}
+ {!!value.length && (currentTagName ?? selectedCountLabel)}
- {value.length > 1 && (
+ {currentTagName && value.length > 1 && (
{`+${value.length - 1}`}
)}
{!value.length && (
diff --git a/web/features/tag-management/components/tag-search-content.tsx b/web/features/tag-management/components/tag-search-content.tsx
index 3f9c48b2c40..d2686af242b 100644
--- a/web/features/tag-management/components/tag-search-content.tsx
+++ b/web/features/tag-management/components/tag-search-content.tsx
@@ -89,7 +89,7 @@ export const TagSearchContentView = ({
if (isCreateTagOption(tag) && canManageTags) {
return (
-
+ value={tag.id}>
key={tag.id}
- value={tag}
+ value={tag.id}
disabled={!canBindOrUnbindTags && !canManageTags}
>
{tag.name}
diff --git a/web/features/tag-management/components/tag-selector.tsx b/web/features/tag-management/components/tag-selector.tsx
index 36b718a11cf..c800c05bb3a 100644
--- a/web/features/tag-management/components/tag-selector.tsx
+++ b/web/features/tag-management/components/tag-selector.tsx
@@ -8,6 +8,7 @@ import {
ComboboxPortal,
ComboboxPositioner,
ComboboxTrigger,
+ createComboboxItems,
} from '@langgenius/dify-ui/combobox'
import { toast } from '@langgenius/dify-ui/toast'
import { useMutation, useQuery } from '@tanstack/react-query'
@@ -24,15 +25,12 @@ import { TagSearchContent } from './tag-search-content'
import { TagTriggerContent } from './tag-trigger-content'
const normalizeTagName = (name: string) => name.trim().toLocaleLowerCase()
-const TAG_COMBOBOX_FILTER: NonNullable['filter']> = (
- tag,
- query,
-) => normalizeTagName(tag.name).includes(normalizeTagName(query))
-const tagToString = (tag: TagComboboxItem) => tag.name
-const isSameTag = (item: TagComboboxItem, value: TagComboboxItem) => item.id === value.id
+const TAG_COMBOBOX_FILTER: NonNullable<
+ ComboboxProps['filter']
+> = (tag, query) => normalizeTagName(tag.name).includes(normalizeTagName(query))
type TagSelectorRootProps = Omit<
- ComboboxProps,
+ ComboboxProps,
| 'items'
| 'multiple'
| 'value'
@@ -43,6 +41,7 @@ type TagSelectorRootProps = Omit<
| 'onInputValueChange'
| 'filter'
| 'itemToStringLabel'
+ | 'itemToStringValue'
| 'isItemEqualToValue'
| 'open'
| 'defaultOpen'
@@ -75,7 +74,7 @@ export const TagSelector = ({
}: TagSelectorProps) => {
const { t } = useTranslation()
const [open, setOpen] = useState(false)
- const [draftTags, setDraftTags] = useState(value)
+ const [draftTagIds, setDraftTagIds] = useState(() => value.map((tag) => tag.id))
const [inputValue, setInputValue] = useState('')
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
const canManageTags = hasPermission(workspacePermissionKeys, getTagManagePermissionKey(type))
@@ -123,7 +122,10 @@ export const TagSelector = ({
}
for (const tag of value) {
- if (tag.type === type && !tagIds.has(tag.id)) nextItems.push(tag)
+ if (tag.type === type && !tagIds.has(tag.id)) {
+ tagIds.add(tag.id)
+ nextItems.push(tag)
+ }
}
if (
@@ -143,9 +145,17 @@ export const TagSelector = ({
return nextItems
}, [canManageTags, inputValue, tagList, type, value])
+ const tagItemById = useMemo(() => new Map(items.map((tag) => [tag.id, tag])), [items])
+ const tagItems = useMemo(
+ () =>
+ createComboboxItems(items, {
+ getValue: (tag) => tag.id,
+ getLabel: (tag) => tag.name,
+ }),
+ [items],
+ )
const applyTagBindings = useCallback(() => {
- const draftTagIds = draftTags.map((tag) => tag.id)
const draftTagIdSet = new Set(draftTagIds)
const tagSelectionChanged =
selectedTagIds.length !== draftTagIds.length ||
@@ -184,19 +194,19 @@ export const TagSelector = ({
},
},
)
- }, [applyTagBindingsMutation, draftTags, onTagsChange, selectedTagIds, t, targetId, type])
+ }, [applyTagBindingsMutation, draftTagIds, onTagsChange, selectedTagIds, t, targetId, type])
const handleOpenChange = useCallback(
(nextOpen: boolean) => {
if (nextOpen) {
- setDraftTags(value)
+ setDraftTagIds(selectedTagIds)
} else {
applyTagBindings()
}
setOpen(nextOpen)
},
- [applyTagBindings, value],
+ [applyTagBindings, selectedTagIds],
)
const createNewTag = useCallback(
@@ -225,32 +235,34 @@ export const TagSelector = ({
)
const handleValueChange = useCallback(
- (nextTags: TagComboboxItem[]) => {
- const createOption = nextTags.find(isCreateTagOption)
- if (createOption) {
+ (nextTagIds: string[]) => {
+ const createOptionId = nextTagIds.find((tagId) => {
+ const tag = tagItemById.get(tagId)
+ return tag ? isCreateTagOption(tag) : false
+ })
+ const createOption = createOptionId ? tagItemById.get(createOptionId) : undefined
+ if (createOption && isCreateTagOption(createOption)) {
createNewTag(createOption.name)
return
}
- setDraftTags(nextTags.filter((tag) => !isCreateTagOption(tag)))
+ setDraftTagIds(nextTagIds)
},
- [createNewTag],
+ [createNewTag, tagItemById],
)
return (
-
{...rootProps}
open={open}
onOpenChange={handleOpenChange}
- items={items}
+ items={tagItems}
multiple
- value={draftTags}
+ value={draftTagIds}
onValueChange={handleValueChange}
inputValue={inputValue}
onInputValueChange={setInputValue}
filter={TAG_COMBOBOX_FILTER}
- itemToStringLabel={tagToString}
- isItemEqualToValue={isSameTag}
>