From 8946d7fbf433fef748b22a274bf39ae5066f08ee Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Wed, 29 Jul 2026 11:07:30 +0800 Subject: [PATCH] refactor(web): remove workflow node useBoolean usage (#39729) --- oxlint-suppressions.json | 5 -- .../_base/components/__tests__/field.spec.tsx | 34 +------- .../components/code-generator-button.tsx | 14 ++- .../code-editor/editor-support-vars.tsx | 9 +- .../workflow/nodes/_base/components/field.tsx | 6 +- .../nodes/_base/components/selector.tsx | 16 ++-- .../components/switch-plugin-version.tsx | 12 ++- .../object-child-tree-panel/show/field.tsx | 6 +- .../nodes/_base/hooks/use-output-var-list.ts | 23 ++--- .../nodes/http/hooks/use-key-value-list.ts | 5 +- .../workflow/nodes/http/use-config.ts | 14 ++- .../workflow/nodes/human-input/panel.tsx | 13 ++- .../components/add-dataset.tsx | 13 ++- .../components/dataset-item.tsx | 14 ++- .../llm/components/prompt-generator-btn.tsx | 14 ++- .../nodes/llm/components/structure-output.tsx | 15 ++-- .../extract-parameter/__tests__/list.spec.tsx | 87 ------------------- .../components/extract-parameter/list.tsx | 24 +++-- .../components/extract-parameter/update.tsx | 11 ++- .../nodes/start/components/var-item.tsx | 15 ++-- .../workflow/nodes/start/use-config.ts | 23 ++--- .../tool-form/__tests__/item.spec.tsx | 71 +-------------- .../nodes/tool/components/tool-form/item.tsx | 13 ++- .../workflow/nodes/tool/hooks/use-config.ts | 11 ++- .../components/trigger-form/item.tsx | 13 ++- .../__tests__/use-config.spec.tsx | 24 ----- .../components/var-group-item.tsx | 10 +-- .../nodes/variable-assigner/use-config.ts | 37 ++------ 28 files changed, 149 insertions(+), 403 deletions(-) delete mode 100644 web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/__tests__/list.spec.tsx diff --git a/oxlint-suppressions.json b/oxlint-suppressions.json index 87eb09c5b33..e6fab131196 100644 --- a/oxlint-suppressions.json +++ b/oxlint-suppressions.json @@ -5022,11 +5022,6 @@ "count": 3 } }, - "web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/__tests__/list.spec.tsx": { - "no-unused-vars": { - "count": 1 - } - }, "web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/item.tsx": { "jsx_a11y/click-events-have-key-events": { "count": 2 diff --git a/web/app/components/workflow/nodes/_base/components/__tests__/field.spec.tsx b/web/app/components/workflow/nodes/_base/components/__tests__/field.spec.tsx index a40d03603de..94af7cf6a39 100644 --- a/web/app/components/workflow/nodes/_base/components/__tests__/field.spec.tsx +++ b/web/app/components/workflow/nodes/_base/components/__tests__/field.spec.tsx @@ -2,28 +2,8 @@ import { fireEvent, render, screen } from '@testing-library/react' import Field from '../field' describe('Field', () => { - it('should render subtitle styling, tooltip, operations, warning dot and required marker', () => { - const { container } = render( - operation} - required - warningDot - isSubTitle - />, - ) - - expect(screen.getByText('Knowledge')).toBeInTheDocument() - expect(screen.getByLabelText('tooltip text')).toBeInTheDocument() - expect(screen.getByRole('button', { name: 'operation' })).toBeInTheDocument() - expect(screen.getByText('*')).toBeInTheDocument() - expect(container.querySelector('.system-xs-medium-uppercase')).not.toBeNull() - expect(container.querySelector('.bg-text-warning-secondary')).not.toBeNull() - }) - it('should toggle folded children when supportFold is enabled', () => { - const { container } = render( + render(
folded content
, @@ -33,20 +13,8 @@ describe('Field', () => { fireEvent.click(screen.getByText('Foldable').closest('.cursor-pointer')!) expect(screen.getByText('folded content')).toBeInTheDocument() - expect(container.querySelector('svg')).toHaveStyle({ transform: 'rotate(0deg)' }) fireEvent.click(screen.getByText('Foldable').closest('.cursor-pointer')!) expect(screen.queryByText('folded content')).not.toBeInTheDocument() }) - - it('should render inline children without folding support', () => { - const { container } = render( - -
always visible
-
, - ) - - expect(screen.getByText('always visible')).toBeInTheDocument() - expect(container.firstChild).toHaveClass('flex') - }) }) diff --git a/web/app/components/workflow/nodes/_base/components/code-generator-button.tsx b/web/app/components/workflow/nodes/_base/components/code-generator-button.tsx index e7bda2fc5da..1a935d2242e 100644 --- a/web/app/components/workflow/nodes/_base/components/code-generator-button.tsx +++ b/web/app/components/workflow/nodes/_base/components/code-generator-button.tsx @@ -3,9 +3,8 @@ 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 { useBoolean } from 'ahooks' import * as React from 'react' -import { useCallback } from 'react' +import { useCallback, useState } from 'react' 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' @@ -27,20 +26,19 @@ const CodeGenerateBtn: FC = ({ codeLanguages, onGenerated, }) => { - const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = - useBoolean(false) + const [showAutomatic, setShowAutomatic] = useState(false) const handleAutomaticRes = useCallback( (res: GenRes) => { onGenerated?.(res.modified) - showAutomaticFalse() + setShowAutomatic(false) }, - [onGenerated, showAutomaticFalse], + [onGenerated], ) const configsMap = useHooksStore((s) => s.configsMap) return (
- + setShowAutomatic(true)}> {showAutomatic && ( @@ -48,7 +46,7 @@ const CodeGenerateBtn: FC = ({ mode={AppModeEnum.CHAT} isShow={showAutomatic} codeLanguages={codeLanguages} - onClose={showAutomaticFalse} + onClose={() => setShowAutomatic(false)} onFinished={handleAutomaticRes} flowId={configsMap?.flowId || ''} nodeId={nodeId} diff --git a/web/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx b/web/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx index f2174f15b1b..4606e7f4cbd 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx @@ -3,7 +3,6 @@ import type { FC } from 'react' import type { Props as EditorProps } from '.' import type { NodeOutPutVar, Variable } from '@/app/components/workflow/types' import { cn } from '@langgenius/dify-ui/cn' -import { useBoolean } from 'ahooks' import * as React from 'react' import { useEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' @@ -29,7 +28,7 @@ const CodeEditor: FC = ({ availableVars, varList, onAddVar, ...editorProp const monacoRef = useRef(null) const popupRef = useRef(null) - const [isShowVarPicker, { setTrue: showVarPicker, setFalse: hideVarPicker }] = useBoolean(false) + const [isShowVarPicker, setIsShowVarPicker] = useState(false) const [popupPosition, setPopupPosition] = useState({ x: 0, y: 0 }) @@ -48,9 +47,9 @@ const CodeEditor: FC = ({ availableVars, varList, onAddVar, ...editorProp const popupY = editorRect.top + cursorCoords.top + 20 // Adjust the vertical position as needed setPopupPosition({ x: popupX, y: popupY }) - showVarPicker() + setIsShowVarPicker(true) } else { - hideVarPicker() + setIsShowVarPicker(false) } } @@ -137,7 +136,7 @@ const CodeEditor: FC = ({ availableVars, varList, onAddVar, ...editorProp }, ]) - hideVarPicker() + setIsShowVarPicker(false) } return ( diff --git a/web/app/components/workflow/nodes/_base/components/field.tsx b/web/app/components/workflow/nodes/_base/components/field.tsx index 8325c388936..7c399b6ccaf 100644 --- a/web/app/components/workflow/nodes/_base/components/field.tsx +++ b/web/app/components/workflow/nodes/_base/components/field.tsx @@ -2,8 +2,8 @@ import type { FC, ReactNode } from 'react' import { cn } from '@langgenius/dify-ui/cn' import { RiArrowDownSLine } from '@remixicon/react' -import { useBoolean } from 'ahooks' import * as React from 'react' +import { useState } from 'react' import { Infotip } from '@/app/components/base/infotip' type Props = Readonly<{ @@ -40,7 +40,7 @@ const Field: FC = ({ required, warningDot, }) => { - const [fold, { toggle: toggleFold }] = useBoolean(true) + const [fold, setFold] = useState(true) const tooltipLabel = tooltip ? getTextFromNode(tooltip) || getTextFromNode(title) || 'Help' : undefined @@ -48,7 +48,7 @@ const Field: FC = ({ return (
supportFold && toggleFold()} + onClick={() => supportFold && setFold((isFolded) => !isFolded)} className={cn('flex items-center justify-between', supportFold && 'cursor-pointer')} >
diff --git a/web/app/components/workflow/nodes/_base/components/selector.tsx b/web/app/components/workflow/nodes/_base/components/selector.tsx index f072eff6073..c762462cb0f 100644 --- a/web/app/components/workflow/nodes/_base/components/selector.tsx +++ b/web/app/components/workflow/nodes/_base/components/selector.tsx @@ -1,8 +1,9 @@ 'use client' import type { FC } from 'react' import { cn } from '@langgenius/dify-ui/cn' -import { useBoolean, useClickAway } from 'ahooks' +import { useClickAway } from 'ahooks' import * as React from 'react' +import { useState } from 'react' import { ChevronSelectorVertical } from '@/app/components/base/icons/src/vender/line/arrows' import { Check } from '@/app/components/base/icons/src/vender/line/general' @@ -49,10 +50,10 @@ const TypeSelector: FC = ({ const item = allOptions ? allOptions.find((item) => item.value === value) : list.find((item) => item.value === value) - const [showOption, { setFalse: setHide, toggle: toggleShow }] = useBoolean(false) + const [showOption, setShowOption] = useState(false) const ref = React.useRef(null) useClickAway(() => { - setHide() + setShowOption(false) }, ref) return (
= ({ ref={ref} > {trigger ? ( -
+
setShowOption((isShown) => !isShown)} + className={cn(!readonly && 'cursor-pointer')} + > {trigger}
) : (
setShowOption((isShown) => !isShown)} className={cn( showOption && 'bg-state-base-hover', 'flex h-5 cursor-pointer items-center rounded-md pr-0.5 pl-1 text-xs font-semibold text-text-secondary hover:bg-state-base-hover', @@ -96,7 +100,7 @@ const TypeSelector: FC = ({
{ - setHide() + setShowOption(false) onChange(item.value) }} className={cn( diff --git a/web/app/components/workflow/nodes/_base/components/switch-plugin-version.tsx b/web/app/components/workflow/nodes/_base/components/switch-plugin-version.tsx index 55d0c028753..64857e8cb64 100644 --- a/web/app/components/workflow/nodes/_base/components/switch-plugin-version.tsx +++ b/web/app/components/workflow/nodes/_base/components/switch-plugin-version.tsx @@ -3,7 +3,6 @@ import type { FC, ReactNode } from 'react' import { cn } from '@langgenius/dify-ui/cn' import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' -import { useBoolean } from 'ahooks' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Badge from '@/app/components/base/badge' @@ -29,8 +28,7 @@ export const SwitchPluginVersion: FC = (props) => { const [pluginId] = uniqueIdentifier?.split(':') || [''] const [isShow, setIsShow] = useState(false) - const [isShowUpdateModal, { setTrue: showUpdateModal, setFalse: hideUpdateModal }] = - useBoolean(false) + const [isShowUpdateModal, setIsShowUpdateModal] = useState(false) const [target, setTarget] = useState<{ version: string pluginUniqueIden: string @@ -43,10 +41,10 @@ export const SwitchPluginVersion: FC = (props) => { const pluginDetail = pluginDetails.data?.plugins.at(0) const handleUpdatedFromMarketplace = useCallback(() => { - hideUpdateModal() + setIsShowUpdateModal(false) pluginDetails.refetch() onChange?.(target!.version) - }, [hideUpdateModal, onChange, pluginDetails, target]) + }, [onChange, pluginDetails, target]) const { getIconUrl } = useGetIcon() const icon = pluginDetail?.declaration.icon ? getIconUrl(pluginDetail.declaration.icon) @@ -77,7 +75,7 @@ export const SwitchPluginVersion: FC = (props) => { > {isShowUpdateModal && pluginDetail && ( setIsShowUpdateModal(false)} plugin={pluginManifestToCardPluginProps({ ...pluginDetail.declaration, icon: icon!, @@ -123,7 +121,7 @@ export const SwitchPluginVersion: FC = (props) => { pluginUniqueIden: state.unique_identifier, version: state.version, }) - showUpdateModal() + setIsShowUpdateModal(true) }} trigger={ = ({ name, payload, depth = 1, required, rootClassName }) const isRoot = depth === 1 const hasChildren = payload.type === Type.object && payload.properties const hasEnum = payload.enum && payload.enum.length > 0 - const [fold, { toggle: toggleFold }] = useBoolean(false) + const [fold, setFold] = useState(false) return (
@@ -36,7 +36,7 @@ const Field: FC = ({ name, payload, depth = 1, required, rootClassName }) 'absolute top-[50%] left-[-18px] h-4 w-4 translate-y-[-50%] cursor-pointer bg-components-panel-bg text-text-tertiary', fold && 'rotate-270 text-text-accent', )} - onClick={toggleFold} + onClick={() => setFold((isFolded) => !isFolded)} /> )}
({ onOutputKeyOrdersChange([...outputKeyOrders, newKey]) }, [generateNewKey, inputs, setInputs, onOutputKeyOrdersChange, outputKeyOrders, varKey]) - const [ - isShowRemoveVarConfirm, - { setTrue: showRemoveVarConfirm, setFalse: hideRemoveVarConfirm }, - ] = useBoolean(false) + const [isShowRemoveVarConfirm, setIsShowRemoveVarConfirm] = useState(false) const [removedVar, setRemovedVar] = useState([]) const removeVarInNode = useCallback(() => { const varId = nodesWithInspectVars @@ -129,21 +126,14 @@ function useOutputVarList({ })?.id if (varId) deleteInspectVar(id, varId) removeUsedVarInNodes(removedVar) - hideRemoveVarConfirm() - }, [ - deleteInspectVar, - hideRemoveVarConfirm, - id, - nodesWithInspectVars, - removeUsedVarInNodes, - removedVar, - ]) + setIsShowRemoveVarConfirm(false) + }, [deleteInspectVar, id, nodesWithInspectVars, removeUsedVarInNodes, removedVar]) const handleRemoveVariable = useCallback( (index: number) => { const key = outputKeyOrders[index]! if (isVarUsedInNodes([id, key])) { - showRemoveVarConfirm() + setIsShowRemoveVarConfirm(true) setRemovedVar([id, key]) return } @@ -180,7 +170,6 @@ function useOutputVarList({ onOutputKeyOrdersChange, nodesWithInspectVars, deleteInspectVar, - showRemoveVarConfirm, varKey, ], ) @@ -190,7 +179,7 @@ function useOutputVarList({ handleAddVariable, handleRemoveVariable, isShowRemoveVarConfirm, - hideRemoveVarConfirm, + hideRemoveVarConfirm: () => setIsShowRemoveVarConfirm(false), onRemoveVarConfirm: removeVarInNode, } } diff --git a/web/app/components/workflow/nodes/http/hooks/use-key-value-list.ts b/web/app/components/workflow/nodes/http/hooks/use-key-value-list.ts index 1f270666326..0563f041745 100644 --- a/web/app/components/workflow/nodes/http/hooks/use-key-value-list.ts +++ b/web/app/components/workflow/nodes/http/hooks/use-key-value-list.ts @@ -1,5 +1,4 @@ import type { KeyValue } from '../types' -import { useBoolean } from 'ahooks' import { uniqueId } from 'es-toolkit/compat' import { useCallback, useEffect, useState } from 'react' @@ -63,14 +62,14 @@ const useKeyValueList = (value: string, onChange: (value: string) => void, noFil ]) }, [list, setList]) - const [isKeyValueEdit, { toggle: toggleIsKeyValueEdit }] = useBoolean(true) + const [isKeyValueEdit, setIsKeyValueEdit] = useState(true) return { list: list.length === 0 ? [{ id: uniqueId(UNIQUE_ID_PREFIX), key: '', value: '' }] : list, // no item can not add new item setList, addItem, isKeyValueEdit, - toggleIsKeyValueEdit, + toggleIsKeyValueEdit: () => setIsKeyValueEdit((isEditing) => !isEditing), } } diff --git a/web/app/components/workflow/nodes/http/use-config.ts b/web/app/components/workflow/nodes/http/use-config.ts index 7718f5ead61..dc87c7ce2b4 100644 --- a/web/app/components/workflow/nodes/http/use-config.ts +++ b/web/app/components/workflow/nodes/http/use-config.ts @@ -1,6 +1,5 @@ import type { Var } from '../../types' import type { Authorization, Body, HttpNodeType, Method, Timeout } from './types' -import { useBoolean } from 'ahooks' import { produce } from 'immer' import { useCallback, useEffect, useState } from 'react' import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' @@ -113,8 +112,7 @@ const useConfig = (id: string, payload: HttpNodeType) => { ) // authorization - const [isShowAuthorization, { setTrue: showAuthorization, setFalse: hideAuthorization }] = - useBoolean(false) + const [isShowAuthorization, setIsShowAuthorization] = useState(false) const setAuthorization = useCallback( (authorization: Authorization) => { @@ -141,7 +139,7 @@ const useConfig = (id: string, payload: HttpNodeType) => { }, []) // curl import panel - const [isShowCurlPanel, { setTrue: showCurlPanel, setFalse: hideCurlPanel }] = useBoolean(false) + const [isShowCurlPanel, setIsShowCurlPanel] = useState(false) const handleCurlImport = useCallback( (newNode: HttpNodeType) => { @@ -194,14 +192,14 @@ const useConfig = (id: string, payload: HttpNodeType) => { handleSSLVerifyChange, // authorization isShowAuthorization, - showAuthorization, - hideAuthorization, + showAuthorization: () => setIsShowAuthorization(true), + hideAuthorization: () => setIsShowAuthorization(false), setAuthorization, setTimeout, // curl import isShowCurlPanel, - showCurlPanel, - hideCurlPanel, + showCurlPanel: () => setIsShowCurlPanel(true), + hideCurlPanel: () => setIsShowCurlPanel(false), handleCurlImport, } } diff --git a/web/app/components/workflow/nodes/human-input/panel.tsx b/web/app/components/workflow/nodes/human-input/panel.tsx index 79426136eef..d1565d004a6 100644 --- a/web/app/components/workflow/nodes/human-input/panel.tsx +++ b/web/app/components/workflow/nodes/human-input/panel.tsx @@ -11,10 +11,9 @@ import { RiExpandDiagonalLine, RiEyeLine, } from '@remixicon/react' -import { useBoolean } from 'ahooks' import copy from 'copy-to-clipboard' import * as React from 'react' -import { useCallback } 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' @@ -70,10 +69,10 @@ const Panel: FC> = ({ id, data }) => { }, }) - const [isExpandFormContent, { toggle: toggleExpandFormContent }] = useBoolean(false) + const [isExpandFormContent, setIsExpandFormContent] = useState(false) const nodePanelWidth = useStore((state) => state.nodePanelWidth) - const [isPreview, { toggle: togglePreview, setFalse: hidePreview }] = useBoolean(false) + const [isPreview, setIsPreview] = useState(false) const onAddUseAction = useCallback(() => { const index = inputs.user_actions.length + 1 @@ -131,7 +130,7 @@ const Panel: FC> = ({ id, data }) => { 'flex items-center space-x-1 px-2', isPreview && 'bg-state-accent-active text-text-accent', )} - onClick={togglePreview} + onClick={() => setIsPreview((isPreview) => !isPreview)} >
@@ -160,7 +159,7 @@ const Panel: FC> = ({ id, data }) => { 'flex size-6 cursor-pointer items-center justify-center rounded-md border-none bg-transparent p-0 text-text-secondary hover:bg-components-button-ghost-bg-hover', isExpandFormContent && 'bg-state-accent-active text-text-accent', )} - onClick={toggleExpandFormContent} + onClick={() => setIsExpandFormContent((isExpanded) => !isExpanded)} > {isExpandFormContent ? ( @@ -263,7 +262,7 @@ const Panel: FC> = ({ id, data }) => { content={inputs.form_content} formInputs={inputs.inputs} userActions={inputs.user_actions} - onClose={hidePreview} + onClose={() => setIsPreview(false)} /> )}
diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/add-dataset.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/add-dataset.tsx index 12ff51482e7..96329581225 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/add-dataset.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/add-dataset.tsx @@ -1,9 +1,8 @@ 'use client' import type { FC } from 'react' import type { DataSet } from '@/models/datasets' -import { useBoolean } from 'ahooks' import * as React from 'react' -import { useCallback } from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import SelectDataset from '@/app/components/app/configuration/dataset-config/select-dataset' @@ -15,14 +14,14 @@ type Props = Readonly<{ const AddDataset: FC = ({ selectedIds, modal, onChange }) => { const { t } = useTranslation() - const [isShowModal, { setTrue: showModal, setFalse: hideModal }] = useBoolean(false) + const [isShowModal, setIsShowModal] = useState(false) const handleSelect = useCallback( (datasets: DataSet[]) => { onChange(datasets) - hideModal() + setIsShowModal(false) }, - [onChange, hideModal], + [onChange], ) return (
@@ -30,14 +29,14 @@ const AddDataset: FC = ({ selectedIds, modal, onChange }) => { type="button" aria-label={`${t(($) => $['operation.add'], { ns: 'common' })} ${t(($) => $['nodes.knowledgeRetrieval.knowledge'], { ns: 'workflow' })}`} className="cursor-pointer rounded-md border-none bg-transparent p-1 outline-hidden select-none hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid" - onClick={showModal} + onClick={() => setIsShowModal(true)} >