refactor(web): migrate domain icon commands (#40621)

This commit is contained in:
yyh
2026-08-13 02:29:36 +00:00
committed by GitHub
parent 312bc4912d
commit 71b31e17b0
63 changed files with 730 additions and 608 deletions
+1 -1
View File
@@ -1169,7 +1169,7 @@
},
"web/app/components/base/icons/src/public/other/index.ts": {
"no-barrel-files/no-barrel-files": {
"count": 6
"count": 4
}
},
"web/app/components/base/icons/src/public/tracing/index.ts": {
@@ -81,4 +81,4 @@ const ActionButton = ({
ActionButton.displayName = 'ActionButton'
export default ActionButton
export { ActionButton, ActionButtonState }
export { ActionButtonState }
@@ -1,61 +0,0 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "27",
"height": "27",
"viewBox": "0 0 27 27",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"fill-rule": "evenodd",
"clip-rule": "evenodd",
"d": "M9.625 0C9.27992 0.000320434 8.93828 0.0686147 8.6196 0.200983C8.30091 0.333351 8.01142 0.527199 7.76766 0.771458C7.5239 1.01572 7.33064 1.3056 7.19893 1.62456C7.06721 1.94351 6.99962 2.28529 7 2.63037C6.99968 2.97541 7.06732 3.31714 7.19907 3.63603C7.33081 3.95493 7.52408 4.24476 7.76783 4.48897C8.01159 4.73317 8.30105 4.92698 8.61971 5.05932C8.93836 5.19165 9.27996 5.25993 9.625 5.26025H12.25V2.63037C12.2504 2.28529 12.1828 1.94351 12.0511 1.62456C11.9194 1.3056 11.7261 1.01572 11.4823 0.771458C11.2386 0.527199 10.9491 0.333351 10.6304 0.200983C10.3117 0.0686147 9.97008 0.000320434 9.625 0ZM9.625 7.01416H2.625C2.27996 7.01448 1.93836 7.08276 1.61971 7.21509C1.30106 7.34743 1.01159 7.54124 0.767835 7.78544C0.524081 8.02965 0.330814 8.31948 0.199069 8.63838C0.0673241 8.95728 -0.000319124 9.299 1.63476e-06 9.64404C-0.000383292 9.98912 0.0672126 10.3309 0.198929 10.6499C0.330645 10.9688 0.523902 11.2587 0.767662 11.503C1.01142 11.7472 1.30091 11.9411 1.6196 12.0734C1.93828 12.2058 2.27992 12.2741 2.625 12.2744H9.625C9.97008 12.2741 10.3117 12.2058 10.6304 12.0734C10.9491 11.9411 11.2386 11.7472 11.4823 11.503C11.7261 11.2587 11.9194 10.9688 12.0511 10.6499C12.1828 10.3309 12.2504 9.98912 12.25 9.64404C12.2503 9.299 12.1827 8.95728 12.0509 8.63838C11.9192 8.31948 11.7259 8.02965 11.4822 7.78544C11.2384 7.54124 10.9489 7.34743 10.6303 7.21509C10.3116 7.08276 9.97004 7.01448 9.625 7.01416Z",
"fill": "#44BEDF"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"fill-rule": "evenodd",
"clip-rule": "evenodd",
"d": "M26.25 9.64404C26.2503 9.299 26.1827 8.95728 26.0509 8.63838C25.9192 8.31948 25.7259 8.02965 25.4822 7.78544C25.2384 7.54124 24.9489 7.34743 24.6303 7.21509C24.3116 7.08276 23.97 7.01448 23.625 7.01416C23.28 7.01448 22.9384 7.08276 22.6197 7.21509C22.3011 7.34743 22.0116 7.54124 21.7678 7.78544C21.5241 8.02965 21.3308 8.31948 21.1991 8.63838C21.0673 8.95728 20.9997 9.299 21 9.64404V12.2744H23.625C23.9701 12.2741 24.3117 12.2058 24.6304 12.0734C24.9491 11.9411 25.2386 11.7472 25.4823 11.503C25.7261 11.2587 25.9194 10.9688 26.0511 10.6499C26.1828 10.3309 26.2504 9.98912 26.25 9.64404ZM19.25 9.64404V2.63037C19.2504 2.28529 19.1828 1.94351 19.0511 1.62456C18.9194 1.3056 18.7261 1.01572 18.4823 0.771458C18.2386 0.527199 17.9491 0.333351 17.6304 0.200983C17.3117 0.0686147 16.9701 0.000320434 16.625 0C16.2799 0.000320434 15.9383 0.0686147 15.6196 0.200983C15.3009 0.333351 15.0114 0.527199 14.7677 0.771458C14.5239 1.01572 14.3306 1.3056 14.1989 1.62456C14.0672 1.94351 13.9996 2.28529 14 2.63037V9.64404C13.9996 9.98912 14.0672 10.3309 14.1989 10.6499C14.3306 10.9688 14.5239 11.2587 14.7677 11.503C15.0114 11.7472 15.3009 11.9411 15.6196 12.0734C15.9383 12.2058 16.2799 12.2741 16.625 12.2744C16.9701 12.2741 17.3117 12.2058 17.6304 12.0734C17.9491 11.9411 18.2386 11.7472 18.4823 11.503C18.7261 11.2587 18.9194 10.9688 19.0511 10.6499C19.1828 10.3309 19.2504 9.98912 19.25 9.64404Z",
"fill": "#2EB67D"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"fill-rule": "evenodd",
"clip-rule": "evenodd",
"d": "M16.625 26.302C16.9701 26.3017 17.3117 26.2334 17.6304 26.101C17.9491 25.9686 18.2386 25.7748 18.4823 25.5305C18.7261 25.2863 18.9194 24.9964 19.0511 24.6774C19.1828 24.3585 19.2504 24.0167 19.25 23.6716C19.2503 23.3266 19.1827 22.9849 19.0509 22.666C18.9192 22.3471 18.7259 22.0572 18.4822 21.813C18.2384 21.5688 17.9489 21.375 17.6303 21.2427C17.3116 21.1103 16.97 21.0421 16.625 21.0417H14V23.6716C13.9996 24.0167 14.0672 24.3585 14.1989 24.6774C14.3306 24.9964 14.5239 25.2863 14.7677 25.5305C15.0114 25.7748 15.3009 25.9686 15.6196 26.101C15.9383 26.2334 16.2799 26.3017 16.625 26.302ZM16.625 19.2878H23.625C23.97 19.2875 24.3116 19.2192 24.6303 19.0869C24.9489 18.9546 25.2384 18.7608 25.4822 18.5166C25.7259 18.2723 25.9192 17.9825 26.0509 17.6636C26.1827 17.3447 26.2503 17.003 26.25 16.658C26.2504 16.3129 26.1828 15.9711 26.0511 15.6521C25.9194 15.3332 25.7261 15.0433 25.4823 14.799C25.2386 14.5548 24.9491 14.3609 24.6304 14.2286C24.3117 14.0962 23.9701 14.0279 23.625 14.0276H16.625C16.2799 14.0279 15.9383 14.0962 15.6196 14.2286C15.3009 14.3609 15.0114 14.5548 14.7677 14.799C14.5239 15.0433 14.3306 15.3332 14.1989 15.6521C14.0672 15.9711 13.9996 16.3129 14 16.658C13.9997 17.003 14.0673 17.3447 14.1991 17.6636C14.3308 17.9825 14.5241 18.2723 14.7678 18.5166C15.0116 18.7608 15.3011 18.9546 15.6197 19.0869C15.9384 19.2192 16.28 19.2875 16.625 19.2878Z",
"fill": "#ECB22E"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"fill-rule": "evenodd",
"clip-rule": "evenodd",
"d": "M2.22485e-06 16.658C-0.000318534 17.003 0.0673247 17.3447 0.19907 17.6636C0.330815 17.9825 0.524081 18.2723 0.767835 18.5166C1.01159 18.7608 1.30106 18.9546 1.61971 19.0869C1.93836 19.2192 2.27996 19.2875 2.625 19.2878C2.97004 19.2875 3.31164 19.2192 3.63029 19.0869C3.94895 18.9546 4.23841 18.7608 4.48217 18.5166C4.72592 18.2723 4.91919 17.9825 5.05093 17.6636C5.18268 17.3447 5.25032 17.003 5.25 16.658V14.0276H2.625C2.27988 14.0279 1.9382 14.0962 1.61948 14.2286C1.30077 14.361 1.01126 14.5549 0.76749 14.7992C0.523723 15.0435 0.330477 15.3335 0.198789 15.6525C0.0671016 15.9715 -0.000446885 16.3128 2.22485e-06 16.658ZM7 16.658V23.6716C6.99962 24.0167 7.06721 24.3585 7.19893 24.6774C7.33064 24.9964 7.5239 25.2863 7.76766 25.5305C8.01142 25.7748 8.30091 25.9686 8.6196 26.101C8.93828 26.2334 9.27992 26.3017 9.625 26.302C9.97008 26.3017 10.3117 26.2334 10.6304 26.101C10.9491 25.9686 11.2386 25.7748 11.4823 25.5305C11.7261 25.2863 11.9194 24.9964 12.0511 24.6774C12.1828 24.3585 12.2504 24.0167 12.25 23.6716V16.6584C12.2504 16.3134 12.1828 15.9716 12.0511 15.6526C11.9194 15.3337 11.7261 15.0438 11.4823 14.7995C11.2386 14.5553 10.9491 14.3614 10.6304 14.2291C10.3117 14.0967 9.97008 14.0284 9.625 14.0281C9.27992 14.0284 8.93828 14.0967 8.6196 14.2291C8.30091 14.3614 8.01142 14.5553 7.76766 14.7995C7.5239 15.0438 7.33064 15.3337 7.19893 15.6526C7.06721 15.9716 6.99962 16.3129 7 16.658Z",
"fill": "#E01E5A"
},
"children": []
}
]
},
"name": "Slack"
}
@@ -1,18 +0,0 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import type { IconData } from '@/app/components/base/icons/IconBase'
import * as React from 'react'
import IconBase from '@/app/components/base/icons/IconBase'
import data from './Slack.json'
const Icon = ({
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
}) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'Slack'
export default Icon
@@ -1,146 +0,0 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "28",
"height": "28",
"viewBox": "0 0 28 28",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"clip-path": "url(#clip0_20307_29670)"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"opacity": "0.1",
"d": "M9.33332 11.8067V20.4167C9.33204 21.432 9.57619 22.4327 10.045 23.3333H15.8783C16.2912 23.305 16.6807 23.1312 16.9776 22.8428C17.2745 22.5545 17.4596 22.1702 17.5 21.7583V10.5H17.1733H10.64C10.2934 10.5 9.96108 10.6377 9.71603 10.8827C9.47098 11.1278 9.33332 11.4601 9.33332 11.8067Z",
"fill": "black"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"opacity": "0.1",
"d": "M16.135 7H12.635C12.8805 7.78672 13.3726 8.47355 14.0386 8.9589C14.7047 9.44425 15.5093 9.70234 16.3333 9.695C16.7301 9.68885 17.1235 9.62197 17.5 9.49667V8.33C17.488 7.97504 17.3392 7.63847 17.0849 7.39061C16.8305 7.14276 16.4902 7.00281 16.135 7Z",
"fill": "black"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M26.6817 10.5H20.8484L19.2267 11.8183V18.34C19.2846 19.4628 19.7715 20.5204 20.5867 21.2946C21.4019 22.0689 22.4833 22.5005 23.6075 22.5005C24.7318 22.5005 25.8131 22.0689 26.6283 21.2946C27.4436 20.5204 27.9304 19.4628 27.9884 18.34V11.8183C27.9899 11.6458 27.9572 11.4746 27.8923 11.3147C27.8273 11.1548 27.7313 11.0094 27.6098 10.8868C27.4883 10.7643 27.3437 10.667 27.1844 10.6006C27.0251 10.5342 26.8543 10.5 26.6817 10.5Z",
"fill": "#5059C9"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M23.9167 9.33333C25.5275 9.33333 26.8333 8.0275 26.8333 6.41667C26.8333 4.80584 25.5275 3.5 23.9167 3.5C22.3058 3.5 21 4.80584 21 6.41667C21 8.0275 22.3058 9.33333 23.9167 9.33333Z",
"fill": "#5059C9"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M10.64 10.5H20.86C21.2065 10.5 21.5389 10.6377 21.7839 10.8827C22.029 11.1278 22.1666 11.4601 22.1666 11.8067V20.4167C22.1666 22.1185 21.4906 23.7506 20.2872 24.9539C19.0839 26.1573 17.4518 26.8333 15.75 26.8333C14.0482 26.8333 12.4161 26.1573 11.2127 24.9539C10.0094 23.7506 9.33331 22.1185 9.33331 20.4167V11.8067C9.33331 11.4601 9.47098 11.1278 9.71603 10.8827C9.96107 10.6377 10.2934 10.5 10.64 10.5Z",
"fill": "#7B83EB"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M16.3333 9.69477C18.4661 9.69477 20.195 7.96584 20.195 5.8331C20.195 3.70036 18.4661 1.97144 16.3333 1.97144C14.2006 1.97144 12.4717 3.70036 12.4717 5.8331C12.4717 7.96584 14.2006 9.69477 16.3333 9.69477Z",
"fill": "#7B83EB"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"opacity": "0.5",
"d": "M9.33332 11.8067V20.4167C9.33204 21.432 9.57619 22.4327 10.045 23.3333H15.8783C16.2912 23.305 16.6807 23.1312 16.9776 22.8428C17.2745 22.5545 17.4596 22.1702 17.5 21.7583V10.5H17.1733H10.64C10.2934 10.5 9.96108 10.6377 9.71603 10.8827C9.47098 11.1278 9.33332 11.4601 9.33332 11.8067Z",
"fill": "black"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"opacity": "0.5",
"d": "M16.135 7H12.635C12.8805 7.78672 13.3726 8.47355 14.0386 8.9589C14.7047 9.44425 15.5093 9.70234 16.3333 9.695C16.7301 9.68885 17.1235 9.62197 17.5 9.49667V8.33C17.488 7.97504 17.3392 7.63847 17.0849 7.39061C16.8305 7.14276 16.4902 7.00281 16.135 7Z",
"fill": "black"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M14.9683 5.83325H1.365C0.611131 5.83325 0 6.44438 0 7.19825V20.8016C0 21.5555 0.611131 22.1666 1.365 22.1666H14.9683C15.7222 22.1666 16.3333 21.5555 16.3333 20.8016V7.19825C16.3333 6.44438 15.7222 5.83325 14.9683 5.83325Z",
"fill": "#4B53BC"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M11.8767 11.1766H9.08833V18.6666H7.25666V11.1766H4.45667V9.33325H11.8767V11.1766Z",
"fill": "white"
},
"children": []
}
]
},
{
"type": "element",
"name": "defs",
"attributes": {},
"children": [
{
"type": "element",
"name": "clipPath",
"attributes": {
"id": "clip0_20307_29670"
},
"children": [
{
"type": "element",
"name": "rect",
"attributes": {
"width": "28",
"height": "28",
"fill": "white"
},
"children": []
}
]
}
]
}
]
},
"name": "Teams"
}
@@ -1,18 +0,0 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import type { IconData } from '@/app/components/base/icons/IconBase'
import * as React from 'react'
import IconBase from '@/app/components/base/icons/IconBase'
import data from './Teams.json'
const Icon = ({
ref,
...props
}: React.SVGProps<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
}) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'Teams'
export default Icon
@@ -2,5 +2,3 @@ export { default as Comment } from './Comment'
export { default as DefaultToolIcon } from './DefaultToolIcon'
export { default as Message3Fill } from './Message3Fill'
export { default as RowStruct } from './RowStruct'
export { default as Slack } from './Slack'
export { default as Teams } from './Teams'
@@ -1,12 +1,12 @@
'use client'
import type { FC } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { toast } from '@langgenius/dify-ui/toast'
import { RiDeleteBinLine, RiNodeTree, RiUploadCloud2Line } from '@remixicon/react'
import * as React from 'react'
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import { formatFileSize } from '@/utils/format'
type Props = Readonly<{
@@ -127,9 +127,12 @@ const Uploader: FC<Props> = ({ file, updateFile, className }) => {
</div>
</div>
<div className="hidden items-center pr-3 group-hover:flex">
<ActionButton onClick={removeFile}>
<RiDeleteBinLine className="size-4 text-text-tertiary" />
</ActionButton>
<IconButton
aria-label={t(($) => $['operation.remove'], { ns: 'common' })}
onClick={removeFile}
>
<RiDeleteBinLine aria-hidden="true" className="size-4 text-text-tertiary" />
</IconButton>
</div>
</div>
)}
@@ -11,12 +11,12 @@ import {
} from '@langgenius/dify-ui/alert-dialog'
import { Button } from '@langgenius/dify-ui/button'
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { toast } from '@langgenius/dify-ui/toast'
import { RiBook2Line, RiCloseLine, RiInformation2Line, RiLock2Fill } from '@remixicon/react'
import { memo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import { createExternalAPI } from '@/service/datasets'
import Form from './Form'
@@ -184,13 +184,13 @@ const AddExternalAPIModal: FC<AddExternalAPIModalProps> = ({
</div>
)}
</div>
<ActionButton
<IconButton
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
className="absolute top-5 right-5"
onClick={onCancel}
>
<RiCloseLine aria-hidden className="h-4.5 w-4.5 shrink-0 text-text-tertiary" />
</ActionButton>
</IconButton>
<Form
value={formData}
onChange={handleDataChange}
@@ -1,10 +1,10 @@
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { RiAddLine, RiBookOpenLine, RiCloseLine } from '@remixicon/react'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Loading from '@/app/components/base/loading'
import { useDocLink } from '@/context/i18n'
import { useModalContext } from '@/context/modal-context'
@@ -71,12 +71,12 @@ const ExternalAPIPanel: React.FC<ExternalAPIPanelProps> = ({
</a>
</div>
<div className="flex items-center">
<ActionButton
<IconButton
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
onClick={() => onClose()}
>
<RiCloseLine aria-hidden className="size-4 text-text-tertiary" />
</ActionButton>
</IconButton>
</div>
</div>
{canManageExternalKnowledgeApi && (
@@ -130,12 +130,6 @@ describe('ExternalKnowledgeAPICard', () => {
expect(screen.queryByRole('button', { name: editButtonName })).not.toBeInTheDocument()
expect(screen.queryByRole('button', { name: deleteButtonName })).not.toBeInTheDocument()
})
it('should render API connection icon', () => {
const { container } = render(<ExternalKnowledgeAPICard {...defaultProps} />)
const icon = container.querySelector('svg')
expect(icon)!.toBeInTheDocument()
})
})
describe('User Interactions - Edit', () => {
@@ -9,13 +9,11 @@ import {
AlertDialogDescription,
AlertDialogTitle,
} from '@langgenius/dify-ui/alert-dialog'
import { RiDeleteBinLine, RiEditLine } from '@remixicon/react'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { useQueryClient } from '@tanstack/react-query'
import * as React from 'react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development'
import { useModalContext } from '@/context/modal-context'
import { consoleQuery } from '@/service/client'
import {
@@ -127,34 +125,34 @@ const ExternalKnowledgeAPICard: React.FC<ExternalKnowledgeAPICardProps> = ({
>
<div className="flex grow flex-col items-start justify-center gap-1.5 py-1">
<div className="flex items-center gap-1 self-stretch text-text-secondary">
<ApiConnectionMod className="size-4" />
<span
aria-hidden
className="i-custom-vender-solid-development-api-connection-mod size-4"
/>
<div className="system-sm-medium">{api.name}</div>
</div>
<div className="self-stretch system-xs-regular text-text-tertiary">{endpoint}</div>
</div>
{canManageExternalKnowledgeApi && (
<div className="flex items-start gap-1">
<ActionButton
<IconButton
aria-label={`${t(($) => $['operation.edit'], { ns: 'common' })} ${api.name} ${endpoint} ${position}`}
onClick={handleEditClick}
>
<RiEditLine
<span
aria-hidden
className="size-4 text-text-tertiary hover:text-text-secondary"
className="i-ri-edit-line size-4 text-text-tertiary hover:text-text-secondary"
/>
</ActionButton>
<ActionButton
</IconButton>
<IconButton
aria-label={`${t(($) => $['operation.delete'], { ns: 'common' })} ${api.name} ${endpoint} ${position}`}
className="hover:bg-state-destructive-hover"
tone="destructive"
onClick={handleDeleteClick}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<RiDeleteBinLine
aria-hidden
className="size-4 text-text-tertiary hover:text-text-destructive"
/>
</ActionButton>
<span aria-hidden className="i-ri-delete-bin-line size-4" />
</IconButton>
</div>
)}
</div>
@@ -14,9 +14,9 @@ import {
useRole,
} from '@floating-ui/react'
import { cn } from '@langgenius/dify-ui/cn'
import { RiDeleteBinLine } from '@remixicon/react'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { useState } from 'react'
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
import { useTranslation } from 'react-i18next'
import { SliceContainer, SliceContent, SliceDivider, SliceLabel } from './shared'
type EditSliceProps = SliceProps<{
@@ -30,6 +30,7 @@ type EditSliceProps = SliceProps<{
}>
export const EditSlice: FC<EditSliceProps> = (props) => {
const { t } = useTranslation()
const {
label,
className,
@@ -94,16 +95,19 @@ export const EditSlice: FC<EditSliceProps> = (props) => {
onMouseEnter={() => setDelBtnHover(true)}
onMouseLeave={() => setDelBtnHover(false)}
>
<ActionButton
<IconButton
aria-label={t(($) => $['operation.remove'], { ns: 'common' })}
variant="ghost"
tone="destructive"
className="rounded-lg bg-state-destructive-hover hover:bg-state-destructive-hover"
onClick={(e) => {
e.stopPropagation()
onDelete()
setDelBtnShow(false)
}}
state={ActionButtonState.Destructive}
>
<RiDeleteBinLine className="size-4" />
</ActionButton>
<span aria-hidden className="i-ri-delete-bin-line size-4" />
</IconButton>
</span>
</FloatingFocusManager>
)}
@@ -1,8 +1,8 @@
import { Button } from '@langgenius/dify-ui/button'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { RiCloseLine } from '@remixicon/react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import RetrievalSettings from '../external-knowledge-base/create/RetrievalSettings'
type ModifyExternalRetrievalModalProps = {
@@ -55,13 +55,13 @@ const ModifyExternalRetrievalModal: React.FC<ModifyExternalRetrievalModalProps>
<div className="grow system-xl-semibold text-text-primary">
{t(($) => $.settingTitle, { ns: 'datasetHitTesting' })}
</div>
<ActionButton
<IconButton
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
className="ml-auto"
onClick={onClose}
>
<RiCloseLine aria-hidden className="size-4 shrink-0" />
</ActionButton>
</IconButton>
</div>
<div className="flex flex-col items-start justify-center gap-4 self-stretch p-4 pt-2">
<RetrievalSettings
@@ -17,10 +17,10 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@langgenius/dify-ui/dropdown-menu'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { toast } from '@langgenius/dify-ui/toast'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import {
useCopyAccessRule,
useDeleteAccessRule,
@@ -76,15 +76,15 @@ const AccessRuleRowMenu = ({ rule, onView, onEdit }: AccessRuleRowMenuProps) =>
<DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger
render={
<ActionButton
size="l"
className="data-popup-open:bg-state-base-hover"
<IconButton
size="lg"
aria-label={t(($) => $['operation.moreActions'], { ns: 'common' })}
/>
className="data-popup-open:bg-state-base-hover"
>
<span aria-hidden className="i-ri-more-fill h-4 w-4 text-text-tertiary" />
</IconButton>
}
>
<span aria-hidden className="i-ri-more-fill h-4 w-4 text-text-tertiary" />
</DropdownMenuTrigger>
/>
<DropdownMenuContent placement="bottom-end" sideOffset={4} popupClassName="min-w-[140px]">
{isBuiltIn ? (
<DropdownMenuItem
@@ -6,9 +6,9 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@langgenius/dify-ui/dropdown-menu'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { memo, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import { CredentialTypeEnum } from '@/app/components/plugins/plugin-auth/types'
type OperatorProps = {
@@ -46,13 +46,13 @@ const Operator = ({
<DropdownMenu>
<DropdownMenuTrigger
render={
<ActionButton
size="l"
className="focus-visible:ring-2 focus-visible:ring-state-accent-solid data-popup-open:bg-state-base-hover"
<IconButton
size="lg"
aria-label={t(($) => $['operation.more'], { ns: 'common' })}
className="data-popup-open:bg-state-base-hover"
>
<span aria-hidden className="i-ri-more-fill size-4 text-text-tertiary" />
</ActionButton>
</IconButton>
}
/>
<DropdownMenuContent placement="bottom-end" sideOffset={4} popupClassName="min-w-[200px]">
@@ -17,11 +17,11 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@langgenius/dify-ui/dropdown-menu'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { toast } from '@langgenius/dify-ui/toast'
import { useQueryClient } from '@tanstack/react-query'
import { memo, useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import { useUpdateRolesOfMember } from '@/service/access-control/use-member-roles'
import { deleteMemberOrCancelInvitation } from '@/service/common'
import { commonQueryKeys } from '@/service/use-common'
@@ -118,18 +118,18 @@ const MemberMenu = ({
<DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger
render={
<ActionButton
size="l"
className="focus-visible:ring-2 focus-visible:ring-state-accent-solid data-popup-open:bg-state-base-hover"
<IconButton
size="lg"
aria-label={t(($) => $['members.memberActions'], {
ns: 'common',
defaultValue: 'Member actions',
})}
/>
className="data-popup-open:bg-state-base-hover"
>
<span aria-hidden className="i-ri-more-fill h-4 w-4 text-text-tertiary" />
</IconButton>
}
>
<span aria-hidden className="i-ri-more-fill h-4 w-4 text-text-tertiary" />
</DropdownMenuTrigger>
/>
<DropdownMenuContent
placement="bottom-end"
sideOffset={4}
@@ -1,10 +1,10 @@
import type { Credential } from '../../declarations'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { StatusDot } from '@langgenius/dify-ui/status-dot'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { memo, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Badge from '@/app/components/base/badge'
type CredentialItemProps = {
@@ -88,15 +88,19 @@ const CredentialItem = ({
<Tooltip>
<TooltipTrigger
render={
<ActionButton
<IconButton
aria-label={t(($) => $['operation.edit'], { ns: 'common' })}
disabled={disabled}
onClick={(e) => {
e.stopPropagation()
onEdit?.(credential)
}}
>
<span className="i-ri-equalizer-2-line size-4 text-text-tertiary" />
</ActionButton>
<span
aria-hidden="true"
className="i-ri-equalizer-2-line size-4 text-text-tertiary"
/>
</IconButton>
}
/>
<TooltipContent>{t(($) => $['operation.edit'], { ns: 'common' })}</TooltipContent>
@@ -106,7 +110,8 @@ const CredentialItem = ({
<Tooltip>
<TooltipTrigger
render={
<ActionButton
<IconButton
aria-label={t(($) => $['operation.delete'], { ns: 'common' })}
className="hover:bg-transparent"
onClick={(e) => {
if (disabled || disableDeleteWhenSelected) return
@@ -115,13 +120,14 @@ const CredentialItem = ({
}}
>
<span
aria-hidden="true"
className={cn(
'i-ri-delete-bin-line size-4 text-text-tertiary',
!disableDeleteWhenSelected && 'hover:text-text-destructive',
disableDeleteWhenSelected && 'opacity-50',
)}
/>
</ActionButton>
</IconButton>
}
/>
<TooltipContent>
@@ -16,11 +16,11 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@langgenius/dify-ui/dropdown-menu'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { toast } from '@langgenius/dify-ui/toast'
import { useAtomValue } from 'jotai'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import { workspacePermissionKeysAtom } from '@/context/permission-state'
import {
useCopyWorkspaceRole,
@@ -111,15 +111,15 @@ const RowMenu = ({ roleCategory, role, onView, onEdit }: RowMenuProps) => {
<DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger
render={
<ActionButton
size="m"
<IconButton
size="md"
className="shrink-0 data-popup-open:bg-state-base-hover"
aria-label={t(($) => $['operation.moreActions'], { ns: 'common' })}
/>
>
<span aria-hidden className="i-ri-more-fill h-4 w-4 text-text-tertiary" />
</IconButton>
}
>
<span aria-hidden className="i-ri-more-fill h-4 w-4 text-text-tertiary" />
</DropdownMenuTrigger>
/>
<DropdownMenuContent placement="bottom-end" sideOffset={4} popupClassName="min-w-[160px]">
{hasViewAction && (
<DropdownMenuItem
@@ -3,9 +3,9 @@ import type { PluginPayload } from '../types'
import type { FormSchema } from '@/app/components/base/form/types'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { memo, useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Badge from '@/app/components/base/badge'
import { FormTypeEnum } from '@/app/components/base/form/types'
import { useRenderI18nObject } from '@/hooks/use-i18n'
@@ -100,7 +100,7 @@ const AddOAuthButton = ({
{redirect_uri && (
<div className="flex w-full py-0.5 system-sm-medium">
<div className="w-0 grow wrap-break-word break-all">{redirect_uri}</div>
<ActionButton
<IconButton
aria-label={t(($) => $['operation.copy'], { ns: 'common' })}
className="shrink-0"
onClick={() => {
@@ -108,7 +108,7 @@ const AddOAuthButton = ({
}}
>
<span aria-hidden className="i-ri-clipboard-line size-4" />
</ActionButton>
</IconButton>
</div>
)}
</div>
@@ -1,13 +1,13 @@
import type { Credential } from '../types'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { StatusDot } from '@langgenius/dify-ui/status-dot'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiInformationLine } from '@remixicon/react'
import { useSuspenseQuery } from '@tanstack/react-query'
import { memo, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Badge from '@/app/components/base/badge'
import Input from '@/app/components/base/input'
import { userProfileQueryOptions } from '@/features/account-profile/client'
@@ -179,7 +179,8 @@ const Item = ({
<Tooltip>
<TooltipTrigger
render={
<ActionButton
<IconButton
aria-label={t(($) => $['operation.rename'], { ns: 'common' })}
disabled={disabled || !canManageCredential}
onClick={(e) => {
e.stopPropagation()
@@ -187,8 +188,11 @@ const Item = ({
setRenameValue(credential.name)
}}
>
<span className="i-ri-edit-line size-4 text-text-tertiary" />
</ActionButton>
<span
aria-hidden="true"
className="i-ri-edit-line size-4 text-text-tertiary"
/>
</IconButton>
}
/>
<TooltipContent>{t(($) => $['operation.rename'], { ns: 'common' })}</TooltipContent>
@@ -202,7 +206,8 @@ const Item = ({
<Tooltip>
<TooltipTrigger
render={
<ActionButton
<IconButton
aria-label={t(($) => $['operation.edit'], { ns: 'common' })}
disabled={disabled || !canManageCredential}
onClick={(e) => {
e.stopPropagation()
@@ -213,8 +218,11 @@ const Item = ({
})
}}
>
<span className="i-ri-equalizer-2-line size-4 text-text-tertiary" />
</ActionButton>
<span
aria-hidden="true"
className="i-ri-equalizer-2-line size-4 text-text-tertiary"
/>
</IconButton>
}
/>
<TooltipContent>{t(($) => $['operation.edit'], { ns: 'common' })}</TooltipContent>
@@ -224,7 +232,8 @@ const Item = ({
<Tooltip>
<TooltipTrigger
render={
<ActionButton
<IconButton
aria-label={t(($) => $['operation.delete'], { ns: 'common' })}
className="hover:bg-transparent"
disabled={disabled || !canManageCredential}
onClick={(e) => {
@@ -232,8 +241,11 @@ const Item = ({
onDelete?.(credential.id)
}}
>
<span className="i-ri-delete-bin-line size-4 text-text-tertiary hover:text-text-destructive" />
</ActionButton>
<span
aria-hidden="true"
className="i-ri-delete-bin-line size-4 text-text-tertiary hover:text-text-destructive"
/>
</IconButton>
}
/>
<TooltipContent>{t(($) => $['operation.delete'], { ns: 'common' })}</TooltipContent>
@@ -1,5 +1,6 @@
import type { StrategyDetail as StrategyDetailType } from '@/app/components/plugins/types'
import { fireEvent, render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import StrategyDetail from '../strategy-detail'
@@ -116,14 +117,12 @@ describe('StrategyDetail', () => {
})
describe('User Interactions', () => {
it('should call onHide when close button clicked', () => {
it('should call onHide when close button clicked', async () => {
const user = userEvent.setup()
render(<StrategyDetail provider={mockProvider} detail={mockDetail} onHide={mockOnHide} />)
// Find the close button (ActionButton with action-btn class)
const closeButton = screen
.getAllByRole('button')
.find((btn) => btn.classList.contains('action-btn'))
if (closeButton) fireEvent.click(closeButton)
await user.click(screen.getByRole('button', { name: /operation\.close|close/i }))
expect(mockOnHide).toHaveBeenCalledTimes(1)
})
@@ -3,11 +3,11 @@
import type { PluginDetail } from '../../types'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useQuery } from '@tanstack/react-query'
import { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Badge from '@/app/components/base/badge'
import { AuthCategory, PluginAuth } from '@/app/components/plugins/plugin-auth'
import { OperationDropdown } from '@/app/components/plugins/plugin-detail-panel/operation-dropdown'
@@ -310,12 +310,12 @@ const DetailHeader = ({
showCheckVersion={canUpdatePlugin}
showRemove={canDeletePlugin}
/>
<ActionButton
<IconButton
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
onClick={onHide}
>
<span aria-hidden className="i-ri-close-line size-4" />
</ActionButton>
</IconButton>
</div>
)}
</div>
@@ -8,6 +8,7 @@ import {
AlertDialogContent,
AlertDialogTitle,
} from '@langgenius/dify-ui/alert-dialog'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { StatusDot } from '@langgenius/dify-ui/status-dot'
import { Switch } from '@langgenius/dify-ui/switch'
import { toast } from '@langgenius/dify-ui/toast'
@@ -17,8 +18,6 @@ import copy from 'copy-to-clipboard'
import * as React from 'react'
import { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import { CopyCheck } from '@/app/components/base/icons/src/vender/line/files'
import {
addDefaultValue,
toolCredentialToFormSchemas,
@@ -155,19 +154,19 @@ const EndpointCard = ({ pluginDetail, data, handleChange }: Props) => {
<div>{data.name}</div>
</div>
<div className="flex w-0 items-center overflow-hidden opacity-0 group-hover:w-auto group-hover:overflow-visible group-hover:opacity-100 focus-within:w-auto focus-within:overflow-visible focus-within:opacity-100">
<ActionButton
<IconButton
aria-label={t(($) => $['operation.edit'], { ns: 'common' })}
onClick={showEndpointModalConfirm}
>
<span aria-hidden className="i-ri-edit-line size-4" />
</ActionButton>
<ActionButton
</IconButton>
<IconButton
aria-label={t(($) => $['operation.delete'], { ns: 'common' })}
tone="destructive"
onClick={showDeleteConfirm}
className="text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive"
>
<span aria-hidden className="i-ri-delete-bin-line size-4" />
</ActionButton>
</IconButton>
</div>
</div>
{(data.declaration.endpoints ?? [])
@@ -182,20 +181,23 @@ const EndpointCard = ({ pluginDetail, data, handleChange }: Props) => {
<Tooltip>
<TooltipTrigger
render={
<ActionButton
<IconButton
aria-label={copyLabel}
className="ml-2 hidden shrink-0 group-hover/item:flex"
onClick={() => handleCopy(`${data.url}${endpoint.path}`)}
>
{isCopied ? (
<CopyCheck aria-hidden className="size-3.5 text-text-tertiary" />
<span
aria-hidden
className="i-custom-vender-line-files-copy-check size-3.5 text-text-tertiary"
/>
) : (
<span
aria-hidden
className="i-ri-clipboard-line size-3.5 text-text-tertiary"
/>
)}
</ActionButton>
</IconButton>
}
/>
<TooltipContent placement="top">{copyLabel}</TooltipContent>
@@ -1,12 +1,12 @@
import type { PluginDetail } from '@/app/components/plugins/types'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { toast } from '@langgenius/dify-ui/toast'
import { useBoolean } from 'ahooks'
import * as React from 'react'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import { toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
import { useDocLink } from '@/context/i18n'
import {
@@ -110,12 +110,12 @@ const EndpointListContent = ({ declaration, detail }: EndpointListContentProps)
</PopoverContent>
</Popover>
</div>
<ActionButton
<IconButton
aria-label={t(($) => $['detailPanel.endpointModalTitle'], { ns: 'plugin' })}
onClick={showEndpointModal}
>
<span aria-hidden className="i-ri-add-line size-4" />
</ActionButton>
</IconButton>
</div>
{data.endpoints.length === 0 && (
<div className="mb-1 flex justify-center rounded-[10px] bg-background-section p-3 system-xs-regular text-text-tertiary">
@@ -12,11 +12,11 @@ import {
DrawerPortal,
DrawerViewport,
} from '@langgenius/dify-ui/drawer'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { toast } from '@langgenius/dify-ui/toast'
import { RiArrowRightUpLine, RiCloseLine } from '@remixicon/react'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form'
import { useRenderI18nObject } from '@/hooks/use-i18n'
import { ReadmeEntrance } from '../readme-panel/entrance'
@@ -104,9 +104,12 @@ const EndpointModal: FC<Props> = ({
<div className="system-xl-semibold text-text-primary">
{t(($) => $['detailPanel.endpointModalTitle'], { ns: 'plugin' })}
</div>
<ActionButton onClick={onCancel}>
<RiCloseLine className="size-4" />
</ActionButton>
<IconButton
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
onClick={onCancel}
>
<RiCloseLine aria-hidden="true" className="size-4" />
</IconButton>
</div>
<div className="mt-0.5 system-xs-regular text-text-tertiary">
{t(($) => $['detailPanel.endpointModalDesc'], { ns: 'plugin' })}
@@ -11,11 +11,11 @@ import {
DrawerPortal,
DrawerViewport,
} from '@langgenius/dify-ui/drawer'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { RiArrowLeftLine, RiCloseLine } from '@remixicon/react'
import * as React from 'react'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Divider from '@/app/components/base/divider'
import Icon from '@/app/components/plugins/card/base/card-icon'
import Description from '@/app/components/plugins/card/base/description'
@@ -87,9 +87,12 @@ const StrategyDetail: FC<Props> = ({ provider, detail, onHide }) => {
{/* header */}
<div className="relative border-b border-divider-subtle p-4 pb-3">
<div className="absolute top-3 right-3">
<ActionButton onClick={onHide}>
<RiCloseLine className="size-4" />
</ActionButton>
<IconButton
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
onClick={onHide}
>
<RiCloseLine aria-hidden="true" className="size-4" />
</IconButton>
</div>
<div
className="mb-2 flex cursor-pointer items-center gap-1 system-xs-semibold-uppercase text-text-accent-secondary"
@@ -3,10 +3,11 @@ import type {
TriggerProviderApiEntity,
TriggerSubscription,
} from '@/app/components/workflow/block-selector/types'
import { fireEvent, render, screen, within } from '@testing-library/react'
import { render, screen, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { SupportedCreationMethods } from '@/app/components/plugins/types'
import { CreateSubscriptionButton } from '../index'
import { CreateButtonType } from '../types'
const toastMocks = vi.hoisted(() => ({
success: vi.fn(),
@@ -198,13 +199,19 @@ describe('CreateSubscriptionButton', () => {
})
it('does not create more than ten subscriptions', async () => {
const user = userEvent.setup()
mockSubscriptions.push(
...Array.from({ length: 10 }, (_, index) => ({ id: `${index}` }) as TriggerSubscription),
)
render(<CreateSubscriptionButton />)
render(<CreateSubscriptionButton buttonType={CreateButtonType.ICON_BUTTON} />)
fireEvent.click(screen.getByRole('button', { name: /subscription\.createButton\.manual/ }))
const createButton = screen.getByLabelText(/subscription\.createButton\.manual/)
await user.click(createButton)
createButton.focus()
expect(createButton).toHaveAttribute('aria-disabled', 'true')
expect(createButton).toHaveFocus()
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
})
})
@@ -0,0 +1,13 @@
@reference "../../../../../styles/globals.css";
.highlightedIconButton {
@apply border-[0.5px] border-components-button-secondary-border-hover bg-components-button-secondary-bg-hover text-components-button-secondary-accent-text shadow-xs;
&:hover {
@apply border-components-button-secondary-border-disabled bg-components-button-secondary-bg-disabled text-components-button-secondary-accent-text-disabled;
}
&[data-disabled]:hover {
@apply bg-components-button-secondary-bg-disabled text-components-button-secondary-accent-text-disabled;
}
}
@@ -1,6 +1,7 @@
import type { TriggerSubscriptionBuilder } from '@/app/components/workflow/block-selector/types'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import {
Select,
SelectContent,
@@ -13,7 +14,6 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/too
import { useBoolean } from 'ahooks'
import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ActionButton, ActionButtonState } from '@/app/components/base/action-button'
import Badge from '@/app/components/base/badge'
import { Infotip } from '@/app/components/base/infotip'
import { openOAuthPopup } from '@/hooks/use-oauth'
@@ -26,6 +26,7 @@ import { SupportedCreationMethods } from '../../../types'
import { usePluginStore } from '../../store'
import { useSubscriptionList } from '../use-subscription-list'
import { CommonCreateModal } from './common-modal'
import styles from './index.module.css'
import { OAuthClientSettingsModal } from './oauth-client'
import { CreateButtonType, DEFAULT_METHOD } from './types'
@@ -131,14 +132,14 @@ export const CreateSubscriptionButton = ({
<Tooltip>
<TooltipTrigger
render={
<ActionButton
<IconButton
aria-label={t(($) => $['subscription.addType.options.oauth.clientSettings'], {
ns: 'pluginTrigger',
})}
onClick={onClickClientSettings}
>
<span aria-hidden className="i-ri-equalizer-2-line size-4 text-text-tertiary" />
</ActionButton>
</IconButton>
}
/>
<TooltipContent>
@@ -315,22 +316,21 @@ export const CreateSubscriptionButton = ({
<TooltipTrigger
disabled={!(supportedMethods?.length === 1 || subscriptionCount >= MAX_COUNT)}
render={
<ActionButton
<IconButton
aria-label={buttonTextMap[methodType!]}
onClick={onClickCreate}
disabled={subscriptionCount >= MAX_COUNT}
focusableWhenDisabled
className={cn(
'float-right',
shape === 'circle' &&
'rounded-full! border-[0.5px] border-components-button-secondary-border-hover bg-components-button-secondary-bg-hover text-components-button-secondary-accent-text shadow-xs hover:border-components-button-secondary-border-disabled hover:bg-components-button-secondary-bg-disabled hover:text-components-button-secondary-accent-text-disabled',
subscriptionCount >= MAX_COUNT &&
shape === 'square' &&
'data-disabled:hover:bg-state-base-hover',
shape === 'circle' && cn('rounded-full', styles.highlightedIconButton),
)}
state={
subscriptionCount >= MAX_COUNT
? ActionButtonState.Disabled
: ActionButtonState.Default
}
>
<span aria-hidden className="i-ri-add-line size-4" />
</ActionButton>
</IconButton>
}
/>
<TooltipContent>
@@ -1,11 +1,10 @@
'use client'
import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types'
import { cn } from '@langgenius/dify-ui/cn'
import { RiCheckLine, RiDeleteBinLine, RiWebhookLine } from '@remixicon/react'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import * as React from 'react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import { Infotip } from '@/app/components/base/infotip'
import { CreateSubscriptionButton } from './create'
import { CreateButtonType } from './create/types'
@@ -61,11 +60,15 @@ export const SubscriptionSelectorView: React.FC<SubscriptionSelectorProps> = ({
>
<div className="flex items-center">
{selectedId === subscription.id && (
<RiCheckLine className="mr-2 size-4 shrink-0 text-text-accent" />
<span
aria-hidden
className="mr-2 i-ri-check-line size-4 shrink-0 text-text-accent"
/>
)}
<RiWebhookLine
<span
aria-hidden
className={cn(
'mr-1.5 size-3.5 text-text-secondary',
'mr-1.5 i-ri-webhook-line size-3.5 text-text-secondary',
selectedId !== subscription.id && 'ml-6',
)}
/>
@@ -74,15 +77,17 @@ export const SubscriptionSelectorView: React.FC<SubscriptionSelectorProps> = ({
</span>
</div>
</button>
<ActionButton
<IconButton
aria-label={t(($) => $['operation.delete'], { ns: 'common' })}
tone="destructive"
onClick={(e) => {
e.stopPropagation()
setDeletedSubscription(subscription)
}}
className="subscription-delete-btn hidden shrink-0 text-text-tertiary group-hover:flex hover:bg-state-destructive-hover hover:text-text-destructive"
className="subscription-delete-btn hidden shrink-0 group-hover:flex"
>
<RiDeleteBinLine className="size-4" />
</ActionButton>
<span aria-hidden="true" className="i-ri-delete-bin-line size-4" />
</IconButton>
</div>
))}
</div>
@@ -2,11 +2,10 @@
import type { PluginDetail } from '@/app/components/plugins/types'
import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { RiDeleteBinLine, RiEditLine, RiWebhookLine } from '@remixicon/react'
import { useBoolean } from 'ahooks'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import { DeleteConfirm } from './delete-confirm'
import { EditModal } from './edit'
@@ -33,23 +32,26 @@ const SubscriptionCard = ({ data, pluginDetail }: Props) => {
>
<div className="flex items-center justify-between">
<div className="flex h-6 items-center gap-1">
<RiWebhookLine className="size-4 text-text-secondary" />
<span aria-hidden className="i-ri-webhook-line size-4 text-text-secondary" />
<span className="system-md-semibold text-text-secondary">{data.name}</span>
</div>
<div className="hidden items-center gap-1 group-hover:flex">
<ActionButton
<IconButton
aria-label={t(($) => $['operation.edit'], { ns: 'common' })}
onClick={showEditModal}
className="transition-colors hover:bg-state-base-hover"
className="transition-colors"
>
<RiEditLine className="size-4" />
</ActionButton>
<ActionButton
<span aria-hidden="true" className="i-ri-edit-line size-4" />
</IconButton>
<IconButton
aria-label={t(($) => $['operation.delete'], { ns: 'common' })}
tone="destructive"
onClick={showDeleteModal}
className="subscription-delete-btn transition-colors hover:bg-state-destructive-hover hover:text-text-destructive"
className="subscription-delete-btn transition-colors"
>
<RiDeleteBinLine className="size-4" />
</ActionButton>
<span aria-hidden="true" className="i-ri-delete-bin-line size-4" />
</IconButton>
</div>
</div>
@@ -1,6 +1,7 @@
import type { TriggerEvent } from '@/app/components/plugins/types'
import type { TriggerProviderApiEntity } from '@/app/components/workflow/block-selector/types'
import { fireEvent, render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { EventDetailDrawer } from '../event-detail-drawer'
@@ -188,7 +189,9 @@ describe('EventDetailDrawer', () => {
})
describe('User Interactions', () => {
it('should call onClose when close button clicked', () => {
it('should call onClose when close button clicked', async () => {
const user = userEvent.setup()
render(
<EventDetailDrawer
eventInfo={mockEventInfo}
@@ -197,11 +200,7 @@ describe('EventDetailDrawer', () => {
/>,
)
// Find the close button (ActionButton with action-btn class)
const closeButton = screen
.getAllByRole('button')
.find((btn) => btn.classList.contains('action-btn'))
if (closeButton) fireEvent.click(closeButton)
await user.click(screen.getByRole('button', { name: /operation\.close|close/i }))
expect(mockOnClose).toHaveBeenCalledTimes(1)
})
@@ -12,9 +12,9 @@ import {
DrawerPortal,
DrawerViewport,
} from '@langgenius/dify-ui/drawer'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { RiArrowLeftLine, RiCloseLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Divider from '@/app/components/base/divider'
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
import Icon from '@/app/components/plugins/card/base/card-icon'
@@ -99,9 +99,12 @@ export const EventDetailDrawer: FC<EventDetailDrawerProps> = (props) => {
<DrawerContent className="flex min-h-0 flex-1 flex-col p-0 pb-0">
<div className="relative border-b border-divider-subtle p-4 pb-3">
<div className="absolute top-3 right-3">
<ActionButton onClick={onClose}>
<RiCloseLine className="size-4" />
</ActionButton>
<IconButton
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
onClick={onClose}
>
<RiCloseLine aria-hidden="true" className="size-4" />
</IconButton>
</div>
<div
className="mb-2 flex cursor-pointer items-center gap-1 system-xs-semibold-uppercase text-text-accent-secondary"
@@ -10,6 +10,7 @@ import {
AlertDialogContent,
AlertDialogTitle,
} from '@langgenius/dify-ui/alert-dialog'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { toast } from '@langgenius/dify-ui/toast'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useBoolean } from 'ahooks'
@@ -19,7 +20,6 @@ import { useTranslation } from 'react-i18next'
import { useModalContext } from '@/context/modal-context'
import { uninstallPlugin } from '@/service/plugins'
import { useInvalidateInstalledPluginList } from '@/service/use-plugins'
import ActionButton from '../../base/action-button'
import { checkForUpdates, fetchReleases } from '../install-plugin/hooks'
import PluginInfo from '../plugin-page/plugin-info'
import { PluginSource } from '../types'
@@ -120,12 +120,12 @@ const Action: FC<Props> = ({
<Tooltip>
<TooltipTrigger
render={
<ActionButton
<IconButton
aria-label={t(($) => $[`${i18nPrefix}.checkForUpdates`], { ns: 'plugin' })}
onClick={handleFetchNewVersion}
>
<span aria-hidden className="i-ri-loop-left-line size-4 text-text-tertiary" />
</ActionButton>
</IconButton>
}
/>
<TooltipContent>
@@ -137,12 +137,12 @@ const Action: FC<Props> = ({
<Tooltip>
<TooltipTrigger
render={
<ActionButton
<IconButton
aria-label={t(($) => $[`${i18nPrefix}.pluginInfo`], { ns: 'plugin' })}
onClick={showPluginInfo}
>
<span aria-hidden className="i-ri-information-2-line size-4 text-text-tertiary" />
</ActionButton>
</IconButton>
}
/>
<TooltipContent>
@@ -154,13 +154,13 @@ const Action: FC<Props> = ({
<Tooltip>
<TooltipTrigger
render={
<ActionButton
<IconButton
aria-label={t(($) => $[`${i18nPrefix}.delete`], { ns: 'plugin' })}
className="text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive"
tone="destructive"
onClick={showDeleteConfirm}
>
<span aria-hidden className="i-ri-delete-bin-line size-4" />
</ActionButton>
</IconButton>
}
/>
<TooltipContent>{t(($) => $[`${i18nPrefix}.delete`], { ns: 'plugin' })}</TooltipContent>
@@ -2,12 +2,12 @@
import type { InputVarType } from '@/app/components/workflow/types'
import type { InputVar } from '@/models/pipeline'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { RiDeleteBinLine, RiDraggable, RiEditLine } from '@remixicon/react'
import { useHover } from 'ahooks'
import * as React from 'react'
import { useCallback, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Badge from '@/app/components/base/badge'
import { InputField } from '@/app/components/base/icons/src/vender/pipeline'
import InputVarTypeIcon from '@/app/components/workflow/nodes/_base/components/input-var-type-icon'
@@ -80,12 +80,22 @@ const FieldItem = ({ readonly, payload, index, onClickEdit, onRemove }: FieldIte
</div>
{isHovering && !readonly ? (
<div className="flex shrink-0 items-center gap-x-1">
<ActionButton className="mr-1" onClick={handleOnClickEdit}>
<RiEditLine className="size-4 text-text-tertiary" />
</ActionButton>
<ActionButton onClick={handleRemove}>
<RiDeleteBinLine className="size-4 text-text-tertiary group-hover:text-text-destructive" />
</ActionButton>
<IconButton
aria-label={t(($) => $['operation.edit'], { ns: 'common' })}
className="mr-1"
onClick={handleOnClickEdit}
>
<RiEditLine aria-hidden="true" className="size-4 text-text-tertiary" />
</IconButton>
<IconButton
aria-label={t(($) => $['operation.remove'], { ns: 'common' })}
onClick={handleRemove}
>
<RiDeleteBinLine
aria-hidden="true"
className="size-4 text-text-tertiary group-hover:text-text-destructive"
/>
</IconButton>
</div>
) : (
<div className="flex shrink-0 items-center gap-x-2">
@@ -1,10 +1,10 @@
import type { InputVar } from '@/models/pipeline'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { RiAddLine } from '@remixicon/react'
import * as React from 'react'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import RemoveEffectVarConfirm from '@/app/components/workflow/nodes/_base/components/remove-effect-var-confirm'
import FieldListContainer from './field-list-container'
import { useFieldList } from './hooks'
@@ -55,14 +55,13 @@ const FieldList = ({
<div className="flex flex-col">
<div className={cn('flex items-center gap-x-2 px-4', labelClassName)}>
<div className="grow">{LabelRightContent}</div>
<ActionButton
<IconButton
aria-label={t(($) => $['operation.add'], { ns: 'common' })}
onClick={() => handleOpenInputFieldEditor()}
disabled={readonly}
className={cn(readonly && 'cursor-not-allowed')}
>
<RiAddLine className="size-4 text-text-tertiary" aria-hidden="true" />
</ActionButton>
</IconButton>
</div>
<FieldListContainer
className="flex flex-col gap-y-1 px-4 pb-1"
@@ -12,6 +12,7 @@ import {
} from '@langgenius/dify-ui/alert-dialog'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { StatusDot } from '@langgenius/dify-ui/status-dot'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useBoolean } from 'ahooks'
@@ -19,7 +20,6 @@ import copy from 'copy-to-clipboard'
import * as React from 'react'
import { useCallback, useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Icon from '@/app/components/plugins/card/base/card-icon'
import { useCanManageMCP } from '@/app/components/tools/hooks/use-tool-permissions'
import { openOAuthPopup } from '@/hooks/use-oauth'
@@ -183,12 +183,12 @@ const MCPDetailContent: FC<Props> = ({
onRemove={() => onDelete(detail.id)}
/>
)}
<ActionButton
<IconButton
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
onClick={onHide}
>
<span aria-hidden className="i-ri-close-line size-4" />
</ActionButton>
</IconButton>
</div>
</div>
<div className="mt-5">
@@ -7,9 +7,9 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@langgenius/dify-ui/dropdown-menu'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
type Props = Readonly<{
inCard?: boolean
@@ -25,15 +25,15 @@ const OperationDropdown: FC<Props> = ({ inCard, onOpenChange, onEdit, onRemove }
<DropdownMenu onOpenChange={onOpenChange}>
<DropdownMenuTrigger
render={
<ActionButton
size={inCard ? 'l' : 'm'}
<IconButton
size={inCard ? 'lg' : 'md'}
aria-label={t(($) => $['operation.more'], { ns: 'common' })}
className="data-popup-open:bg-state-base-hover"
/>
>
<span aria-hidden className={cn('i-ri-more-fill size-4', inCard && 'size-5')} />
</IconButton>
}
>
<span aria-hidden className={cn('i-ri-more-fill size-4', inCard && 'size-5')} />
</DropdownMenuTrigger>
/>
<DropdownMenuContent placement="bottom-end" sideOffset={4} popupClassName="w-[160px]">
<DropdownMenuItem onClick={onEdit}>
<span aria-hidden className="i-ri-edit-line size-4 shrink-0 text-text-tertiary" />
@@ -1,12 +1,12 @@
'use client'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Input } from '@langgenius/dify-ui/input'
import { RiAddLine, RiDeleteBinLine } from '@remixicon/react'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { v4 as uuid } from 'uuid'
import ActionButton from '@/app/components/base/action-button'
export type HeaderItem = {
id: string
@@ -103,13 +103,13 @@ const HeadersInput = ({ headersItems, onChange, readonly = false, isMasked = fal
readOnly={readonly}
/>
{!readonly && !!headersItems.length && (
<ActionButton
<IconButton
aria-label={`${t(($) => $['operation.delete'], { ns: 'common' })} ${item.key.trim() || t(($) => $['mcp.modal.headerKey'], { ns: 'tools' })} ${index + 1}`}
onClick={() => handleRemoveItem(index)}
className="mr-2"
>
<RiDeleteBinLine aria-hidden className="size-4 text-text-destructive" />
</ActionButton>
</IconButton>
)}
</div>
</div>
+3 -3
View File
@@ -25,6 +25,7 @@ import {
DrawerPortal,
DrawerViewport,
} from '@langgenius/dify-ui/drawer'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { StatusDot } from '@langgenius/dify-ui/status-dot'
import { toast } from '@langgenius/dify-ui/toast'
import { RiCloseLine } from '@remixicon/react'
@@ -32,7 +33,6 @@ import { useQueryClient } from '@tanstack/react-query'
import * as React from 'react'
import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Loading from '@/app/components/base/loading'
import { ConfigurationMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import Icon from '@/app/components/plugins/card/base/card-icon'
@@ -301,12 +301,12 @@ const ProviderDetail = ({ collection, onHide, onRefreshData }: Props) => {
</div>
</div>
<div className="flex gap-1">
<ActionButton
<IconButton
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
onClick={onHide}
>
<RiCloseLine className="size-4" />
</ActionButton>
</IconButton>
</div>
</div>
</div>
@@ -3,11 +3,11 @@ import type { FC } from 'react'
import type { CodeLanguage } from '../../code/types'
import type { GenRes } from '@/service/debug'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import * as React from 'react'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { GetCodeGeneratorResModal } from '@/app/components/app/configuration/config/code-generator/get-code-generator-res'
import { ActionButton } from '@/app/components/base/action-button'
import { Generator } from '@/app/components/base/icons/src/vender/other'
import { AppModeEnum } from '@/types/app'
import { useHooksStore } from '../../../hooks-store'
@@ -40,13 +40,13 @@ const CodeGenerateBtn: FC<Props> = ({
return (
<div className={cn(className)}>
<ActionButton
<IconButton
aria-label={t(($) => $['operation.automatic'], { ns: 'appDebug' })}
className="hover:bg-[#155EFF]/8"
onClick={() => setShowAutomatic(true)}
>
<Generator aria-hidden className="size-4 text-primary-600" />
</ActionButton>
</IconButton>
{showAutomatic && (
<GetCodeGeneratorResModal
mode={AppModeEnum.CHAT}
@@ -0,0 +1,58 @@
import type { ReactNode } from 'react'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import copy from 'copy-to-clipboard'
import Base from '../base'
vi.mock('copy-to-clipboard', () => ({
default: vi.fn(),
}))
vi.mock(
'@/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap',
() => ({
default: ({ children }: { children: ReactNode }) => <div>{children}</div>,
}),
)
vi.mock('@/app/components/base/file-uploader/file-list-in-log', () => ({
default: () => null,
}))
vi.mock('@/app/components/workflow/nodes/_base/hooks/use-toggle-expend', () => ({
default: () => ({
wrapClassName: '',
wrapStyle: {},
isExpand: false,
setIsExpand: vi.fn(),
editorExpandHeight: 560,
}),
}))
vi.mock('../../code-generator-button', () => ({
default: () => null,
}))
vi.mock('../../toggle-expand-btn', () => ({
default: () => null,
}))
vi.mock('../wrap', () => ({
default: ({ children }: { children: ReactNode }) => <div>{children}</div>,
}))
describe('Base editor', () => {
it('copies the editor value from its named icon command', async () => {
const user = userEvent.setup()
render(
<Base title="Code" value="const answer = 42" isFocus={false}>
<div>Editor</div>
</Base>,
)
await user.click(screen.getByRole('button', { name: 'common.operation.copy' }))
expect(copy).toHaveBeenCalledWith('const answer = 42')
})
})
@@ -4,11 +4,12 @@ import type { CodeLanguage } from '../../../code/types'
import type { FileEntity } from '@/app/components/base/file-uploader/types'
import type { Node, NodeOutPutVar } from '@/app/components/workflow/types'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import copy from 'copy-to-clipboard'
import * as React from 'react'
import { useCallback, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import PromptEditorHeightResizeWrap from '@/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap'
import ActionButton from '@/app/components/base/action-button'
import FileListInLog from '@/app/components/base/file-uploader/file-list-in-log'
import { Copy, CopyCheck } from '@/app/components/base/icons/src/vender/line/files'
import useToggleExpend from '@/app/components/workflow/nodes/_base/hooks/use-toggle-expend'
@@ -58,6 +59,7 @@ const Base: FC<Props> = ({
tip,
footer,
}) => {
const { t } = useTranslation()
const ref = useRef<HTMLDivElement>(null)
const { wrapClassName, wrapStyle, isExpand, setIsExpand, editorExpandHeight } = useToggleExpend({
ref,
@@ -109,13 +111,17 @@ const Base: FC<Props> = ({
/>
</div>
)}
<ActionButton className="ml-1" onClick={handleCopy}>
<IconButton
aria-label={t(($) => $['operation.copy'], { ns: 'common' })}
className="ml-1"
onClick={handleCopy}
>
{!isCopied ? (
<Copy className="size-4 cursor-pointer" />
<Copy aria-hidden="true" className="size-4 cursor-pointer" />
) : (
<CopyCheck className="size-4" />
<CopyCheck aria-hidden="true" className="size-4" />
)}
</ActionButton>
</IconButton>
<div className="ml-1">
<ToggleExpandBtn isExpand={isExpand} onExpandChange={setIsExpand} />
</div>
@@ -2,6 +2,7 @@
import type { FC, ReactNode } from 'react'
import type { ModelConfig, Node, NodeOutPutVar, Variable } from '../../../../types'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { Switch } from '@langgenius/dify-ui/switch'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
@@ -11,7 +12,6 @@ import copy from 'copy-to-clipboard'
import * as React from 'react'
import { useCallback, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import { Copy, CopyCheck } from '@/app/components/base/icons/src/vender/line/files'
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
import { Jinja } from '@/app/components/base/icons/src/vender/workflow'
@@ -265,9 +265,12 @@ const Editor: FC<Props> = ({
<Tooltip>
<TooltipTrigger
render={
<ActionButton onClick={handleInsertVariable}>
<Variable02 className="size-4" />
</ActionButton>
<IconButton
aria-label={t(($) => $['common.insertVarTip'], { ns: 'workflow' })}
onClick={handleInsertVariable}
>
<Variable02 aria-hidden="true" className="size-4" />
</IconButton>
}
/>
<TooltipContent>
@@ -276,18 +279,24 @@ const Editor: FC<Props> = ({
</Tooltip>
)}
{showRemove && (
<ActionButton onClick={onRemove}>
<RiDeleteBinLine className="size-4" />
</ActionButton>
<IconButton
aria-label={t(($) => $['operation.remove'], { ns: 'common' })}
onClick={onRemove}
>
<RiDeleteBinLine aria-hidden="true" className="size-4" />
</IconButton>
)}
{!isCopied ? (
<ActionButton onClick={handleCopy}>
<Copy className="size-4" />
</ActionButton>
<IconButton
aria-label={t(($) => $['operation.copy'], { ns: 'common' })}
onClick={handleCopy}
>
<Copy aria-hidden="true" className="size-4" />
</IconButton>
) : (
<ActionButton>
<CopyCheck className="size-4" />
</ActionButton>
<IconButton aria-label={t(($) => $['operation.copy'], { ns: 'common' })}>
<CopyCheck aria-hidden="true" className="size-4" />
</IconButton>
)}
<ToggleExpandBtn isExpand={isExpand} onExpandChange={setIsExpand} />
</div>
@@ -1,8 +1,8 @@
'use client'
import type { FC } from 'react'
import { RiDeleteBinLine } from '@remixicon/react'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import * as React from 'react'
import ActionButton from '@/app/components/base/action-button'
import { useTranslation } from 'react-i18next'
type Props = Readonly<{
className?: string
@@ -10,14 +10,17 @@ type Props = Readonly<{
}>
const Remove: FC<Props> = ({ onClick }) => {
const { t } = useTranslation()
return (
<ActionButton
size="l"
className="group shrink-0 hover:bg-state-destructive-hover!"
<IconButton
aria-label={t(($) => $['operation.remove'], { ns: 'common' })}
size="lg"
tone="destructive"
className="shrink-0"
onClick={onClick}
>
<RiDeleteBinLine className="size-4 text-text-tertiary group-hover:text-text-destructive" />
</ActionButton>
<span aria-hidden="true" className="i-ri-delete-bin-line size-4" />
</IconButton>
)
}
export default React.memo(Remove)
@@ -2,14 +2,13 @@
import type { FC } from 'react'
import type { AssignerNodeOperation } from '../../types'
import type { ValueSelector, Var } from '@/app/components/workflow/types'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { RiDeleteBinLine } from '@remixicon/react'
import { noop } from 'es-toolkit/function'
import { produce } from 'immer'
import * as React from 'react'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Input from '@/app/components/base/input'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import ListNoDataPlaceholder from '@/app/components/workflow/nodes/_base/components/list-no-data-placeholder'
@@ -261,13 +260,15 @@ const VarList: FC<Props> = ({
/>
)}
</div>
<ActionButton
size="l"
className="group shrink-0 hover:bg-state-destructive-hover!"
<IconButton
aria-label={t(($) => $['operation.remove'], { ns: 'common' })}
size="lg"
tone="destructive"
className="shrink-0"
onClick={handleVarRemove(index)}
>
<RiDeleteBinLine className="size-4 text-text-tertiary group-hover:text-text-destructive" />
</ActionButton>
<span aria-hidden="true" className="i-ri-delete-bin-line size-4" />
</IconButton>
</div>
)
})}
@@ -1,10 +1,10 @@
import type { FC } from 'react'
import type { AssignerNodeType } from './types'
import type { NodePanelProps } from '@/app/components/workflow/types'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { RiAddLine } from '@remixicon/react'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import VarList from './components/var-list'
import { useHandleAddOperationItem } from './hooks'
import useConfig from './use-config'
@@ -38,12 +38,12 @@ const Panel: FC<NodePanelProps<AssignerNodeType>> = ({ id, data }) => {
<div className="flex grow flex-col items-start justify-center system-sm-semibold-uppercase text-text-secondary">
{t(($) => $[`${i18nPrefix}.variables`], { ns: 'workflow' })}
</div>
<ActionButton
<IconButton
aria-label={t(($) => $['operation.add'], { ns: 'common' })}
onClick={handleAddOperation}
>
<RiAddLine aria-hidden="true" className="size-4 shrink-0 text-text-tertiary" />
</ActionButton>
</IconButton>
</div>
<VarList
readonly={readOnly}
@@ -30,15 +30,6 @@ vi.mock('@langgenius/dify-ui/toast', () => ({
},
}))
vi.mock('@/app/components/base/action-button', () => ({
__esModule: true,
default: (props: { children: ReactNode; onClick: () => void }) => (
<button type="button" aria-label="action-button" onClick={props.onClick}>
{props.children}
</button>
),
}))
vi.mock('@/app/components/workflow/store', () => ({
useStore: (selector: (state: { nodePanelWidth: number }) => unknown) => mockUseStore(selector),
}))
@@ -359,7 +350,7 @@ describe('human-input/panel', () => {
await user.click(screen.getByRole('button', { name: 'change-form-inputs' }))
await user.click(screen.getByRole('button', { name: 'rename-form-input' }))
await user.click(screen.getByRole('button', { name: 'remove-form-input' }))
await user.click(screen.getByRole('button', { name: 'action-button' }))
await user.click(screen.getByRole('button', { name: 'common.operation.add' }))
await user.click(screen.getByRole('button', { name: 'change-action-approve' }))
await user.click(screen.getByRole('button', { name: 'delete-action-approve' }))
await user.click(screen.getByRole('button', { name: 'timeout:editable' }))
@@ -417,7 +408,7 @@ describe('human-input/panel', () => {
expect(
screen.queryByRole('button', { name: /workflow\.nodes\.humanInput\.formContent\.preview/ }),
).not.toBeInTheDocument()
expect(screen.queryByRole('button', { name: 'action-button' })).not.toBeInTheDocument()
expect(screen.queryByRole('button', { name: 'common.operation.add' })).not.toBeInTheDocument()
expect(screen.getByRole('button', { name: 'timeout:readonly' })).toBeInTheDocument()
expect(screen.queryByText('form-preview')).not.toBeInTheDocument()
})
@@ -3,20 +3,13 @@ import type { DeliveryMethod, EmailConfig, FormInputItem } from '../../types'
import type { Node, NodeOutPutVar } from '@/app/components/workflow/types'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { StatusDot } from '@langgenius/dify-ui/status-dot'
import { Switch } from '@langgenius/dify-ui/switch'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
RiDeleteBinLine,
RiEqualizer2Line,
RiMailSendFill,
RiRobot2Fill,
RiSendPlane2Line,
} from '@remixicon/react'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
import Badge from '@/app/components/base/badge/index'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { DeliveryMethodType } from '../../types'
@@ -103,12 +96,18 @@ const DeliveryMethodItem: FC<DeliveryMethodItemProps> = ({
<div className="flex items-center gap-1.5">
{method.type === DeliveryMethodType.WebApp && (
<div className="rounded-sm border border-divider-regular bg-components-icon-bg-indigo-solid p-0.5">
<RiRobot2Fill className="size-3.5 text-text-primary-on-surface" />
<span
aria-hidden
className="i-ri-robot-2-fill size-3.5 text-text-primary-on-surface"
/>
</div>
)}
{method.type === DeliveryMethodType.Email && (
<div className="rounded-sm border border-divider-regular bg-components-icon-bg-blue-solid p-0.5">
<RiMailSendFill className="size-3.5 text-text-primary-on-surface" />
<span
aria-hidden
className="i-ri-mail-send-fill size-3.5 text-text-primary-on-surface"
/>
</div>
)}
<div className="system-xs-medium text-text-secondary capitalize">{method.type}</div>
@@ -127,12 +126,12 @@ const DeliveryMethodItem: FC<DeliveryMethodItemProps> = ({
<Tooltip>
<TooltipTrigger
render={
<ActionButton
<IconButton
aria-label={emailSenderTooltipContent}
onClick={() => setShowTestEmailModal(true)}
>
<RiSendPlane2Line className="size-4" />
</ActionButton>
<span aria-hidden className="i-ri-send-plane-2-line size-4" />
</IconButton>
}
/>
<TooltipContent>{emailSenderTooltipContent}</TooltipContent>
@@ -140,12 +139,12 @@ const DeliveryMethodItem: FC<DeliveryMethodItemProps> = ({
<Tooltip>
<TooltipTrigger
render={
<ActionButton
<IconButton
aria-label={configureLabel}
onClick={() => setShowEmailModal(true)}
>
<RiEqualizer2Line className="size-4" />
</ActionButton>
<span aria-hidden className="i-ri-equalizer-2-line size-4" />
</IconButton>
}
/>
<TooltipContent>{configureLabel}</TooltipContent>
@@ -155,15 +154,15 @@ const DeliveryMethodItem: FC<DeliveryMethodItemProps> = ({
<Tooltip>
<TooltipTrigger
render={
<ActionButton
<IconButton
aria-label={removeLabel}
state={isHovering ? ActionButtonState.Destructive : ActionButtonState.Default}
tone="destructive"
onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)}
onClick={() => onDelete(method.type)}
>
<RiDeleteBinLine className="size-4" />
</ActionButton>
<span aria-hidden className="i-ri-delete-bin-line size-4" />
</IconButton>
}
/>
<TooltipContent>{removeLabel}</TooltipContent>
@@ -2,21 +2,13 @@
import type { FC } from 'react'
import type { DeliveryMethod } from '../../types'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import {
RiAddLine,
RiDiscordFill,
RiLightbulbFlashFill,
RiMailSendFill,
RiRobot2Fill,
} from '@remixicon/react'
import { useSuspenseQuery } from '@tanstack/react-query'
import { memo, useMemo, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { v4 as uuid4 } from 'uuid'
import ActionButton from '@/app/components/base/action-button'
import Badge from '@/app/components/base/badge'
import { Slack, Teams } from '@/app/components/base/icons/src/public/other'
import useWorkflowNodes from '@/app/components/workflow/store/workflow/use-nodes'
import { isTriggerWorkflow } from '@/app/components/workflow/utils/workflow-entry'
import { useProviderContextSelector } from '@/context/provider-context'
@@ -63,12 +55,12 @@ const MethodSelector: FC<MethodSelectorProps> = ({ data, onAdd, onShowUpgradeTip
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger
render={
<ActionButton
<IconButton
aria-label={t(($) => $[`${i18nPrefix}.deliveryMethod.title`], { ns: 'workflow' })}
className="data-popup-open:bg-state-base-hover"
>
<RiAddLine className="size-4" />
</ActionButton>
<span aria-hidden="true" className="i-ri-add-line size-4" />
</IconButton>
}
/>
<PopoverContent
@@ -99,7 +91,10 @@ const MethodSelector: FC<MethodSelectorProps> = ({ data, onAdd, onShowUpgradeTip
webAppDeliveryInfo.disabled && 'opacity-50',
)}
>
<RiRobot2Fill className="size-4 text-text-primary-on-surface" />
<span
aria-hidden
className="i-ri-robot-2-fill size-4 text-text-primary-on-surface"
/>
</div>
<div className={cn('p-1', webAppDeliveryInfo.disabled && 'opacity-50')}>
<div className="mb-0.5 truncate system-sm-medium text-text-primary">
@@ -150,7 +145,10 @@ const MethodSelector: FC<MethodSelectorProps> = ({ data, onAdd, onShowUpgradeTip
emailDeliveryInfo.added && 'opacity-50',
)}
>
<RiMailSendFill className="size-4 text-text-primary-on-surface" />
<span
aria-hidden
className="i-ri-mail-send-fill size-4 text-text-primary-on-surface"
/>
</div>
<div className={cn('p-1', emailDeliveryInfo.added && 'opacity-50')}>
<div className="mb-0.5 truncate system-sm-medium text-text-primary">
@@ -183,7 +181,10 @@ const MethodSelector: FC<MethodSelectorProps> = ({ data, onAdd, onShowUpgradeTip
'opacity-50',
)}
>
<Slack className="size-4 text-text-primary-on-surface" />
<span
aria-hidden
className="i-custom-public-other-slack size-4 text-text-primary-on-surface"
/>
</div>
<div className={cn('p-1', 'opacity-50')}>
<div className="mb-0.5 truncate system-sm-medium text-text-primary">
@@ -214,7 +215,10 @@ const MethodSelector: FC<MethodSelectorProps> = ({ data, onAdd, onShowUpgradeTip
'opacity-50',
)}
>
<Teams className="size-4 text-text-primary-on-surface" />
<span
aria-hidden
className="i-custom-public-other-teams size-4 text-text-primary-on-surface"
/>
</div>
<div className={cn('p-1', 'opacity-50')}>
<div className="mb-0.5 truncate system-sm-medium text-text-primary">
@@ -245,7 +249,10 @@ const MethodSelector: FC<MethodSelectorProps> = ({ data, onAdd, onShowUpgradeTip
'opacity-50',
)}
>
<RiDiscordFill className="size-5 text-text-primary-on-surface" />
<span
aria-hidden
className="i-ri-discord-fill size-5 text-text-primary-on-surface"
/>
</div>
<div className={cn('p-1', 'opacity-50')}>
<div className="mb-0.5 truncate system-sm-medium text-text-primary">
@@ -273,7 +280,10 @@ const MethodSelector: FC<MethodSelectorProps> = ({ data, onAdd, onShowUpgradeTip
'rounded-sm border border-divider-regular bg-components-icon-bg-midnight-solid p-1',
)}
>
<RiLightbulbFlashFill className="size-4 text-text-primary-on-surface" />
<span
aria-hidden
className="i-ri-lightbulb-flash-fill size-4 text-text-primary-on-surface"
/>
</div>
<div className="system-sm-regular text-text-secondary">
<div>
@@ -2,9 +2,9 @@
import type { FC } from 'react'
import type { FormInputItem, UserAction } from '../types'
import { Button } from '@langgenius/dify-ui/button'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Badge from '@/app/components/base/badge'
import { getButtonStyle } from '@/app/components/base/chat/chat/answer/human-input-content/utils'
import { Markdown } from '@/app/components/base/markdown'
@@ -69,12 +69,9 @@ const FormContentPreview: FC<FormContentPreviewProps> = ({
<Badge uppercase className="border-text-accent-secondary text-text-accent-secondary">
{t(($) => $[`${i18nPrefix}.formContent.preview`], { ns: 'workflow' })}
</Badge>
<ActionButton
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
onClick={onClose}
>
<IconButton aria-label={t(($) => $['operation.close'], { ns: 'common' })} onClick={onClose}>
<span aria-hidden className="i-ri-close-line size-5 text-text-tertiary" />
</ActionButton>
</IconButton>
</div>
<div className="max-h-[calc(100vh-167px)] overflow-y-auto px-4">
<Markdown
@@ -3,6 +3,7 @@ import type { FormInputItem, HumanInputNodeType } from './types'
import type { NodePanelProps, Var } from '@/app/components/workflow/types'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { toast } from '@langgenius/dify-ui/toast'
import {
RiAddLine,
@@ -15,7 +16,6 @@ import copy from 'copy-to-clipboard'
import * as React from 'react'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Divider from '@/app/components/base/divider'
import { Infotip } from '@/app/components/base/infotip'
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
@@ -201,9 +201,12 @@ const Panel: FC<NodePanelProps<HumanInputNodeType>> = ({ id, data }) => {
</div>
{!readOnly && (
<div className="flex items-center px-1">
<ActionButton onClick={onAddUseAction}>
<RiAddLine className="size-4" />
</ActionButton>
<IconButton
aria-label={t(($) => $['operation.add'], { ns: 'common' })}
onClick={onAddUseAction}
>
<RiAddLine aria-hidden="true" className="size-4" />
</IconButton>
</div>
)}
</div>
@@ -10,12 +10,11 @@ import {
DrawerPortal,
DrawerViewport,
} from '@langgenius/dify-ui/drawer'
import { RiDeleteBinLine, RiEditLine } from '@remixicon/react'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import * as React from 'react'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import SettingsModal from '@/app/components/app/configuration/dataset-config/settings-modal'
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
import AppIcon from '@/app/components/base/app-icon'
import Badge from '@/app/components/base/badge'
import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
@@ -98,27 +97,25 @@ const DatasetItem: FC<Props> = ({
{!readonly && (
<div className="ml-2 hidden shrink-0 items-center space-x-1 group-hover/dataset-item:flex">
{editable && (
<ActionButton
<IconButton
aria-label={t(($) => $['operation.edit'], { ns: 'common' })}
onClick={(e) => {
e.stopPropagation()
setIsShowSettingsModal(true)
}}
>
<RiEditLine className="size-4 shrink-0 text-text-tertiary" />
</ActionButton>
<span aria-hidden className="i-ri-edit-line size-4 shrink-0 text-text-tertiary" />
</IconButton>
)}
<ActionButton
<IconButton
aria-label={t(($) => $['operation.remove'], { ns: 'common' })}
onClick={handleRemove}
state={isDeleteHovered ? ActionButtonState.Destructive : ActionButtonState.Default}
tone="destructive"
onMouseEnter={() => setIsDeleteHovered(true)}
onMouseLeave={() => setIsDeleteHovered(false)}
>
<RiDeleteBinLine
className={`size-4 shrink-0 ${isDeleteHovered ? 'text-text-destructive' : 'text-text-tertiary'}`}
/>
</ActionButton>
<span aria-hidden className="i-ri-delete-bin-line size-4 shrink-0" />
</IconButton>
</div>
)}
{payload.is_multimodal && (
@@ -3,11 +3,11 @@ import type { FC } from 'react'
import type { ModelConfig } from '@/app/components/workflow/types'
import type { GenRes } from '@/service/debug'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import * as React from 'react'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import GetAutomaticResModal from '@/app/components/app/configuration/config/automatic/get-automatic-res'
import { ActionButton } from '@/app/components/base/action-button'
import { Generator } from '@/app/components/base/icons/src/vender/other'
import { AppModeEnum } from '@/types/app'
import { useHooksStore } from '../../../hooks-store'
@@ -40,13 +40,13 @@ const PromptGeneratorBtn: FC<Props> = ({
const configsMap = useHooksStore((s) => s.configsMap)
return (
<div className={cn(className)}>
<ActionButton
<IconButton
aria-label={t(($) => $['operation.automatic'], { ns: 'appDebug' })}
className="hover:bg-[#155EFF]/8"
onClick={() => setShowAutomatic(true)}
>
<Generator aria-hidden className="size-4 text-primary-600" />
</ActionButton>
</IconButton>
{showAutomatic && (
<GetAutomaticResModal
mode={AppModeEnum.CHAT}
@@ -0,0 +1,42 @@
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { ValueType, VarType } from '@/app/components/workflow/types'
import Item from '../item'
vi.mock('../form-item', () => ({
default: () => <div />,
}))
vi.mock('../input-mode-selec', () => ({
default: () => <div />,
}))
vi.mock('../variable-type-select', () => ({
default: () => <div />,
}))
describe('Loop variable item', () => {
it('removes the current variable from its named icon command', async () => {
const user = userEvent.setup()
const handleRemoveLoopVariable = vi.fn()
render(
<Item
nodeId="loop-node"
item={{
id: 'loop-variable',
label: 'item',
var_type: VarType.string,
value_type: ValueType.constant,
value: '',
}}
handleRemoveLoopVariable={handleRemoveLoopVariable}
handleUpdateLoopVariable={vi.fn()}
/>,
)
await user.click(screen.getByRole('button', { name: 'common.operation.remove' }))
expect(handleRemoveLoopVariable).toHaveBeenCalledWith('loop-variable')
})
})
@@ -2,11 +2,11 @@ import type {
LoopVariable,
LoopVariablesComponentShape,
} from '@/app/components/workflow/nodes/loop/types'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { toast } from '@langgenius/dify-ui/toast'
import { RiDeleteBinLine } from '@remixicon/react'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Input from '@/app/components/base/input'
import { ValueType, VarType } from '@/app/components/workflow/types'
import { checkKeys, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var'
@@ -99,9 +99,14 @@ const Item = ({ nodeId, item, handleRemoveLoopVariable, handleUpdateLoopVariable
<FormItem nodeId={nodeId} item={item} onChange={handleUpdateItemValue} />
</div>
</div>
<ActionButton className="shrink-0" size="l" onClick={() => handleRemoveLoopVariable(item.id)}>
<RiDeleteBinLine className="size-4 text-text-tertiary" />
</ActionButton>
<IconButton
aria-label={t(($) => $['operation.remove'], { ns: 'common' })}
className="shrink-0"
size="lg"
onClick={() => handleRemoveLoopVariable(item.id)}
>
<RiDeleteBinLine aria-hidden="true" className="size-4 text-text-tertiary" />
</IconButton>
</div>
)
}
@@ -1,11 +1,11 @@
'use client'
import type { FC } from 'react'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { toast } from '@langgenius/dify-ui/toast'
import { produce } from 'immer'
import * as React from 'react'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import VariableTypeSelector from '@/app/components/workflow/panel/chat-variable-panel/components/variable-type-select'
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
@@ -136,13 +136,14 @@ const ObjectValueItem: FC<Props> = ({ index, list, onChange }) => {
/>
{list.length > 1 && !isFocus && (
<div className="absolute top-0.5 right-1 z-10">
<ActionButton
size="m"
className="group hover:bg-state-destructive-hover!"
<IconButton
aria-label={t(($) => $['operation.remove'], { ns: 'common' })}
size="md"
tone="destructive"
onClick={handleItemRemove(index)}
>
<span className="i-ri-delete-bin-line size-4 text-text-tertiary group-hover:text-text-destructive" />
</ActionButton>
<span aria-hidden="true" className="i-ri-delete-bin-line size-4" />
</IconButton>
</div>
)}
</div>
@@ -1,5 +1,6 @@
import type { StartNodeType } from '../../nodes/start/types'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiCloseLine, RiEqualizer2Line } from '@remixicon/react'
import { debounce } from 'es-toolkit/compat'
@@ -97,9 +98,9 @@ const DebugAndPreview = () => {
<Tooltip>
<TooltipTrigger
render={
<ActionButton aria-label={restartLabel} onClick={() => handleRestartChat()}>
<IconButton aria-label={restartLabel} onClick={() => handleRestartChat()}>
<RefreshCcw01 aria-hidden="true" className="size-4" />
</ActionButton>
</IconButton>
}
/>
<TooltipContent>{restartLabel}</TooltipContent>
@@ -0,0 +1,161 @@
import type { currentVarType } from '../panel'
import type { VarInInspect } from '@/types/workflow'
import { screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { VarInInspectType } from '@/types/workflow'
import { renderWorkflowFlowComponent } from '../../__tests__/workflow-test-env'
import { BlockEnum, VarType } from '../../types'
import Right from '../right'
const { mockResetConversationVar, mockResetToLastRunVar } = vi.hoisted(() => ({
mockResetConversationVar: vi.fn(),
mockResetToLastRunVar: vi.fn(),
}))
vi.mock('../../hooks/use-inspect-vars-crud', () => ({
default: () => ({
editInspectVarValue: vi.fn(),
resetConversationVar: mockResetConversationVar,
resetToLastRunVar: mockResetToLastRunVar,
}),
}))
vi.mock('../../hooks/use-nodes-interactions', () => ({
useNodesInteractions: () => ({
handleNodeSelect: vi.fn(),
}),
}))
vi.mock('../../hooks/use-tool-icon', () => ({
useToolIcon: () => '',
}))
vi.mock('../../hooks-store', () => ({
useHooksStore: <T,>(selector: (state: { configsMap?: { flowId: string } }) => T) =>
selector({ configsMap: { flowId: 'flow-1' } }),
}))
vi.mock('../../nodes/_base/hooks/use-node-crud', () => ({
default: () => ({ setInputs: vi.fn() }),
}))
vi.mock('../../nodes/_base/hooks/use-node-info', () => ({
default: () => ({ node: undefined }),
}))
vi.mock('@/context/event-emitter', () => ({
useEventEmitterContextContext: () => ({ eventEmitter: undefined }),
}))
vi.mock('../value-content', () => ({
default: () => <div>Value</div>,
}))
const createVariable = (overrides: Partial<VarInInspect> = {}): VarInInspect => ({
id: 'var-1',
type: VarInInspectType.node,
name: 'result',
description: '',
selector: ['node-1', 'result'],
value_type: VarType.string,
value: 'value',
edited: false,
visible: true,
is_truncated: false,
full_content: {
size_bytes: 0,
download_url: '',
},
...overrides,
})
const createCurrentNodeVar = (overrides: Partial<VarInInspect> = {}): currentVarType => ({
nodeId: 'node-1',
nodeType: BlockEnum.Code,
title: 'Code',
nodeData: {
type: BlockEnum.Code,
title: 'Code',
desc: '',
},
var: createVariable(overrides),
})
const renderRight = (
currentNodeVar: currentVarType,
options: { bottomPanelWidth?: number; handleOpenMenu?: () => void } = {},
) => {
const handleOpenMenu = options.handleOpenMenu ?? vi.fn()
const result = renderWorkflowFlowComponent(
<Right nodeId="node-1" currentNodeVar={currentNodeVar} handleOpenMenu={handleOpenMenu} />,
{
nodes: [],
edges: [],
initialStoreState: {
bottomPanelWidth: options.bottomPanelWidth ?? 560,
},
},
)
return { ...result, handleOpenMenu }
}
describe('VariableInspect Right', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('opens the variable menu from the named narrow-panel command', async () => {
const user = userEvent.setup()
const { handleOpenMenu } = renderRight(createCurrentNodeVar(), { bottomPanelWidth: 400 })
await user.click(screen.getByRole('button', { name: 'workflow.debug.variableInspect.title' }))
expect(handleOpenMenu).toHaveBeenCalledTimes(1)
})
it('exposes the full-content download as a named link', () => {
renderRight(
createCurrentNodeVar({
is_truncated: true,
full_content: {
size_bytes: 1024,
download_url: 'https://example.com/result.txt',
},
}),
)
expect(
screen.getByRole('link', { name: 'workflow.debug.variableInspect.exportToolTip' }),
).toHaveAttribute('href', 'https://example.com/result.txt')
})
it('resets an edited node variable from its named command', async () => {
const user = userEvent.setup()
renderRight(createCurrentNodeVar({ edited: true }))
await user.click(screen.getByRole('button', { name: 'workflow.debug.variableInspect.reset' }))
expect(mockResetToLastRunVar).toHaveBeenCalledWith('node-1', 'var-1')
})
it('resets an edited conversation variable from its named command', async () => {
const user = userEvent.setup()
renderRight(
createCurrentNodeVar({
type: VarInInspectType.conversation,
edited: true,
}),
)
await user.click(
screen.getByRole('button', {
name: 'workflow.debug.variableInspect.resetConversationVar',
}),
)
expect(mockResetConversationVar).toHaveBeenCalledWith('var-1')
})
})
@@ -1,6 +1,8 @@
import type { currentVarType } from './panel'
import type { NodeWithVar, VarInInspect } from '@/types/workflow'
import { cn } from '@langgenius/dify-ui/cn'
// import { Button } from '@langgenius/dify-ui/button'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
RiArrowRightSLine,
@@ -11,8 +13,6 @@ import {
} from '@remixicon/react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
// import { Button } from '@langgenius/dify-ui/button'
import ActionButton from '@/app/components/base/action-button'
import BlockIcon from '@/app/components/workflow/block-icon'
import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
import { VarInInspectType } from '@/types/workflow'
@@ -146,12 +146,12 @@ const Group = ({
<Tooltip>
<TooltipTrigger
render={
<ActionButton
<IconButton
aria-label={t(($) => $['debug.variableInspect.view'], { ns: 'workflow' })}
onClick={handleView}
>
<RiFileList3Line className="size-4" aria-hidden />
</ActionButton>
</IconButton>
}
/>
<TooltipContent>
@@ -161,12 +161,12 @@ const Group = ({
<Tooltip>
<TooltipTrigger
render={
<ActionButton
<IconButton
aria-label={t(($) => $['debug.variableInspect.clearNode'], { ns: 'workflow' })}
onClick={handleClear}
>
<RiDeleteBinLine className="size-4" aria-hidden />
</ActionButton>
</IconButton>
}
/>
<TooltipContent>
@@ -2,10 +2,10 @@ import type { FC } from 'react'
import type { NodeProps } from '../types'
import type { VarInInspect } from '@/types/workflow'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { RiCloseLine } from '@remixicon/react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { VarInInspectType } from '@/types/workflow'
@@ -175,12 +175,12 @@ const Panel: FC = () => {
<div className="system-sm-semibold-uppercase text-text-primary">
{t(($) => $['debug.variableInspect.title'], { ns: 'workflow' })}
</div>
<ActionButton
<IconButton
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
onClick={() => setShowVariableInspectPanel(false)}
>
<RiCloseLine className="size-4" />
</ActionButton>
</IconButton>
</div>
<div className="grow p-2">
<Listening onStop={handleStopListening} />
@@ -196,12 +196,12 @@ const Panel: FC = () => {
<div className="system-sm-semibold-uppercase text-text-primary">
{t(($) => $['debug.variableInspect.title'], { ns: 'workflow' })}
</div>
<ActionButton
<IconButton
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
onClick={() => setShowVariableInspectPanel(false)}
>
<RiCloseLine className="size-4" />
</ActionButton>
</IconButton>
</div>
<div className="grow p-2">
<Empty />
@@ -1,20 +1,14 @@
import type { currentVarType } from './panel'
import type { GenRes } from '@/service/debug'
import { buttonVariants } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
RiArrowGoBackLine,
RiCloseLine,
RiFileDownloadFill,
RiMenuLine,
RiSparklingFill,
} from '@remixicon/react'
import { useBoolean } from 'ahooks'
import { produce } from 'immer'
import { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import GetAutomaticResModal from '@/app/components/app/configuration/config/automatic/get-automatic-res'
import ActionButton from '@/app/components/base/action-button'
import Badge from '@/app/components/base/badge'
import CopyFeedback from '@/app/components/base/copy-feedback'
import Loading from '@/app/components/base/loading'
@@ -145,9 +139,13 @@ const Right = ({ nodeId, currentNodeVar, handleOpenMenu, isValueFetching }: Prop
{/* header */}
<div className="flex shrink-0 items-center justify-between gap-1 px-2 pt-2">
{bottomPanelWidth < 488 && (
<ActionButton className="shrink-0" onClick={handleOpenMenu}>
<RiMenuLine className="size-4" />
</ActionButton>
<IconButton
aria-label={t(($) => $['debug.variableInspect.title'], { ns: 'workflow' })}
className="shrink-0"
onClick={handleOpenMenu}
>
<span aria-hidden className="i-ri-menu-line size-4" />
</IconButton>
)}
<div className="flex w-0 grow items-center gap-1">
{currentNodeVar?.var && (
@@ -212,7 +210,10 @@ const Right = ({ nodeId, currentNodeVar, handleOpenMenu, isValueFetching }: Prop
className="cursor-pointer rounded-md p-1 hover:bg-state-accent-active"
onClick={handleShowPromptGenerator}
>
<RiSparklingFill className="size-4 text-components-input-border-active-prompt-1" />
<span
aria-hidden
className="i-ri-sparkling-fill size-4 text-components-input-border-active-prompt-1"
/>
</div>
}
/>
@@ -225,15 +226,22 @@ const Right = ({ nodeId, currentNodeVar, handleOpenMenu, isValueFetching }: Prop
<Tooltip>
<TooltipTrigger
render={
<ActionButton>
<a
href={fullContent?.download_url}
target="_blank"
rel="noopener noreferrer"
>
<RiFileDownloadFill className="size-4" />
</a>
</ActionButton>
<a
aria-label={t(($) => $['debug.variableInspect.exportToolTip'], {
ns: 'workflow',
})}
href={fullContent?.download_url}
target="_blank"
rel="noopener noreferrer"
className={buttonVariants({
variant: 'ghost',
size: 'small',
className:
'size-6 rounded-lg p-0 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary',
})}
>
<span aria-hidden className="i-ri-file-download-fill size-4" />
</a>
}
/>
<TooltipContent>
@@ -255,9 +263,14 @@ const Right = ({ nodeId, currentNodeVar, handleOpenMenu, isValueFetching }: Prop
<Tooltip>
<TooltipTrigger
render={
<ActionButton onClick={resetValue}>
<RiArrowGoBackLine className="size-4" />
</ActionButton>
<IconButton
aria-label={t(($) => $['debug.variableInspect.reset'], {
ns: 'workflow',
})}
onClick={resetValue}
>
<span aria-hidden className="i-ri-arrow-go-back-line size-4" />
</IconButton>
}
/>
<TooltipContent>
@@ -271,9 +284,14 @@ const Right = ({ nodeId, currentNodeVar, handleOpenMenu, isValueFetching }: Prop
<Tooltip>
<TooltipTrigger
render={
<ActionButton onClick={handleClear}>
<RiArrowGoBackLine className="size-4" />
</ActionButton>
<IconButton
aria-label={t(($) => $['debug.variableInspect.resetConversationVar'], {
ns: 'workflow',
})}
onClick={handleClear}
>
<span aria-hidden className="i-ri-arrow-go-back-line size-4" />
</IconButton>
}
/>
<TooltipContent>
@@ -288,9 +306,12 @@ const Right = ({ nodeId, currentNodeVar, handleOpenMenu, isValueFetching }: Prop
)}
</>
)}
<ActionButton onClick={handleClose}>
<RiCloseLine className="size-4" />
</ActionButton>
<IconButton
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
onClick={handleClose}
>
<span aria-hidden className="i-ri-close-line size-4" />
</IconButton>
</div>
</div>
{/* content */}