mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 11:04:27 +08:00
fix(web): align agent skills interactions with design
This commit is contained in:
parent
8b72e93067
commit
e61d4ff85d
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.30314 1.54615C8.48087 1.50085 8.66708 1.49473 8.84742 1.52858C9.07685 1.57176 9.27914 1.6956 9.41968 1.77467L13.1768 3.88795C13.2961 3.955 13.4666 4.04398 13.6098 4.17701L13.6697 4.23691C13.7899 4.36839 13.8809 4.52463 13.9366 4.69394C14.0074 4.90941 13.9998 5.13856 13.9998 5.29485V9.55917C13.9998 9.70492 14.0069 9.91888 13.9444 10.1223C13.9076 10.2422 13.8527 10.3559 13.7823 10.4596L13.7068 10.5598C13.5702 10.7233 13.3869 10.8339 13.2647 10.9133L8.25171 14.1718C8.11671 14.2596 7.92272 14.396 7.69637 14.4537C7.51868 14.499 7.33292 14.5051 7.15275 14.4713C6.92315 14.4281 6.72034 14.3049 6.57984 14.2258L2.82268 12.1119C2.68658 12.0353 2.48283 11.9301 2.32984 11.7629C2.20953 11.6314 2.11852 11.475 2.06291 11.3059C1.99209 11.0903 1.99976 10.8606 1.99976 10.7044V6.44069C1.99976 6.29486 1.99262 6.08092 2.0551 5.87753L2.09807 5.7597C2.14656 5.64426 2.21216 5.53647 2.29273 5.44003L2.34611 5.38144C2.47424 5.24974 2.62775 5.15609 2.73479 5.08652L7.85979 1.75514C7.98194 1.67712 8.13342 1.58951 8.30314 1.54615ZM3.33309 10.7044C3.33309 10.7559 3.33334 10.7962 3.33374 10.8307C3.33392 10.8452 3.33411 10.8578 3.33439 10.8684C3.34356 10.8739 3.3543 10.8806 3.36695 10.8879C3.39682 10.9052 3.43208 10.9245 3.47697 10.9498L6.74064 12.7857V11.649L3.33309 9.73235V10.7044ZM8.07398 11.621V12.6972L12.5382 9.7955C12.5784 9.76933 12.6098 9.74883 12.6365 9.73105C12.6475 9.72368 12.6564 9.71639 12.6645 9.71087C12.6647 9.70125 12.6656 9.69004 12.6658 9.67701C12.6661 9.64494 12.6664 9.60721 12.6664 9.55917V8.636L8.07398 11.621ZM3.33309 8.2024L6.74064 10.1191V8.98235L3.33309 7.06568V8.2024ZM8.07398 8.95436V10.0299L12.6664 7.04485V5.96933L8.07398 8.95436ZM8.58374 2.87493L3.95288 5.8847L7.38192 7.81373L12.046 4.78183L8.76604 2.93613C8.71971 2.91007 8.68338 2.89008 8.6521 2.87298C8.63863 2.86561 8.62684 2.85931 8.61695 2.8541C8.60751 2.85987 8.59651 2.86685 8.58374 2.87493Z" fill="currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@ -7,6 +7,9 @@
|
||||
"width": 15,
|
||||
"height": 15
|
||||
},
|
||||
"agent-v2-building-blocks": {
|
||||
"body": "<g fill=\"none\"><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M8.30314 1.54615C8.48087 1.50085 8.66708 1.49473 8.84742 1.52858C9.07685 1.57176 9.27914 1.6956 9.41968 1.77467L13.1768 3.88795C13.2961 3.955 13.4666 4.04398 13.6098 4.17701L13.6697 4.23691C13.7899 4.36839 13.8809 4.52463 13.9366 4.69394C14.0074 4.90941 13.9998 5.13856 13.9998 5.29485V9.55917C13.9998 9.70492 14.0069 9.91888 13.9444 10.1223C13.9076 10.2422 13.8527 10.3559 13.7823 10.4596L13.7068 10.5598C13.5702 10.7233 13.3869 10.8339 13.2647 10.9133L8.25171 14.1718C8.11671 14.2596 7.92272 14.396 7.69637 14.4537C7.51868 14.499 7.33292 14.5051 7.15275 14.4713C6.92315 14.4281 6.72034 14.3049 6.57984 14.2258L2.82268 12.1119C2.68658 12.0353 2.48283 11.9301 2.32984 11.7629C2.20953 11.6314 2.11852 11.475 2.06291 11.3059C1.99209 11.0903 1.99976 10.8606 1.99976 10.7044V6.44069C1.99976 6.29486 1.99262 6.08092 2.0551 5.87753L2.09807 5.7597C2.14656 5.64426 2.21216 5.53647 2.29273 5.44003L2.34611 5.38144C2.47424 5.24974 2.62775 5.15609 2.73479 5.08652L7.85979 1.75514C7.98194 1.67712 8.13342 1.58951 8.30314 1.54615ZM3.33309 10.7044C3.33309 10.7559 3.33334 10.7962 3.33374 10.8307C3.33392 10.8452 3.33411 10.8578 3.33439 10.8684C3.34356 10.8739 3.3543 10.8806 3.36695 10.8879C3.39682 10.9052 3.43208 10.9245 3.47697 10.9498L6.74064 12.7857V11.649L3.33309 9.73235V10.7044ZM8.07398 11.621V12.6972L12.5382 9.7955C12.5784 9.76933 12.6098 9.74883 12.6365 9.73105C12.6475 9.72368 12.6564 9.71639 12.6645 9.71087C12.6647 9.70125 12.6656 9.69004 12.6658 9.67701C12.6661 9.64494 12.6664 9.60721 12.6664 9.55917V8.636L8.07398 11.621ZM3.33309 8.2024L6.74064 10.1191V8.98235L3.33309 7.06568V8.2024ZM8.07398 8.95436V10.0299L12.6664 7.04485V5.96933L8.07398 8.95436ZM8.58374 2.87493L3.95288 5.8847L7.38192 7.81373L12.046 4.78183L8.76604 2.93613C8.71971 2.91007 8.68338 2.89008 8.6521 2.87298C8.63863 2.86561 8.62684 2.85931 8.61695 2.8541C8.60751 2.85987 8.59651 2.86685 8.58374 2.87493Z\" fill=\"currentColor\"/></g>"
|
||||
},
|
||||
"agent-v2-configure": {
|
||||
"body": "<g fill=\"none\"><path d=\"M7.5 2.2912C10.875 2.66428 13.5 5.52559 13.5 9V15.75H0V9C0 5.52559 2.62504 2.66428 6 2.2912V0H7.5V2.2912ZM12 14.25V9C12 6.10051 9.6495 3.75 6.75 3.75C3.85051 3.75 1.5 6.10051 1.5 9V14.25H12ZM6.75 12.75C4.67893 12.75 3 11.071 3 9C3 6.92895 4.67893 5.25 6.75 5.25C8.82105 5.25 10.5 6.92895 10.5 9C10.5 11.071 8.82105 12.75 6.75 12.75ZM6.75 11.25C7.99268 11.25 9 10.2427 9 9C9 7.75732 7.99268 6.75 6.75 6.75C5.50732 6.75 4.5 7.75732 4.5 9C4.5 10.2427 5.50732 11.25 6.75 11.25ZM6.75 9.75C6.33578 9.75 6 9.41422 6 9C6 8.58578 6.33578 8.25 6.75 8.25C7.16422 8.25 7.5 8.58578 7.5 9C7.5 9.41422 7.16422 9.75 6.75 9.75Z\" fill=\"currentColor\"/></g>",
|
||||
"width": 14
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"prefix": "custom-vender",
|
||||
"name": "Dify Custom Vender",
|
||||
"total": 331,
|
||||
"total": 332,
|
||||
"version": "0.0.0-private",
|
||||
"author": {
|
||||
"name": "LangGenius, Inc.",
|
||||
@ -14,11 +14,11 @@
|
||||
},
|
||||
"samples": [
|
||||
"agent-v2-access-point",
|
||||
"agent-v2-building-blocks",
|
||||
"agent-v2-configure",
|
||||
"agent-v2-configure-active",
|
||||
"agent-v2-configure-build",
|
||||
"agent-v2-configure-preview",
|
||||
"agent-v2-end-user-auth"
|
||||
"agent-v2-configure-preview"
|
||||
],
|
||||
"palette": false
|
||||
}
|
||||
|
||||
@ -456,15 +456,49 @@ describe('AgentSkills', () => {
|
||||
|
||||
await user.click(missingSkill)
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
|
||||
|
||||
await user.click(
|
||||
screen.getByRole('button', {
|
||||
name: 'agentV2.agentDetail.configure.skills.moreActions:{"name":"Missing Skill"}',
|
||||
}),
|
||||
)
|
||||
expect(screen.queryByText('common.operation.download')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('common.operation.delete')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should delete a configured skill by config name', async () => {
|
||||
const user = userEvent.setup()
|
||||
const { container } = renderAgentSkills()
|
||||
|
||||
const removeButton = container.querySelector('[data-agent-skill-remove-button]')
|
||||
expect(removeButton).not.toBeNull()
|
||||
const embeddedBadge = screen.getByText(
|
||||
'agentV2.agentDetail.configure.skills.addMenu.upload.badge',
|
||||
)
|
||||
expect(embeddedBadge).toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByText('agentV2.agentDetail.configure.skills.itemType'),
|
||||
).not.toBeInTheDocument()
|
||||
expect(
|
||||
screen
|
||||
.getByRole('button', { name: 'Tender Analyzer' })
|
||||
.querySelector('.i-custom-vender-agent-v2-building-blocks'),
|
||||
).toHaveClass('text-text-secondary')
|
||||
|
||||
fireEvent.click(removeButton!)
|
||||
await user.click(
|
||||
screen.getByRole('button', {
|
||||
name: 'agentV2.agentDetail.configure.skills.moreActions:{"name":"Tender Analyzer"}',
|
||||
}),
|
||||
)
|
||||
expect(embeddedBadge).toHaveClass('opacity-0')
|
||||
|
||||
const deleteAction = screen.getByText('common.operation.delete')
|
||||
fireEvent.mouseEnter(deleteAction.closest('[data-agent-skill-remove-button]')!)
|
||||
|
||||
expect(container.querySelector('[data-agent-skill-row]')).toHaveClass(
|
||||
'border-state-destructive-border!',
|
||||
'bg-state-destructive-hover!',
|
||||
)
|
||||
|
||||
await user.click(deleteAction)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mocks.deleteSkillMutationFn).toHaveBeenCalled()
|
||||
@ -574,12 +608,20 @@ describe('AgentSkills', () => {
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
|
||||
)
|
||||
await user.click(
|
||||
screen.getByRole('button', {
|
||||
name: /agentV2\.agentDetail\.configure\.skills\.addMenu\.workspace\.label/i,
|
||||
}),
|
||||
)
|
||||
await user.click(await screen.findByRole('button', { name: /Refund approval/ }))
|
||||
const workspaceMenuItem = screen.getByRole('button', {
|
||||
name: /agentV2\.agentDetail\.configure\.skills\.addMenu\.workspace\.label/i,
|
||||
})
|
||||
expect(
|
||||
workspaceMenuItem.querySelector('.i-custom-vender-agent-v2-building-blocks'),
|
||||
).toHaveClass('text-text-secondary')
|
||||
await user.click(workspaceMenuItem)
|
||||
|
||||
const workspaceSkillButton = await screen.findByRole('button', { name: /Refund approval/ })
|
||||
expect(
|
||||
workspaceSkillButton.querySelector('.i-custom-vender-agent-v2-building-blocks'),
|
||||
).toHaveClass('text-text-secondary')
|
||||
expect(screen.queryByText('💳')).not.toBeInTheDocument()
|
||||
await user.click(workspaceSkillButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mocks.replaceAgentSkillBindingsMutationFn.mock.calls[0]?.[0]).toEqual({
|
||||
@ -706,6 +748,7 @@ describe('AgentSkills', () => {
|
||||
data: [
|
||||
createWorkspaceSkill(),
|
||||
createWorkspaceSkill({
|
||||
description: 'Draft skill description.',
|
||||
id: 'draft-skill',
|
||||
name: 'draft-skill',
|
||||
display_name: 'Draft skill',
|
||||
@ -745,8 +788,16 @@ describe('AgentSkills', () => {
|
||||
const draftSkillButton = screen
|
||||
.getByText('agentV2.agentDetail.configure.skills.workspaceSelector.draft')
|
||||
.closest('button')
|
||||
expect(addedSkillButton).toBeDisabled()
|
||||
expect(draftSkillButton).toBeDisabled()
|
||||
expect(addedSkillButton).not.toBeDisabled()
|
||||
expect(addedSkillButton).toHaveAttribute('aria-disabled', 'true')
|
||||
expect(draftSkillButton).not.toBeDisabled()
|
||||
expect(draftSkillButton).toHaveAttribute('aria-disabled', 'true')
|
||||
|
||||
await user.hover(draftSkillButton!)
|
||||
expect(await screen.findByText('Draft skill description.')).toBeInTheDocument()
|
||||
|
||||
await user.click(draftSkillButton!)
|
||||
await user.click(addedSkillButton!)
|
||||
|
||||
expect(mocks.replaceAgentSkillBindingsMutationFn).not.toHaveBeenCalled()
|
||||
})
|
||||
@ -832,14 +883,26 @@ describe('AgentSkills', () => {
|
||||
}),
|
||||
}
|
||||
})
|
||||
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
|
||||
const { container } = renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
|
||||
|
||||
await user.click(
|
||||
await screen.findByRole('button', {
|
||||
name: 'agentV2.agentDetail.configure.skills.moreActions:{"name":"Refund approval"}',
|
||||
}),
|
||||
)
|
||||
await user.click(await screen.findByText('agentV2.agentDetail.configure.skills.removeAction'))
|
||||
expect(screen.getByText('refund-approval')).toHaveClass('opacity-0')
|
||||
|
||||
const removeAction = await screen.findByText(
|
||||
'agentV2.agentDetail.configure.skills.removeAction',
|
||||
)
|
||||
fireEvent.mouseEnter(removeAction.closest('[data-workspace-skill-remove-action]')!)
|
||||
|
||||
expect(container.querySelector('[data-workspace-skill-row]')).toHaveClass(
|
||||
'border-state-destructive-border!',
|
||||
'bg-state-destructive-hover!',
|
||||
)
|
||||
|
||||
await user.click(removeAction)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mocks.replaceAgentSkillBindingsMutationFn.mock.calls[0]?.[0]).toEqual({
|
||||
@ -1072,9 +1135,10 @@ describe('AgentSkills', () => {
|
||||
|
||||
await user.click(
|
||||
screen.getByRole('button', {
|
||||
name: /common\.operation\.download.*Tender Analyzer/,
|
||||
name: 'agentV2.agentDetail.configure.skills.moreActions:{"name":"Tender Analyzer"}',
|
||||
}),
|
||||
)
|
||||
await user.click(screen.getByText('common.operation.download'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mocks.skillDownloadQueryOptions).toHaveBeenCalledWith(
|
||||
@ -1098,6 +1162,20 @@ describe('AgentSkills', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should expose only download from an embedded skill row in read-only mode', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderAgentSkills({ readOnly: true })
|
||||
|
||||
await user.click(
|
||||
screen.getByRole('button', {
|
||||
name: 'agentV2.agentDetail.configure.skills.moreActions:{"name":"Tender Analyzer"}',
|
||||
}),
|
||||
)
|
||||
|
||||
expect(screen.getByText('common.operation.download')).toBeInTheDocument()
|
||||
expect(screen.queryByText('common.operation.delete')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should download a whole workflow skill package with node_id', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderAgentSkills({
|
||||
@ -1114,9 +1192,10 @@ describe('AgentSkills', () => {
|
||||
|
||||
await user.click(
|
||||
screen.getByRole('button', {
|
||||
name: /common\.operation\.download.*Tender Analyzer/,
|
||||
name: 'agentV2.agentDetail.configure.skills.moreActions:{"name":"Tender Analyzer"}',
|
||||
}),
|
||||
)
|
||||
await user.click(screen.getByText('common.operation.download'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mocks.skillDownloadQueryOptions).toHaveBeenCalledWith(
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||
@ -75,7 +76,7 @@ function AgentSkillAddMenuItem({
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn('mt-0.5 size-4 shrink-0 text-text-tertiary', iconClassName)}
|
||||
className={cn('mt-0.5 size-4 shrink-0 text-text-secondary', iconClassName)}
|
||||
/>
|
||||
<span className="flex min-w-0 flex-1 flex-col gap-0.5">
|
||||
<span className="flex min-w-0 items-center gap-2">
|
||||
@ -92,20 +93,19 @@ function AgentSkillAddMenuItem({
|
||||
)
|
||||
}
|
||||
|
||||
function WorkspaceSkillIcon({ icon }: { icon?: string }) {
|
||||
function WorkspaceSkillIcon() {
|
||||
return (
|
||||
<span className="flex size-5 shrink-0 items-center justify-center rounded-md border-[0.5px] border-divider-subtle bg-background-default-dodge">
|
||||
{icon ? (
|
||||
<span className="text-[12px] leading-none">{icon}</span>
|
||||
) : (
|
||||
<span aria-hidden className="i-ri-box-3-line size-3.5 text-text-tertiary" />
|
||||
)}
|
||||
<span className="flex size-6 shrink-0 items-center justify-center rounded-md border-[0.5px] border-divider-regular bg-text-primary-on-surface p-1 backdrop-blur-xs">
|
||||
<span
|
||||
aria-hidden
|
||||
className="i-custom-vender-agent-v2-building-blocks size-4 text-text-secondary"
|
||||
/>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function WorkspaceSkillRow({
|
||||
disabled,
|
||||
unavailable,
|
||||
isAdded,
|
||||
isPending,
|
||||
onSelect,
|
||||
@ -113,7 +113,7 @@ function WorkspaceSkillRow({
|
||||
selected,
|
||||
skill,
|
||||
}: {
|
||||
disabled: boolean
|
||||
unavailable: boolean
|
||||
isAdded: boolean
|
||||
isPending: boolean
|
||||
onSelect: (skill: SkillResponse) => void
|
||||
@ -122,30 +122,34 @@ function WorkspaceSkillRow({
|
||||
skill: SkillResponse
|
||||
}) {
|
||||
const { t } = useTranslation('agentV2')
|
||||
const cannotAdd = unavailable || isAdded || isPending
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled || isAdded || isPending}
|
||||
onClick={() => onSelect(skill)}
|
||||
aria-disabled={cannotAdd}
|
||||
onClick={() => {
|
||||
onPreview(skill)
|
||||
if (!cannotAdd) onSelect(skill)
|
||||
}}
|
||||
onFocus={() => onPreview(skill)}
|
||||
onMouseEnter={() => onPreview(skill)}
|
||||
className={cn(
|
||||
'flex h-12 w-full min-w-0 items-center gap-2 rounded-lg px-2 text-left outline-hidden hover:not-disabled:bg-state-base-hover focus-visible:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid disabled:cursor-default disabled:opacity-60',
|
||||
'flex h-8 w-full min-w-0 items-center gap-1 rounded-lg pr-2.5 pl-3 text-left outline-hidden hover:bg-state-base-hover focus-visible:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid',
|
||||
selected && 'bg-state-base-hover',
|
||||
isPending && 'cursor-wait opacity-60',
|
||||
)}
|
||||
>
|
||||
<WorkspaceSkillIcon icon={skill.icon} />
|
||||
<span className="flex w-0 min-w-0 flex-1 flex-col gap-0.5">
|
||||
<span className="truncate system-sm-medium text-text-secondary">{skill.display_name}</span>
|
||||
<span className="truncate system-xs-regular text-text-tertiary">{skill.name}</span>
|
||||
<WorkspaceSkillIcon />
|
||||
<span className="w-0 min-w-0 flex-1 truncate system-sm-medium text-text-secondary">
|
||||
{skill.display_name}
|
||||
</span>
|
||||
{isAdded && (
|
||||
<span className="shrink-0 system-xs-medium text-text-tertiary">
|
||||
{t(($) => $['agentDetail.configure.skills.workspaceSelector.added'])}
|
||||
</span>
|
||||
)}
|
||||
{!isAdded && disabled && (
|
||||
{!isAdded && unavailable && (
|
||||
<span className="shrink-0 system-xs-medium text-text-tertiary">
|
||||
{t(($) => $['agentDetail.configure.skills.workspaceSelector.draft'])}
|
||||
</span>
|
||||
@ -159,19 +163,19 @@ function WorkspaceSkillPreview({ skill }: { skill?: SkillResponse }) {
|
||||
|
||||
if (!skill) {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center px-6 text-center system-xs-regular text-text-tertiary">
|
||||
<div className="flex min-h-32 items-center justify-center px-6 text-center system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['agentDetail.configure.skills.workspaceSelector.empty'])}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col gap-4 p-4">
|
||||
<div className="flex min-w-0 items-start gap-3">
|
||||
<WorkspaceSkillIcon icon={skill.icon} />
|
||||
<div className="flex max-h-[428px] flex-col gap-2 overflow-y-auto px-3 pt-3 pb-4">
|
||||
<div className="flex min-w-0 flex-col items-start gap-1">
|
||||
<WorkspaceSkillIcon />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate system-md-semibold text-text-primary">{skill.display_name}</div>
|
||||
<div className="mt-0.5 truncate system-xs-regular text-text-tertiary">{skill.name}</div>
|
||||
<div className="truncate system-md-medium text-text-primary">{skill.display_name}</div>
|
||||
<div className="truncate system-xs-regular text-text-tertiary">{skill.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
{!!skill.tags?.length && (
|
||||
@ -186,7 +190,7 @@ function WorkspaceSkillPreview({ skill }: { skill?: SkillResponse }) {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<p className="line-clamp-6 system-sm-regular text-text-secondary">{skill.description}</p>
|
||||
<p className="system-xs-regular text-text-secondary">{skill.description}</p>
|
||||
{(skill.updated_by_name || skill.created_by_name) && (
|
||||
<div className="mt-auto system-xs-regular text-text-tertiary">
|
||||
{skill.updated_by_name || skill.created_by_name}
|
||||
@ -240,9 +244,9 @@ function WorkspaceSkillSelector({
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex h-[520px] w-[560px] overflow-hidden rounded-xl border border-divider-regular bg-components-panel-bg shadow-lg">
|
||||
<div className="flex min-w-0 flex-1 flex-col border-r border-divider-subtle">
|
||||
<div className="border-b border-divider-subtle p-3">
|
||||
<div className="relative h-[520px] w-[320px]">
|
||||
<div className="flex h-full w-full flex-col overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg backdrop-blur-[5px]">
|
||||
<div className="border-b border-divider-subtle p-2">
|
||||
<div className="relative">
|
||||
<SearchInput
|
||||
value={keyword}
|
||||
@ -255,12 +259,12 @@ function WorkspaceSkillSelector({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto p-1.5" onScroll={handleListScroll}>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto p-1" onScroll={handleListScroll}>
|
||||
{skillsQuery.isPending && (
|
||||
<div className="space-y-2 p-1">
|
||||
<SkeletonRectangle className="h-10 rounded-lg" />
|
||||
<SkeletonRectangle className="h-10 rounded-lg" />
|
||||
<SkeletonRectangle className="h-10 rounded-lg" />
|
||||
<div className="space-y-1">
|
||||
<SkeletonRectangle className="h-8 rounded-lg" />
|
||||
<SkeletonRectangle className="h-8 rounded-lg" />
|
||||
<SkeletonRectangle className="h-8 rounded-lg" />
|
||||
</div>
|
||||
)}
|
||||
{!skillsQuery.isPending && skills.length === 0 && (
|
||||
@ -272,7 +276,7 @@ function WorkspaceSkillSelector({
|
||||
skills.map((skill) => (
|
||||
<WorkspaceSkillRow
|
||||
key={skill.id}
|
||||
disabled={!skill.latest_published_version_id}
|
||||
unavailable={!skill.latest_published_version_id}
|
||||
isAdded={boundSkillIdSet.has(skill.id)}
|
||||
isPending={isBindingPending}
|
||||
selected={previewSkill?.id === skill.id}
|
||||
@ -282,21 +286,21 @@ function WorkspaceSkillSelector({
|
||||
/>
|
||||
))}
|
||||
{skillsQuery.isFetchingNextPage && (
|
||||
<div className="space-y-2 p-1">
|
||||
<SkeletonRectangle className="h-10 rounded-lg" />
|
||||
<SkeletonRectangle className="h-10 rounded-lg" />
|
||||
<div className="space-y-1">
|
||||
<SkeletonRectangle className="h-8 rounded-lg" />
|
||||
<SkeletonRectangle className="h-8 rounded-lg" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Link
|
||||
href="/skills"
|
||||
className="flex h-10 items-center justify-between border-t border-divider-subtle px-3 system-sm-medium text-text-secondary outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
className="flex h-8 items-center gap-0.5 border-t border-divider-subtle px-4 system-xs-medium text-text-tertiary outline-hidden hover:bg-state-base-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
>
|
||||
<span>{t(($) => $['agentDetail.configure.skills.workspaceSelector.manage'])}</span>
|
||||
<span aria-hidden className="i-ri-arrow-right-up-line size-4 text-text-tertiary" />
|
||||
<span aria-hidden className="i-ri-arrow-right-up-line size-3" />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="w-[240px] shrink-0 bg-background-default">
|
||||
<div className="absolute top-[52px] left-[-244px] w-[240px] overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-[5px]">
|
||||
<WorkspaceSkillPreview skill={previewSkill} />
|
||||
</div>
|
||||
</div>
|
||||
@ -314,20 +318,32 @@ function WorkspaceAgentSkillItem({
|
||||
}) {
|
||||
const { t } = useTranslation('agentV2')
|
||||
const readOnly = useAgentOrchestrateReadOnly()
|
||||
const [isActionsOpen, setIsActionsOpen] = useState(false)
|
||||
const [isRemoveHighlighted, setIsRemoveHighlighted] = useState(false)
|
||||
const displayName = skill.display_name || skill.name
|
||||
const handleOpenInLibrary = useCallback(() => {
|
||||
window.open(`/skills/${skill.id}`, '_blank', 'noopener,noreferrer')
|
||||
}, [skill.id])
|
||||
|
||||
return (
|
||||
<div className="group relative h-8 overflow-hidden rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs shadow-shadow-shadow-3 hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-sm">
|
||||
<div
|
||||
data-workspace-skill-row
|
||||
className={cn(
|
||||
'group relative h-8 overflow-hidden rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs shadow-shadow-shadow-3 hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-sm',
|
||||
isRemoveHighlighted &&
|
||||
'border-state-destructive-border! bg-state-destructive-hover! shadow-xs!',
|
||||
)}
|
||||
>
|
||||
<Link
|
||||
href={`/skills/${skill.id}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="flex h-full w-full min-w-0 cursor-pointer items-center gap-1 rounded-lg py-1 pr-8 pl-2 text-left outline-hidden select-none focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid"
|
||||
className="flex h-full w-full min-w-0 cursor-pointer items-center gap-1 rounded-lg px-2 py-1 text-left outline-hidden select-none focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid"
|
||||
>
|
||||
<WorkspaceSkillIcon icon={skill.icon} />
|
||||
<span
|
||||
aria-hidden
|
||||
className="i-custom-vender-agent-v2-building-blocks size-4 shrink-0 text-text-secondary"
|
||||
/>
|
||||
<span className="flex w-0 min-w-0 flex-1 items-center gap-1">
|
||||
<span className="min-w-0 truncate system-sm-medium text-text-secondary">
|
||||
{displayName}
|
||||
@ -341,35 +357,58 @@ function WorkspaceAgentSkillItem({
|
||||
className={cn(
|
||||
'shrink-0 system-xs-regular text-text-tertiary',
|
||||
!readOnly && 'group-focus-within:opacity-0 group-hover:opacity-0',
|
||||
isActionsOpen && 'opacity-0',
|
||||
)}
|
||||
>
|
||||
{skill.name}
|
||||
</span>
|
||||
</Link>
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenu
|
||||
modal={false}
|
||||
onOpenChange={(open) => {
|
||||
setIsActionsOpen(open)
|
||||
if (!open) setIsRemoveHighlighted(false)
|
||||
}}
|
||||
>
|
||||
<DropdownMenuTrigger
|
||||
aria-label={t(($) => $['agentDetail.configure.skills.moreActions'], {
|
||||
name: displayName,
|
||||
})}
|
||||
className="absolute top-1/2 right-1 z-10 flex size-6 -translate-y-1/2 items-center justify-center rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden data-popup-open:bg-state-base-hover data-popup-open:text-text-secondary"
|
||||
className={cn(
|
||||
'pointer-events-none absolute top-1/2 right-1 z-10 flex size-6 -translate-y-1/2 items-center justify-center rounded-md text-text-tertiary opacity-0 group-focus-within:pointer-events-auto group-focus-within:opacity-100 group-hover:pointer-events-auto group-hover:opacity-100 hover:bg-state-base-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden data-popup-open:pointer-events-auto data-popup-open:bg-state-base-hover data-popup-open:text-text-secondary data-popup-open:opacity-100',
|
||||
isRemoveHighlighted && 'text-text-destructive!',
|
||||
)}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<span aria-hidden className="i-ri-more-fill size-4" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent placement="bottom-end" sideOffset={4} popupClassName="w-48">
|
||||
<DropdownMenuItem className="gap-2" onClick={handleOpenInLibrary}>
|
||||
<span aria-hidden className="i-ri-arrow-right-up-line size-4 shrink-0" />
|
||||
<span
|
||||
aria-hidden
|
||||
className="i-ri-arrow-right-up-line size-4 shrink-0 text-text-tertiary"
|
||||
/>
|
||||
<span>{t(($) => $['agentDetail.configure.skills.openInLibrary'])}</span>
|
||||
</DropdownMenuItem>
|
||||
{canRemove && (
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
className="gap-2"
|
||||
onClick={() => onRemove(skill.id)}
|
||||
>
|
||||
<span aria-hidden className="i-ri-delete-bin-line size-4 shrink-0" />
|
||||
<span>{t(($) => $['agentDetail.configure.skills.removeAction'])}</span>
|
||||
</DropdownMenuItem>
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
data-workspace-skill-remove-action
|
||||
className="group gap-2 data-highlighted:bg-state-destructive-hover data-highlighted:text-text-destructive"
|
||||
onClick={() => onRemove(skill.id)}
|
||||
onFocus={() => setIsRemoveHighlighted(true)}
|
||||
onBlur={() => setIsRemoveHighlighted(false)}
|
||||
onMouseEnter={() => setIsRemoveHighlighted(true)}
|
||||
onMouseLeave={() => setIsRemoveHighlighted(false)}
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className="i-ri-delete-bin-line size-4 shrink-0 text-text-tertiary group-data-highlighted:text-text-destructive"
|
||||
/>
|
||||
<span>{t(($) => $['agentDetail.configure.skills.removeAction'])}</span>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@ -595,14 +634,14 @@ export function AgentSkills() {
|
||||
sideOffset={4}
|
||||
popupClassName={
|
||||
addMenuView === 'menu'
|
||||
? 'w-[320px] bg-components-panel-bg-blur p-1 shadow-lg backdrop-blur-[5px]'
|
||||
: 'w-[560px] overflow-hidden border-none bg-transparent p-0 shadow-none'
|
||||
? 'w-[280px] bg-components-panel-bg-blur p-1 shadow-lg backdrop-blur-[5px]'
|
||||
: 'w-[320px] overflow-visible border-none bg-transparent p-0 shadow-none'
|
||||
}
|
||||
>
|
||||
{addMenuView === 'menu' ? (
|
||||
<>
|
||||
<AgentSkillAddMenuItem
|
||||
iconClassName="i-custom-public-agent-building-blocks"
|
||||
iconClassName="i-custom-vender-agent-v2-building-blocks"
|
||||
label={t(($) => $['agentDetail.configure.skills.addMenu.workspace.label'])}
|
||||
description={t(
|
||||
($) => $['agentDetail.configure.skills.addMenu.workspace.description'],
|
||||
@ -638,11 +677,6 @@ export function AgentSkills() {
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{workspaceSkills.length > 0 && (
|
||||
<div className="px-1 pt-1 pb-0.5 system-xs-medium-uppercase text-text-tertiary">
|
||||
{t(($) => $['agentDetail.configure.skills.fromSkillLibrary'])}
|
||||
</div>
|
||||
)}
|
||||
{workspaceSkills.map((skill) => (
|
||||
<WorkspaceAgentSkillItem
|
||||
key={skill.id}
|
||||
|
||||
@ -4,6 +4,13 @@ import type { AgentConfigApiContext } from '../config-context'
|
||||
import type { AgentSkill } from '@/features/agent-v2/agent-composer/form-state'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog } from '@langgenius/dify-ui/dialog'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -28,6 +35,8 @@ export function AgentSkillItem({
|
||||
const queryClient = useQueryClient()
|
||||
const readOnly = useAgentOrchestrateReadOnly()
|
||||
const [isPreviewOpen, setIsPreviewOpen] = useState(false)
|
||||
const [isActionsOpen, setIsActionsOpen] = useState(false)
|
||||
const [isRemoveHighlighted, setIsRemoveHighlighted] = useState(false)
|
||||
const handleRemove = useCallback(() => {
|
||||
onRemove(skill.id)
|
||||
}, [onRemove, skill.id])
|
||||
@ -84,16 +93,26 @@ export function AgentSkillItem({
|
||||
|
||||
return (
|
||||
<Dialog open={isPreviewOpen} onOpenChange={setIsPreviewOpen}>
|
||||
<div className="group relative h-8 overflow-hidden rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs shadow-shadow-shadow-3 focus-within:bg-components-panel-on-panel-item-bg-hover focus-within:shadow-sm hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-sm has-[[data-agent-skill-remove-button]:focus-visible]:border-state-destructive-border! has-[[data-agent-skill-remove-button]:focus-visible]:bg-state-destructive-hover! has-[[data-agent-skill-remove-button]:focus-visible]:shadow-xs! has-[[data-agent-skill-remove-button]:hover]:border-state-destructive-border! has-[[data-agent-skill-remove-button]:hover]:bg-state-destructive-hover! has-[[data-agent-skill-remove-button]:hover]:shadow-xs!">
|
||||
<div
|
||||
data-agent-skill-row
|
||||
className={cn(
|
||||
'group relative h-8 overflow-hidden rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs shadow-shadow-shadow-3 focus-within:bg-components-panel-on-panel-item-bg-hover focus-within:shadow-sm hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-sm',
|
||||
isRemoveHighlighted &&
|
||||
'border-state-destructive-border! bg-state-destructive-hover! shadow-xs!',
|
||||
)}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={skill.name}
|
||||
disabled={skill.isMissing}
|
||||
className="flex h-full w-full min-w-0 cursor-pointer items-center gap-1 rounded-lg py-1 pr-2.5 pl-2 text-left outline-hidden select-none focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid disabled:cursor-default"
|
||||
className="flex h-full w-full min-w-0 cursor-pointer items-center gap-1 rounded-lg px-2 py-1 text-left outline-hidden select-none focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid disabled:cursor-default"
|
||||
onClick={handleOpenPreview}
|
||||
>
|
||||
<span aria-hidden className="i-custom-public-agent-building-blocks size-4 shrink-0" />
|
||||
<span className="w-0 min-w-0 flex-1 truncate system-sm-medium text-text-secondary">
|
||||
<span
|
||||
aria-hidden
|
||||
className="i-custom-vender-agent-v2-building-blocks size-4 shrink-0 text-text-secondary"
|
||||
/>
|
||||
<span className="w-0 min-w-0 flex-1 truncate system-sm-medium text-text-secondary decoration-divider-deep decoration-dotted group-focus-within:underline group-hover:underline">
|
||||
{skill.name}
|
||||
</span>
|
||||
{skill.isMissing ? (
|
||||
@ -101,11 +120,11 @@ export function AgentSkillItem({
|
||||
) : (
|
||||
<span
|
||||
className={cn(
|
||||
'shrink-0 system-xs-regular text-text-tertiary',
|
||||
'group-focus-within:opacity-0 group-hover:opacity-0',
|
||||
'shrink-0 rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-1 py-0.5 system-2xs-medium-uppercase text-text-tertiary group-focus-within:opacity-0 group-hover:opacity-0',
|
||||
isActionsOpen && 'opacity-0',
|
||||
)}
|
||||
>
|
||||
{t(($) => $['agentDetail.configure.skills.itemType'])}
|
||||
{t(($) => $['agentDetail.configure.skills.addMenu.upload.badge'])}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
@ -115,32 +134,57 @@ export function AgentSkillItem({
|
||||
label={t(($) => $['agentDetail.configure.skills.missing'])}
|
||||
/>
|
||||
)}
|
||||
{!skill.isMissing && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`${tCommon(($) => $['operation.download'])} ${skill.name}`}
|
||||
onClick={handleDownload}
|
||||
className={cn(
|
||||
'pointer-events-none absolute top-1/2 flex size-5 -translate-y-1/2 items-center justify-center rounded-md text-text-tertiary opacity-0 group-focus-within:pointer-events-auto group-focus-within:opacity-100 group-hover:pointer-events-auto group-hover:opacity-100 hover:bg-state-base-hover hover:text-text-secondary focus-visible:bg-state-base-hover focus-visible:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden',
|
||||
readOnly ? 'right-1' : 'right-7',
|
||||
)}
|
||||
{(!skill.isMissing || !readOnly) && (
|
||||
<DropdownMenu
|
||||
modal={false}
|
||||
onOpenChange={(open) => {
|
||||
setIsActionsOpen(open)
|
||||
if (!open) setIsRemoveHighlighted(false)
|
||||
}}
|
||||
>
|
||||
<span aria-hidden className="i-ri-download-line size-4" />
|
||||
</button>
|
||||
)}
|
||||
{!readOnly && (
|
||||
<button
|
||||
type="button"
|
||||
data-agent-skill-remove-button
|
||||
aria-label={t(($) => $['agentDetail.configure.skills.remove'], { name: skill.name })}
|
||||
onClick={handleRemove}
|
||||
className={cn(
|
||||
'pointer-events-none absolute top-1/2 flex size-5 -translate-y-1/2 items-center justify-center rounded-md text-text-tertiary opacity-0 group-focus-within:pointer-events-auto group-focus-within:opacity-100 group-hover:pointer-events-auto group-hover:opacity-100 hover:bg-state-destructive-hover hover:text-text-destructive focus-visible:bg-state-destructive-hover focus-visible:text-text-destructive focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden',
|
||||
skill.isMissing ? 'right-7' : 'right-1',
|
||||
)}
|
||||
>
|
||||
<span aria-hidden className="i-ri-delete-bin-line size-4" />
|
||||
</button>
|
||||
<DropdownMenuTrigger
|
||||
data-agent-skill-actions
|
||||
aria-label={t(($) => $['agentDetail.configure.skills.moreActions'], {
|
||||
name: skill.name,
|
||||
})}
|
||||
className={cn(
|
||||
'pointer-events-none absolute top-1/2 right-1 z-10 flex size-6 -translate-y-1/2 items-center justify-center rounded-md text-text-tertiary opacity-0 group-focus-within:pointer-events-auto group-focus-within:opacity-100 group-hover:pointer-events-auto group-hover:opacity-100 hover:bg-state-base-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden data-popup-open:pointer-events-auto data-popup-open:bg-state-base-hover data-popup-open:text-text-secondary data-popup-open:opacity-100',
|
||||
isRemoveHighlighted && 'text-text-destructive!',
|
||||
)}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<span aria-hidden className="i-ri-more-fill size-4" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent placement="bottom-end" sideOffset={4} popupClassName="w-48">
|
||||
{!skill.isMissing && (
|
||||
<DropdownMenuItem className="gap-2" onClick={handleDownload}>
|
||||
<span
|
||||
aria-hidden
|
||||
className="i-ri-download-line size-4 shrink-0 text-text-tertiary"
|
||||
/>
|
||||
<span>{tCommon(($) => $['operation.download'])}</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{!skill.isMissing && !readOnly && <DropdownMenuSeparator />}
|
||||
{!readOnly && (
|
||||
<DropdownMenuItem
|
||||
data-agent-skill-remove-button
|
||||
className="group gap-2 data-highlighted:bg-state-destructive-hover data-highlighted:text-text-destructive"
|
||||
onClick={handleRemove}
|
||||
onFocus={() => setIsRemoveHighlighted(true)}
|
||||
onBlur={() => setIsRemoveHighlighted(false)}
|
||||
onMouseEnter={() => setIsRemoveHighlighted(true)}
|
||||
onMouseLeave={() => setIsRemoveHighlighted(false)}
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className="i-ri-delete-bin-line size-4 shrink-0 text-text-tertiary group-data-highlighted:text-text-destructive"
|
||||
/>
|
||||
<span>{tCommon(($) => $['operation.delete'])}</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
{isPreviewOpen && <AgentSkillDetailDialog skillName={skill.name} detail={detail} />}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user