feat(question-classifier): add drag-and-drop sorting for topics list

- Implement topic sorting functionality using react-sortablejs
- Add draggable handle and visual feedback during sorting
- Update node internals after sorting to ensure proper rendering
pull/22066/head
Mminamiyama 11 months ago
parent d61ea5a2de
commit d6cfb21992

@ -1,6 +1,6 @@
'use client'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import React, { useCallback, useState } from 'react'
import produce from 'immer'
import { useTranslation } from 'react-i18next'
import { useEdgesInteractions } from '../../../hooks'
@ -8,6 +8,10 @@ import AddButton from '../../_base/components/add-button'
import Item from './class-item'
import type { Topic } from '@/app/components/workflow/nodes/question-classifier/types'
import type { ValueSelector, Var } from '@/app/components/workflow/types'
import { ReactSortable } from 'react-sortablejs'
import { noop } from 'lodash-es'
import { RiDraggable } from '@remixicon/react'
import cn from '@/utils/classnames'
const i18nPrefix = 'workflow.nodes.questionClassifiers'
@ -17,6 +21,7 @@ type Props = {
onChange: (list: Topic[]) => void
readonly?: boolean
filterVar: (payload: Var, valueSelector: ValueSelector) => boolean
handleSortTopic?: (newTopics: (Topic & { id: string })[]) => void
}
const ClassList: FC<Props> = ({
@ -25,6 +30,7 @@ const ClassList: FC<Props> = ({
onChange,
readonly,
filterVar,
handleSortTopic = noop,
}) => {
const { t } = useTranslation()
const { handleEdgeDeleteByDeleteBranch } = useEdgesInteractions()
@ -55,12 +61,34 @@ const ClassList: FC<Props> = ({
}
}, [list, onChange, handleEdgeDeleteByDeleteBranch, nodeId])
const [willDeleteCaseId, setWillDeleteCaseId] = useState('')
const topicCount = list.length
const handleSideWidth = 3
// Todo Remove; edit topic name
return (
<div className='space-y-2'>
<ReactSortable
list={list.map(item => ({ ...item }))}
setList={handleSortTopic}
handle='.handle'
ghostClass='bg-components-panel-bg'
animation={150}
disabled={readonly}
className='space-y-2'
>
{
list.map((item, index) => {
return (
<div key={item.id}
className={cn(
'group relative rounded-[10px] bg-components-panel-bg',
willDeleteCaseId === item.id && 'bg-state-destructive-hover',
`-ml-${handleSideWidth} min-h-[40px] px-0 py-0`,
)}>
<RiDraggable className={cn(
'handle absolute left-0 top-3 hidden h-3 w-3 cursor-pointer text-text-quaternary',
topicCount > 1 && 'group-hover:block',
)} />
<div className={`ml-${handleSideWidth}`}>
<Item
nodeId={nodeId}
key={list[index].id}
@ -71,6 +99,8 @@ const ClassList: FC<Props> = ({
readonly={readonly}
filterVar={filterVar}
/>
</div>
</div>
)
})
}
@ -81,7 +111,7 @@ const ClassList: FC<Props> = ({
/>
)}
</div>
</ReactSortable>
)
}
export default React.memo(ClassList)

@ -40,6 +40,7 @@ const Panel: FC<NodePanelProps<QuestionClassifierNodeType>> = ({
handleVisionResolutionChange,
handleVisionResolutionEnabledChange,
filterVar,
handleSortTopic,
} = useConfig(id, data)
const model = inputs.model
@ -99,6 +100,7 @@ const Panel: FC<NodePanelProps<QuestionClassifierNodeType>> = ({
onChange={handleTopicsChange}
readonly={readOnly}
filterVar={filterVar}
handleSortTopic={handleSortTopic}
/>
</Field>
<Split />

@ -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,
}
}

Loading…
Cancel
Save