From a9cc19f530591846b9c0c00014fa957c2388cf86 Mon Sep 17 00:00:00 2001 From: Minamiyama Date: Thu, 10 Jul 2025 10:03:11 +0800 Subject: [PATCH] feat(question-classifier): add drag-and-drop sorting for topics list (#22066) Co-authored-by: crazywoola <427733928@qq.com> --- .../components/class-item.tsx | 6 ++ .../components/class-list.tsx | 55 +++++++++++++++---- .../nodes/question-classifier/panel.tsx | 2 + .../nodes/question-classifier/use-config.ts | 16 +++++- 4 files changed, 66 insertions(+), 13 deletions(-) diff --git a/web/app/components/workflow/nodes/question-classifier/components/class-item.tsx b/web/app/components/workflow/nodes/question-classifier/components/class-item.tsx index be4a6cb90..478ac925d 100644 --- a/web/app/components/workflow/nodes/question-classifier/components/class-item.tsx +++ b/web/app/components/workflow/nodes/question-classifier/components/class-item.tsx @@ -11,6 +11,8 @@ import { uniqueId } from 'lodash-es' const i18nPrefix = 'workflow.nodes.questionClassifiers' type Props = { + className?: string + headerClassName?: string nodeId: string payload: Topic onChange: (payload: Topic) => void @@ -21,6 +23,8 @@ type Props = { } const ClassItem: FC = ({ + className, + headerClassName, nodeId, payload, onChange, @@ -49,6 +53,8 @@ const ClassItem: FC = ({ return ( void readonly?: boolean filterVar: (payload: Var, valueSelector: ValueSelector) => boolean + handleSortTopic?: (newTopics: (Topic & { id: string })[]) => void } const ClassList: FC = ({ @@ -25,6 +29,7 @@ const ClassList: FC = ({ onChange, readonly, filterVar, + handleSortTopic = noop, }) => { const { t } = useTranslation() const { handleEdgeDeleteByDeleteBranch } = useEdgesInteractions() @@ -55,22 +60,48 @@ const ClassList: FC = ({ } }, [list, onChange, handleEdgeDeleteByDeleteBranch, nodeId]) + const topicCount = list.length + const handleSideWidth = 3 // Todo Remove; edit topic name return ( -
+ ({ ...item }))} + setList={handleSortTopic} + handle='.handle' + ghostClass='bg-components-panel-bg' + animation={150} + disabled={readonly} + className='space-y-2' + > { list.map((item, index) => { + const canDrag = (() => { + if (readonly) + return false + + return topicCount >= 2 + })() return ( - +
+
+ +
+
) }) } @@ -81,7 +112,7 @@ const ClassList: FC = ({ /> )} -
+ ) } export default React.memo(ClassList) diff --git a/web/app/components/workflow/nodes/question-classifier/panel.tsx b/web/app/components/workflow/nodes/question-classifier/panel.tsx index 8cf9ec5f7..8e27f5dce 100644 --- a/web/app/components/workflow/nodes/question-classifier/panel.tsx +++ b/web/app/components/workflow/nodes/question-classifier/panel.tsx @@ -40,6 +40,7 @@ const Panel: FC> = ({ handleVisionResolutionChange, handleVisionResolutionEnabledChange, filterVar, + handleSortTopic, } = useConfig(id, data) const model = inputs.model @@ -99,6 +100,7 @@ const Panel: FC> = ({ onChange={handleTopicsChange} readonly={readOnly} filterVar={filterVar} + handleSortTopic={handleSortTopic} /> diff --git a/web/app/components/workflow/nodes/question-classifier/use-config.ts b/web/app/components/workflow/nodes/question-classifier/use-config.ts index 8eacf5b43..a4acf5b7f 100644 --- a/web/app/components/workflow/nodes/question-classifier/use-config.ts +++ b/web/app/components/workflow/nodes/question-classifier/use-config.ts @@ -9,13 +9,15 @@ import { import { useStore } from '../../store' import useAvailableVarList from '../_base/hooks/use-available-var-list' import useConfigVision from '../../hooks/use-config-vision' -import type { QuestionClassifierNodeType } from './types' +import type { QuestionClassifierNodeType, Topic } from './types' import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { checkHasQueryBlock } from '@/app/components/base/prompt-editor/constants' +import { useUpdateNodeInternals } from 'reactflow' const useConfig = (id: string, payload: QuestionClassifierNodeType) => { + const updateNodeInternals = useUpdateNodeInternals() const { nodesReadOnly: readOnly } = useNodesReadOnly() const isChatMode = useIsChatMode() const defaultConfig = useStore(s => s.nodesDefaultConfigs)[payload.type] @@ -166,6 +168,17 @@ const useConfig = (id: string, payload: QuestionClassifierNodeType) => { return varPayload.type === VarType.string }, []) + const handleSortTopic = useCallback((newTopics: (Topic & { id: string })[]) => { + const newInputs = produce(inputs, (draft) => { + draft.classes = newTopics.filter(Boolean).map(item => ({ + id: item.id, + name: item.name, + })) + }) + setInputs(newInputs) + updateNodeInternals(id) + }, [id, inputs, setInputs, updateNodeInternals]) + return { readOnly, inputs, @@ -185,6 +198,7 @@ const useConfig = (id: string, payload: QuestionClassifierNodeType) => { isVisionModel, handleVisionResolutionEnabledChange, handleVisionResolutionChange, + handleSortTopic, } }