feat(question-classifier): add drag-and-drop sorting for topics list (#22066)
Co-authored-by: crazywoola <427733928@qq.com>
This commit is contained in:
parent
881a151d30
commit
a9cc19f530
4 changed files with 66 additions and 13 deletions
|
|
@ -11,6 +11,8 @@ import { uniqueId } from 'lodash-es'
|
||||||
const i18nPrefix = 'workflow.nodes.questionClassifiers'
|
const i18nPrefix = 'workflow.nodes.questionClassifiers'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
className?: string
|
||||||
|
headerClassName?: string
|
||||||
nodeId: string
|
nodeId: string
|
||||||
payload: Topic
|
payload: Topic
|
||||||
onChange: (payload: Topic) => void
|
onChange: (payload: Topic) => void
|
||||||
|
|
@ -21,6 +23,8 @@ type Props = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ClassItem: FC<Props> = ({
|
const ClassItem: FC<Props> = ({
|
||||||
|
className,
|
||||||
|
headerClassName,
|
||||||
nodeId,
|
nodeId,
|
||||||
payload,
|
payload,
|
||||||
onChange,
|
onChange,
|
||||||
|
|
@ -49,6 +53,8 @@ const ClassItem: FC<Props> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Editor
|
<Editor
|
||||||
|
className={className}
|
||||||
|
headerClassName={headerClassName}
|
||||||
title={`${t(`${i18nPrefix}.class`)} ${index}`}
|
title={`${t(`${i18nPrefix}.class`)} ${index}`}
|
||||||
placeholder={t(`${i18nPrefix}.topicPlaceholder`)!}
|
placeholder={t(`${i18nPrefix}.topicPlaceholder`)!}
|
||||||
value={payload.name}
|
value={payload.name}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@ import AddButton from '../../_base/components/add-button'
|
||||||
import Item from './class-item'
|
import Item from './class-item'
|
||||||
import type { Topic } from '@/app/components/workflow/nodes/question-classifier/types'
|
import type { Topic } from '@/app/components/workflow/nodes/question-classifier/types'
|
||||||
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
||||||
|
import { ReactSortable } from 'react-sortablejs'
|
||||||
|
import { noop } from 'lodash-es'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.questionClassifiers'
|
const i18nPrefix = 'workflow.nodes.questionClassifiers'
|
||||||
|
|
||||||
|
|
@ -17,6 +20,7 @@ type Props = {
|
||||||
onChange: (list: Topic[]) => void
|
onChange: (list: Topic[]) => void
|
||||||
readonly?: boolean
|
readonly?: boolean
|
||||||
filterVar: (payload: Var, valueSelector: ValueSelector) => boolean
|
filterVar: (payload: Var, valueSelector: ValueSelector) => boolean
|
||||||
|
handleSortTopic?: (newTopics: (Topic & { id: string })[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ClassList: FC<Props> = ({
|
const ClassList: FC<Props> = ({
|
||||||
|
|
@ -25,6 +29,7 @@ const ClassList: FC<Props> = ({
|
||||||
onChange,
|
onChange,
|
||||||
readonly,
|
readonly,
|
||||||
filterVar,
|
filterVar,
|
||||||
|
handleSortTopic = noop,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { handleEdgeDeleteByDeleteBranch } = useEdgesInteractions()
|
const { handleEdgeDeleteByDeleteBranch } = useEdgesInteractions()
|
||||||
|
|
@ -55,22 +60,48 @@ const ClassList: FC<Props> = ({
|
||||||
}
|
}
|
||||||
}, [list, onChange, handleEdgeDeleteByDeleteBranch, nodeId])
|
}, [list, onChange, handleEdgeDeleteByDeleteBranch, nodeId])
|
||||||
|
|
||||||
|
const topicCount = list.length
|
||||||
|
const handleSideWidth = 3
|
||||||
// Todo Remove; edit topic name
|
// Todo Remove; edit topic name
|
||||||
return (
|
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) => {
|
list.map((item, index) => {
|
||||||
|
const canDrag = (() => {
|
||||||
|
if (readonly)
|
||||||
|
return false
|
||||||
|
|
||||||
|
return topicCount >= 2
|
||||||
|
})()
|
||||||
return (
|
return (
|
||||||
<Item
|
<div key={item.id}
|
||||||
nodeId={nodeId}
|
className={cn(
|
||||||
key={list[index].id}
|
'group relative rounded-[10px] bg-components-panel-bg',
|
||||||
payload={item}
|
`-ml-${handleSideWidth} min-h-[40px] px-0 py-0`,
|
||||||
onChange={handleClassChange(index)}
|
)}>
|
||||||
onRemove={handleRemoveClass(index)}
|
<div >
|
||||||
index={index + 1}
|
<Item
|
||||||
readonly={readonly}
|
className={cn(canDrag && 'handle')}
|
||||||
filterVar={filterVar}
|
headerClassName={cn(canDrag && 'cursor-grab')}
|
||||||
/>
|
nodeId={nodeId}
|
||||||
|
key={list[index].id}
|
||||||
|
payload={item}
|
||||||
|
onChange={handleClassChange(index)}
|
||||||
|
onRemove={handleRemoveClass(index)}
|
||||||
|
index={index + 1}
|
||||||
|
readonly={readonly}
|
||||||
|
filterVar={filterVar}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -81,7 +112,7 @@ const ClassList: FC<Props> = ({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</ReactSortable>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default React.memo(ClassList)
|
export default React.memo(ClassList)
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ const Panel: FC<NodePanelProps<QuestionClassifierNodeType>> = ({
|
||||||
handleVisionResolutionChange,
|
handleVisionResolutionChange,
|
||||||
handleVisionResolutionEnabledChange,
|
handleVisionResolutionEnabledChange,
|
||||||
filterVar,
|
filterVar,
|
||||||
|
handleSortTopic,
|
||||||
} = useConfig(id, data)
|
} = useConfig(id, data)
|
||||||
|
|
||||||
const model = inputs.model
|
const model = inputs.model
|
||||||
|
|
@ -99,6 +100,7 @@ const Panel: FC<NodePanelProps<QuestionClassifierNodeType>> = ({
|
||||||
onChange={handleTopicsChange}
|
onChange={handleTopicsChange}
|
||||||
readonly={readOnly}
|
readonly={readOnly}
|
||||||
filterVar={filterVar}
|
filterVar={filterVar}
|
||||||
|
handleSortTopic={handleSortTopic}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
<Split />
|
<Split />
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,15 @@ import {
|
||||||
import { useStore } from '../../store'
|
import { useStore } from '../../store'
|
||||||
import useAvailableVarList from '../_base/hooks/use-available-var-list'
|
import useAvailableVarList from '../_base/hooks/use-available-var-list'
|
||||||
import useConfigVision from '../../hooks/use-config-vision'
|
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 useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||||
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
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 { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
import { checkHasQueryBlock } from '@/app/components/base/prompt-editor/constants'
|
import { checkHasQueryBlock } from '@/app/components/base/prompt-editor/constants'
|
||||||
|
import { useUpdateNodeInternals } from 'reactflow'
|
||||||
|
|
||||||
const useConfig = (id: string, payload: QuestionClassifierNodeType) => {
|
const useConfig = (id: string, payload: QuestionClassifierNodeType) => {
|
||||||
|
const updateNodeInternals = useUpdateNodeInternals()
|
||||||
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
||||||
const isChatMode = useIsChatMode()
|
const isChatMode = useIsChatMode()
|
||||||
const defaultConfig = useStore(s => s.nodesDefaultConfigs)[payload.type]
|
const defaultConfig = useStore(s => s.nodesDefaultConfigs)[payload.type]
|
||||||
|
|
@ -166,6 +168,17 @@ const useConfig = (id: string, payload: QuestionClassifierNodeType) => {
|
||||||
return varPayload.type === VarType.string
|
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 {
|
return {
|
||||||
readOnly,
|
readOnly,
|
||||||
inputs,
|
inputs,
|
||||||
|
|
@ -185,6 +198,7 @@ const useConfig = (id: string, payload: QuestionClassifierNodeType) => {
|
||||||
isVisionModel,
|
isVisionModel,
|
||||||
handleVisionResolutionEnabledChange,
|
handleVisionResolutionEnabledChange,
|
||||||
handleVisionResolutionChange,
|
handleVisionResolutionChange,
|
||||||
|
handleSortTopic,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue