mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 02:43:49 +08:00
607 lines
20 KiB
TypeScript
607 lines
20 KiB
TypeScript
'use client'
|
|
|
|
import type {
|
|
SkillDetailResponse,
|
|
SkillReferenceResponse,
|
|
} from '@dify/contracts/api/console/workspaces/types.gen'
|
|
import type { SkillFileMutationCoordinator } from './shared'
|
|
import type { TagComboboxItem } from '@/features/tag-management/components/tag-combobox-item'
|
|
import type { AppIconType } from '@/types/app'
|
|
import { Button } from '@langgenius/dify-ui/button'
|
|
import { cn } from '@langgenius/dify-ui/cn'
|
|
import {
|
|
Combobox,
|
|
ComboboxPopup,
|
|
ComboboxPortal,
|
|
ComboboxPositioner,
|
|
ComboboxTrigger,
|
|
createComboboxItems,
|
|
} from '@langgenius/dify-ui/combobox'
|
|
import { toast } from '@langgenius/dify-ui/toast'
|
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
|
import { useMemo, useState } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import AppIcon from '@/app/components/base/app-icon'
|
|
import { SkeletonRectangle } from '@/app/components/base/skeleton'
|
|
import { isCreateTagOption } from '@/features/tag-management/components/tag-combobox-item'
|
|
import { TagManagementModal } from '@/features/tag-management/components/tag-management-modal'
|
|
import { TagSearchContentView } from '@/features/tag-management/components/tag-search-content'
|
|
import Link from '@/next/link'
|
|
import { consoleQuery } from '@/service/client'
|
|
import { SkillPublishShortcut } from './publish-bar'
|
|
import {
|
|
invalidateSkillDetail,
|
|
runSkillFileMutation,
|
|
SKILL_TAG_CREATE_OPTION_PREFIX,
|
|
} from './shared'
|
|
|
|
const getSkillTagOptionId = (tag: string) => `skill-tag:${tag.trim().toLocaleLowerCase()}`
|
|
|
|
export function SkillTagsEditor({
|
|
detail,
|
|
fileMutationCoordinator,
|
|
readonly,
|
|
skillId,
|
|
}: {
|
|
detail: SkillDetailResponse | undefined
|
|
fileMutationCoordinator: SkillFileMutationCoordinator
|
|
readonly: boolean
|
|
skillId: string
|
|
}) {
|
|
const { t } = useTranslation('skill')
|
|
const { t: tCommon } = useTranslation('common')
|
|
const queryClient = useQueryClient()
|
|
const [addOpen, setAddOpen] = useState(false)
|
|
const [showTagManagement, setShowTagManagement] = useState(false)
|
|
const [tagSearch, setTagSearch] = useState('')
|
|
const [draftTags, setDraftTags] = useState<string[]>([])
|
|
const persistedTags = useMemo(() => detail?.tags ?? [], [detail?.tags])
|
|
const tags = persistedTags
|
|
const metadataMutation = useMutation(
|
|
consoleQuery.workspaces.current.skills.bySkillId.patch.mutationOptions(),
|
|
)
|
|
const tagsQuery = useQuery(consoleQuery.workspaces.current.skills.tags.get.queryOptions())
|
|
const normalizedTagSearch = tagSearch.trim()
|
|
const tagOptions = useMemo<TagComboboxItem[]>(() => {
|
|
const options: TagComboboxItem[] = []
|
|
const seenTags = new Set<string>()
|
|
const addOption = (tag: string) => {
|
|
const normalizedTag = tag.trim()
|
|
const tagKey = normalizedTag.toLocaleLowerCase()
|
|
if (!normalizedTag || seenTags.has(tagKey)) return
|
|
|
|
seenTags.add(tagKey)
|
|
options.push({
|
|
id: getSkillTagOptionId(normalizedTag),
|
|
name: normalizedTag,
|
|
type: 'skill',
|
|
binding_count: '0',
|
|
})
|
|
}
|
|
|
|
for (const tag of tags) addOption(tag)
|
|
for (const tag of tagsQuery.data?.data ?? []) addOption(tag.tag)
|
|
for (const tag of draftTags) addOption(tag)
|
|
const hasExactMatch = options.some(
|
|
(tag) => tag.name.toLocaleLowerCase() === normalizedTagSearch.toLocaleLowerCase(),
|
|
)
|
|
if (normalizedTagSearch && !hasExactMatch) {
|
|
options.push({
|
|
id: `${SKILL_TAG_CREATE_OPTION_PREFIX}${normalizedTagSearch}`,
|
|
name: normalizedTagSearch,
|
|
type: 'skill',
|
|
binding_count: '0',
|
|
isCreateOption: true,
|
|
})
|
|
}
|
|
|
|
return options
|
|
}, [draftTags, normalizedTagSearch, tags, tagsQuery.data?.data])
|
|
const tagOptionById = useMemo(() => new Map(tagOptions.map((tag) => [tag.id, tag])), [tagOptions])
|
|
const tagItems = useMemo(
|
|
() =>
|
|
createComboboxItems(tagOptions, {
|
|
getValue: (tag) => tag.id,
|
|
getLabel: (tag) => tag.name,
|
|
}),
|
|
[tagOptions],
|
|
)
|
|
const draftTagIds = useMemo(() => draftTags.map(getSkillTagOptionId), [draftTags])
|
|
|
|
const saveTags = (nextTags: string[]) => {
|
|
if (!detail || metadataMutation.isPending) return
|
|
|
|
void runSkillFileMutation(fileMutationCoordinator, (expectedUpdatedAt) =>
|
|
metadataMutation.mutateAsync({
|
|
params: {
|
|
skill_id: skillId,
|
|
},
|
|
body: {
|
|
expected_updated_at: expectedUpdatedAt,
|
|
tags: nextTags,
|
|
},
|
|
}),
|
|
)
|
|
.then(() => {
|
|
const addedTag = nextTags.some((tag) => !tags.includes(tag))
|
|
toast.success(
|
|
addedTag
|
|
? t(($) => $['skillManagement.detail.addTagSuccess'])
|
|
: t(($) => $['skillManagement.detail.removeTagSuccess']),
|
|
)
|
|
invalidateSkillDetail(queryClient, skillId)
|
|
void queryClient.invalidateQueries({
|
|
queryKey: consoleQuery.workspaces.current.skills.tags.get.key({ type: 'query' }),
|
|
})
|
|
void queryClient.invalidateQueries({
|
|
queryKey: consoleQuery.workspaces.current.skills.get.key({ type: 'query' }),
|
|
})
|
|
})
|
|
.catch(() => {
|
|
invalidateSkillDetail(queryClient, skillId)
|
|
toast.error(t(($) => $['skillManagement.detail.updateTagsFailed']))
|
|
})
|
|
}
|
|
|
|
const handleOpenChange = (open: boolean) => {
|
|
if (open) {
|
|
setDraftTags(tags)
|
|
setTagSearch('')
|
|
setAddOpen(true)
|
|
return
|
|
}
|
|
|
|
setAddOpen(false)
|
|
setTagSearch('')
|
|
const draftTagSet = new Set(draftTags)
|
|
const tagsChanged =
|
|
tags.length !== draftTags.length || tags.some((tag) => !draftTagSet.has(tag))
|
|
if (tagsChanged) saveTags(draftTags)
|
|
}
|
|
|
|
const renderTagBadge = (tag: string) => (
|
|
<span
|
|
key={tag}
|
|
className="group/tag relative flex max-w-full min-w-4.5 items-center justify-center rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-1.25 py-0.75 system-2xs-medium-uppercase text-text-tertiary"
|
|
>
|
|
<span className="min-w-0 truncate">{tag}</span>
|
|
{!readonly && (
|
|
<button
|
|
type="button"
|
|
aria-label={t(($) => $['skillManagement.detail.removeTag'], { tag })}
|
|
className="flex h-3 max-w-0 shrink-0 cursor-pointer items-center justify-center overflow-hidden text-text-quaternary opacity-0 outline-hidden transition-[max-width,margin,opacity] group-hover/tag:ml-0.5 group-hover/tag:max-w-3 group-hover/tag:opacity-100 hover:text-text-secondary focus-visible:ml-0.5 focus-visible:max-w-3 focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
|
onClick={() => saveTags(tags.filter((currentTag) => currentTag !== tag))}
|
|
>
|
|
<span aria-hidden className="i-ri-close-line size-3" />
|
|
</button>
|
|
)}
|
|
</span>
|
|
)
|
|
|
|
return (
|
|
<>
|
|
<div className="mt-3 flex flex-wrap items-center gap-1">
|
|
{tags.map(renderTagBadge)}
|
|
{!readonly && (
|
|
<Combobox<string, true, TagComboboxItem>
|
|
items={tagItems}
|
|
multiple
|
|
open={addOpen}
|
|
onOpenChange={handleOpenChange}
|
|
value={draftTagIds}
|
|
onValueChange={(nextTagIds) => {
|
|
const createOptionId = nextTagIds.find((tagId) => {
|
|
const tag = tagOptionById.get(tagId)
|
|
return tag ? isCreateTagOption(tag) : false
|
|
})
|
|
const createOption = createOptionId ? tagOptionById.get(createOptionId) : undefined
|
|
if (createOption && isCreateTagOption(createOption)) {
|
|
setDraftTags((currentTags) => [...currentTags, createOption.name])
|
|
setTagSearch('')
|
|
return
|
|
}
|
|
|
|
setDraftTags(
|
|
nextTagIds.flatMap((tagId) => {
|
|
const tag = tagOptionById.get(tagId)
|
|
return tag && !isCreateTagOption(tag) ? [tag.name] : []
|
|
}),
|
|
)
|
|
}}
|
|
inputValue={tagSearch}
|
|
onInputValueChange={setTagSearch}
|
|
filter={(tag, query) =>
|
|
tag.name.toLocaleLowerCase().includes(query.toLocaleLowerCase())
|
|
}
|
|
>
|
|
<ComboboxTrigger
|
|
icon={false}
|
|
disabled={!detail}
|
|
aria-label={t(($) => $['skillManagement.detail.addTag'])}
|
|
className={cn(
|
|
'h-4.5 w-auto min-w-4.5 rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm p-0 text-text-tertiary hover:bg-state-base-hover-alt focus-visible:bg-state-base-hover-alt data-popup-open:bg-state-base-hover',
|
|
tags.length === 0 && 'border-dashed px-1.25',
|
|
)}
|
|
>
|
|
<span className="flex items-center justify-center gap-0.5 system-2xs-medium-uppercase">
|
|
<span aria-hidden className="i-ri-add-line size-3 shrink-0" />
|
|
{tags.length === 0 && t(($) => $['skillManagement.detail.addTag'])}
|
|
</span>
|
|
</ComboboxTrigger>
|
|
<ComboboxPortal>
|
|
<ComboboxPositioner placement="bottom-start" sideOffset={4}>
|
|
<ComboboxPopup className="w-(--anchor-width) min-w-60 rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-0 shadow-lg backdrop-blur-[5px]">
|
|
<TagSearchContentView
|
|
type="skill"
|
|
inputValue={tagSearch}
|
|
onInputValueChange={setTagSearch}
|
|
canBindOrUnbindTags
|
|
canManageTags
|
|
onOpenTagManagement={() => setShowTagManagement(true)}
|
|
onClose={() => handleOpenChange(false)}
|
|
/>
|
|
</ComboboxPopup>
|
|
</ComboboxPositioner>
|
|
</ComboboxPortal>
|
|
</Combobox>
|
|
)}
|
|
{readonly && tags.length === 0 && (
|
|
<span className="system-2xs-medium-uppercase text-text-quaternary">
|
|
{tCommon(($) => $['tag.noTag'])}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<TagManagementModal
|
|
type="skill"
|
|
show={showTagManagement}
|
|
onClose={() => setShowTagManagement(false)}
|
|
onTagsChange={() => {
|
|
invalidateSkillDetail(queryClient, skillId)
|
|
void queryClient.invalidateQueries({
|
|
queryKey: consoleQuery.workspaces.current.skills.tags.get.key({ type: 'query' }),
|
|
})
|
|
void queryClient.invalidateQueries({
|
|
queryKey: consoleQuery.workspaces.current.skills.get.key({ type: 'query' }),
|
|
})
|
|
}}
|
|
/>
|
|
</>
|
|
)
|
|
}
|
|
|
|
export function SkillReferencesPanel({
|
|
compact = false,
|
|
embedded = false,
|
|
enabled = true,
|
|
maxHeight,
|
|
skillId,
|
|
testId,
|
|
visibleLimit,
|
|
}: {
|
|
compact?: boolean
|
|
embedded?: boolean
|
|
enabled?: boolean
|
|
maxHeight?: string
|
|
skillId: string
|
|
testId?: string
|
|
visibleLimit?: number
|
|
}) {
|
|
const { t } = useTranslation('skill')
|
|
const referencesQuery = useQuery({
|
|
...consoleQuery.workspaces.current.skills.bySkillId.references.get.queryOptions({
|
|
input: {
|
|
params: {
|
|
skill_id: skillId,
|
|
},
|
|
},
|
|
enabled,
|
|
}),
|
|
refetchOnMount: 'always',
|
|
})
|
|
const references = referencesQuery.data?.data ?? []
|
|
|
|
if (referencesQuery.isPending) {
|
|
return <SkillReferencesListSkeleton compact={compact} embedded={embedded} />
|
|
}
|
|
|
|
if (references.length === 0) {
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'py-2 system-xs-regular text-text-quaternary',
|
|
embedded ? 'w-full px-1' : 'w-max',
|
|
)}
|
|
>
|
|
{t(($) => $['skillManagement.detail.referencedBy_other'], { count: 0 })}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<SkillReferencesList
|
|
compact={compact}
|
|
embedded={embedded}
|
|
maxHeight={maxHeight}
|
|
references={references}
|
|
testId={testId}
|
|
visibleLimit={visibleLimit}
|
|
/>
|
|
)
|
|
}
|
|
|
|
export function SkillReferencesListSkeleton({
|
|
compact = false,
|
|
embedded = false,
|
|
}: {
|
|
compact?: boolean
|
|
embedded?: boolean
|
|
}) {
|
|
return (
|
|
<div
|
|
className={cn(
|
|
compact
|
|
? cn('space-y-px', !embedded && 'rounded-xl border border-divider-subtle p-1')
|
|
: 'w-52 space-y-1 py-1',
|
|
)}
|
|
>
|
|
<SkeletonRectangle className={cn(compact ? 'h-7 rounded-md' : 'h-8 rounded-lg')} />
|
|
<SkeletonRectangle className={cn(compact ? 'h-7 rounded-md' : 'h-8 rounded-lg')} />
|
|
{compact && <SkeletonRectangle className="h-7 rounded-md" />}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function SkillReferencesList({
|
|
compact = false,
|
|
embedded = false,
|
|
maxHeight,
|
|
references,
|
|
testId,
|
|
visibleLimit,
|
|
}: {
|
|
compact?: boolean
|
|
embedded?: boolean
|
|
maxHeight?: string
|
|
references: SkillReferenceResponse[]
|
|
testId?: string
|
|
visibleLimit?: number
|
|
}) {
|
|
const { t } = useTranslation('skill')
|
|
const [expanded, setExpanded] = useState(false)
|
|
const hasMoreReferences = visibleLimit != null && references.length > visibleLimit
|
|
const visibleReferences =
|
|
hasMoreReferences && !expanded ? references.slice(0, visibleLimit) : references
|
|
const isScrollable = Boolean(maxHeight && (expanded || visibleLimit == null))
|
|
|
|
return (
|
|
<div
|
|
data-testid={testId}
|
|
data-scrollable={isScrollable ? true : undefined}
|
|
className={cn(
|
|
compact
|
|
? cn(
|
|
'flex flex-col gap-px',
|
|
!embedded && 'rounded-xl border border-divider-subtle p-0.75',
|
|
)
|
|
: 'w-max max-w-120 space-y-0.5 py-1',
|
|
isScrollable && `${maxHeight} overflow-y-auto`,
|
|
)}
|
|
>
|
|
{visibleReferences.map((reference) => (
|
|
<SkillReferenceItem
|
|
key={`${reference.type}:${reference.agent_id}:${reference.workflow_id ?? ''}:${reference.node_id ?? ''}`}
|
|
compact={compact}
|
|
reference={reference}
|
|
/>
|
|
))}
|
|
{hasMoreReferences && !expanded && (
|
|
<button
|
|
type="button"
|
|
className={cn(
|
|
'flex h-7 cursor-pointer items-center justify-center rounded-md px-2 system-xs-medium text-text-accent outline-hidden hover:bg-state-accent-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid',
|
|
!compact && 'w-full',
|
|
)}
|
|
onClick={() => setExpanded(true)}
|
|
>
|
|
{t(($) => $['skillManagement.detail.showMoreReferences'], {
|
|
count: references.length - visibleReferences.length,
|
|
})}
|
|
</button>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function SkillPublishConfirmPanel({
|
|
loading,
|
|
onCancel,
|
|
onConfirm,
|
|
open,
|
|
referenceCount,
|
|
skillId,
|
|
}: {
|
|
loading: boolean
|
|
onCancel: () => void
|
|
onConfirm: () => void
|
|
open: boolean
|
|
referenceCount: number
|
|
skillId: string
|
|
}) {
|
|
const { t } = useTranslation('skill')
|
|
const { t: tCommon } = useTranslation('common')
|
|
if (!open) return null
|
|
|
|
return (
|
|
<div
|
|
role="dialog"
|
|
aria-labelledby="skill-publish-confirm-title"
|
|
data-open
|
|
className="pointer-events-auto relative flex w-96 max-w-[calc(100vw-32px)] flex-col overflow-hidden rounded-xl bg-components-panel-bg-blur shadow-lg inset-ring-[0.5px] shadow-shadow-shadow-5 inset-ring-components-panel-border backdrop-blur-[5px]"
|
|
>
|
|
<div className="px-3 pt-3.5 pb-1">
|
|
<h2
|
|
id="skill-publish-confirm-title"
|
|
className="px-1 pr-8 system-xl-semibold text-text-primary"
|
|
>
|
|
{t(($) => $['skillManagement.detail.publishReferencesTitle'])}
|
|
</h2>
|
|
<p className="mt-0.5 px-1 system-xs-regular text-util-colors-warning-warning-600">
|
|
{t(
|
|
($) =>
|
|
referenceCount === 1
|
|
? $['skillManagement.detail.publishReferencesDescription_one']
|
|
: $['skillManagement.detail.publishReferencesDescription_other'],
|
|
{ count: referenceCount },
|
|
)}
|
|
</p>
|
|
</div>
|
|
<div className="px-4 py-2">
|
|
<SkillReferencesPanel
|
|
compact
|
|
enabled={open}
|
|
maxHeight="max-h-[240px]"
|
|
skillId={skillId}
|
|
testId="skill-publish-reference-list"
|
|
visibleLimit={5}
|
|
/>
|
|
</div>
|
|
<div className="flex items-center justify-end gap-2 px-4 pt-2 pb-4">
|
|
<Button className="h-8 min-w-18 rounded-lg px-3" disabled={loading} onClick={onCancel}>
|
|
{tCommon(($) => $['operation.cancel'])}
|
|
</Button>
|
|
<Button
|
|
className="h-8 gap-1 rounded-lg px-3"
|
|
variant="primary"
|
|
loading={loading}
|
|
onClick={onConfirm}
|
|
>
|
|
<span>{t(($) => $['skillManagement.detail.publishUpdate'])}</span>
|
|
<SkillPublishShortcut />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function SkillReferenceItem({
|
|
compact = false,
|
|
reference,
|
|
}: {
|
|
compact?: boolean
|
|
reference: SkillReferenceResponse
|
|
}) {
|
|
const isWorkflowAgent = reference.type === 'workflow_agent_node'
|
|
const agentIconType = reference.agent_icon_type as AppIconType | null | undefined
|
|
const agentImageUrl =
|
|
reference.agent_icon_type === 'image' || reference.agent_icon_type === 'link'
|
|
? reference.agent_icon
|
|
: undefined
|
|
|
|
if (!isWorkflowAgent) {
|
|
const title = reference.display_name || reference.name
|
|
return (
|
|
<Link
|
|
href={`/agents/${reference.agent_id}/configure`}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
className={cn(
|
|
'group/reference flex min-w-0 items-center gap-2 outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid',
|
|
compact ? 'h-7 w-full rounded-md py-1 pr-2 pl-1' : 'h-8 w-fit max-w-120 rounded-lg px-2',
|
|
)}
|
|
>
|
|
<span aria-hidden className="shrink-0">
|
|
<AppIcon
|
|
size="tiny"
|
|
iconType={agentIconType}
|
|
icon={reference.agent_icon ?? undefined}
|
|
background={reference.agent_icon_background}
|
|
imageUrl={agentImageUrl}
|
|
innerIcon={
|
|
agentIconType ? undefined : (
|
|
<span className="i-ri-robot-2-line size-4 text-util-colors-blue-blue-600" />
|
|
)
|
|
}
|
|
/>
|
|
</span>
|
|
<span
|
|
className={cn(
|
|
'min-w-0 truncate system-sm-regular text-text-secondary',
|
|
compact ? 'flex-1' : 'max-w-63',
|
|
)}
|
|
>
|
|
{title}
|
|
</span>
|
|
<span
|
|
aria-hidden
|
|
className={cn(
|
|
'i-ri-external-link-line shrink-0 text-text-quaternary group-hover/reference:text-text-secondary group-focus-visible/reference:text-text-secondary',
|
|
compact ? 'size-3' : 'size-4',
|
|
)}
|
|
/>
|
|
</Link>
|
|
)
|
|
}
|
|
|
|
const workflowName = reference.workflow_name || reference.display_name || reference.name
|
|
const workflowIconType = reference.workflow_icon_type as AppIconType | null | undefined
|
|
const workflowImageUrl =
|
|
reference.workflow_icon_type === 'image' || reference.workflow_icon_type === 'link'
|
|
? reference.workflow_icon
|
|
: undefined
|
|
const workflowContent = (
|
|
<>
|
|
<span aria-hidden className="shrink-0">
|
|
<AppIcon
|
|
size="tiny"
|
|
iconType={workflowIconType}
|
|
icon={reference.workflow_icon ?? undefined}
|
|
background={reference.workflow_icon_background}
|
|
imageUrl={workflowImageUrl}
|
|
innerIcon={
|
|
workflowIconType ? undefined : (
|
|
<span className="i-ri-flow-chart size-4 text-util-colors-blue-blue-600" />
|
|
)
|
|
}
|
|
/>
|
|
</span>
|
|
<span className="max-w-63 min-w-0 flex-1 truncate system-sm-regular text-text-secondary">
|
|
{workflowName}
|
|
</span>
|
|
<span
|
|
aria-hidden
|
|
className={cn(
|
|
'i-ri-external-link-line shrink-0 text-text-quaternary group-hover/reference:text-text-secondary group-focus-visible/reference:text-text-secondary',
|
|
compact ? 'size-3' : 'size-4',
|
|
)}
|
|
/>
|
|
</>
|
|
)
|
|
|
|
if (reference.app_id) {
|
|
return (
|
|
<Link
|
|
href={`/app/${reference.app_id}/workflow`}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
className={cn(
|
|
'group/reference flex min-w-0 items-center gap-2 outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid',
|
|
compact ? 'h-7 w-full rounded-md py-1 pr-2 pl-1' : 'h-8 w-fit max-w-120 rounded-lg px-2',
|
|
)}
|
|
>
|
|
{workflowContent}
|
|
</Link>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'group/reference flex min-w-0 items-center gap-2 hover:bg-state-base-hover',
|
|
compact ? 'h-7 w-full rounded-md py-1 pr-2 pl-1' : 'h-8 w-fit max-w-120 rounded-lg px-2',
|
|
)}
|
|
>
|
|
{workflowContent}
|
|
</div>
|
|
)
|
|
}
|