mirror of
https://github.com/langgenius/dify.git
synced 2026-04-28 11:56:55 +08:00
feat(agent): similar to the start node of workflow, agent variables also support drag-and-drop (#26899)
This commit is contained in:
parent
4d8b8f9210
commit
cff5de626b
@ -1,10 +1,11 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React, { useState } from 'react'
|
import React, { useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useBoolean } from 'ahooks'
|
import { useBoolean } from 'ahooks'
|
||||||
import { useContext } from 'use-context-selector'
|
import { useContext } from 'use-context-selector'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
|
import { ReactSortable } from 'react-sortablejs'
|
||||||
import Panel from '../base/feature-panel'
|
import Panel from '../base/feature-panel'
|
||||||
import EditModal from './config-modal'
|
import EditModal from './config-modal'
|
||||||
import VarItem from './var-item'
|
import VarItem from './var-item'
|
||||||
@ -22,6 +23,7 @@ import { useModalContext } from '@/context/modal-context'
|
|||||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||||
import type { InputVar } from '@/app/components/workflow/types'
|
import type { InputVar } from '@/app/components/workflow/types'
|
||||||
import { InputVarType } from '@/app/components/workflow/types'
|
import { InputVarType } from '@/app/components/workflow/types'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
export const ADD_EXTERNAL_DATA_TOOL = 'ADD_EXTERNAL_DATA_TOOL'
|
export const ADD_EXTERNAL_DATA_TOOL = 'ADD_EXTERNAL_DATA_TOOL'
|
||||||
|
|
||||||
@ -218,6 +220,16 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
|
|||||||
|
|
||||||
showEditModal()
|
showEditModal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const promptVariablesWithIds = useMemo(() => promptVariables.map((item) => {
|
||||||
|
return {
|
||||||
|
id: item.key,
|
||||||
|
variable: { ...item },
|
||||||
|
}
|
||||||
|
}), [promptVariables])
|
||||||
|
|
||||||
|
const canDrag = !readonly && promptVariables.length > 1
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel
|
<Panel
|
||||||
className="mt-2"
|
className="mt-2"
|
||||||
@ -245,18 +257,32 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
|
|||||||
)}
|
)}
|
||||||
{hasVar && (
|
{hasVar && (
|
||||||
<div className='mt-1 px-3 pb-3'>
|
<div className='mt-1 px-3 pb-3'>
|
||||||
{promptVariables.map(({ key, name, type, required, config, icon, icon_background }, index) => (
|
<ReactSortable
|
||||||
<VarItem
|
className='space-y-1'
|
||||||
key={index}
|
list={promptVariablesWithIds}
|
||||||
readonly={readonly}
|
setList={(list) => { onPromptVariablesChange?.(list.map(item => item.variable)) }}
|
||||||
name={key}
|
handle='.handle'
|
||||||
label={name}
|
ghostClass='opacity-50'
|
||||||
required={!!required}
|
animation={150}
|
||||||
type={type}
|
>
|
||||||
onEdit={() => handleConfig({ type, key, index, name, config, icon, icon_background })}
|
{promptVariablesWithIds.map((item, index) => {
|
||||||
onRemove={() => handleRemoveVar(index)}
|
const { key, name, type, required, config, icon, icon_background } = item.variable
|
||||||
/>
|
return (
|
||||||
))}
|
<VarItem
|
||||||
|
className={cn(canDrag && 'handle')}
|
||||||
|
key={key}
|
||||||
|
readonly={readonly}
|
||||||
|
name={key}
|
||||||
|
label={name}
|
||||||
|
required={!!required}
|
||||||
|
type={type}
|
||||||
|
onEdit={() => handleConfig({ type, key, index, name, config, icon, icon_background })}
|
||||||
|
onRemove={() => handleRemoveVar(index)}
|
||||||
|
canDrag={canDrag}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ReactSortable>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import type { FC } from 'react'
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import {
|
import {
|
||||||
RiDeleteBinLine,
|
RiDeleteBinLine,
|
||||||
|
RiDraggable,
|
||||||
RiEditLine,
|
RiEditLine,
|
||||||
} from '@remixicon/react'
|
} from '@remixicon/react'
|
||||||
import type { IInputTypeIconProps } from './input-type-icon'
|
import type { IInputTypeIconProps } from './input-type-icon'
|
||||||
@ -12,6 +13,7 @@ import Badge from '@/app/components/base/badge'
|
|||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
type ItemProps = {
|
type ItemProps = {
|
||||||
|
className?: string
|
||||||
readonly?: boolean
|
readonly?: boolean
|
||||||
name: string
|
name: string
|
||||||
label: string
|
label: string
|
||||||
@ -19,9 +21,11 @@ type ItemProps = {
|
|||||||
type: string
|
type: string
|
||||||
onEdit: () => void
|
onEdit: () => void
|
||||||
onRemove: () => void
|
onRemove: () => void
|
||||||
|
canDrag?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const VarItem: FC<ItemProps> = ({
|
const VarItem: FC<ItemProps> = ({
|
||||||
|
className,
|
||||||
readonly,
|
readonly,
|
||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
@ -29,12 +33,16 @@ const VarItem: FC<ItemProps> = ({
|
|||||||
type,
|
type,
|
||||||
onEdit,
|
onEdit,
|
||||||
onRemove,
|
onRemove,
|
||||||
|
canDrag,
|
||||||
}) => {
|
}) => {
|
||||||
const [isDeleting, setIsDeleting] = useState(false)
|
const [isDeleting, setIsDeleting] = useState(false)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('group relative mb-1 flex h-[34px] w-full items-center rounded-lg border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg pl-2.5 pr-3 shadow-xs last-of-type:mb-0 hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-sm', isDeleting && 'border-state-destructive-border hover:bg-state-destructive-hover', readonly && 'cursor-not-allowed opacity-30')}>
|
<div className={cn('group relative mb-1 flex h-[34px] w-full items-center rounded-lg border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg pl-2.5 pr-3 shadow-xs last-of-type:mb-0 hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-sm', isDeleting && 'border-state-destructive-border hover:bg-state-destructive-hover', readonly && 'cursor-not-allowed opacity-30', className)}>
|
||||||
<VarIcon className='mr-1 h-4 w-4 shrink-0 text-text-accent' />
|
<VarIcon className={cn('mr-1 h-4 w-4 shrink-0 text-text-accent', canDrag && 'group-hover:opacity-0')} />
|
||||||
|
{canDrag && (
|
||||||
|
<RiDraggable className='absolute left-3 top-3 hidden h-3 w-3 cursor-pointer text-text-tertiary group-hover:block' />
|
||||||
|
)}
|
||||||
<div className='flex w-0 grow items-center'>
|
<div className='flex w-0 grow items-center'>
|
||||||
<div className='truncate' title={`${name} · ${label}`}>
|
<div className='truncate' title={`${name} · ${label}`}>
|
||||||
<span className='system-sm-medium text-text-secondary'>{name}</span>
|
<span className='system-sm-medium text-text-secondary'>{name}</span>
|
||||||
|
|||||||
@ -61,7 +61,6 @@ const VarList: FC<Props> = ({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (list.some(item => item.variable?.trim() === newKey.trim())) {
|
if (list.some(item => item.variable?.trim() === newKey.trim())) {
|
||||||
console.log('new key', newKey.trim())
|
|
||||||
setToastHandle(Toast.notify({
|
setToastHandle(Toast.notify({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: t('appDebug.varKeyError.keyAlreadyExists', { key: newKey }),
|
message: t('appDebug.varKeyError.keyAlreadyExists', { key: newKey }),
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import produce from 'immer'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import VarItem from './var-item'
|
import VarItem from './var-item'
|
||||||
import { ChangeType, type InputVar, type MoreInfo } from '@/app/components/workflow/types'
|
import { ChangeType, type InputVar, type MoreInfo } from '@/app/components/workflow/types'
|
||||||
import { v4 as uuid4 } from 'uuid'
|
|
||||||
import { ReactSortable } from 'react-sortablejs'
|
import { ReactSortable } from 'react-sortablejs'
|
||||||
import { RiDraggable } from '@remixicon/react'
|
import { RiDraggable } from '@remixicon/react'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
@ -71,9 +70,8 @@ const VarList: FC<Props> = ({
|
|||||||
}, [list, onChange])
|
}, [list, onChange])
|
||||||
|
|
||||||
const listWithIds = useMemo(() => list.map((item) => {
|
const listWithIds = useMemo(() => list.map((item) => {
|
||||||
const id = uuid4()
|
|
||||||
return {
|
return {
|
||||||
id,
|
id: item.variable,
|
||||||
variable: { ...item },
|
variable: { ...item },
|
||||||
}
|
}
|
||||||
}), [list])
|
}), [list])
|
||||||
@ -88,6 +86,8 @@ const VarList: FC<Props> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const canDrag = !readonly && varCount > 1
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactSortable
|
<ReactSortable
|
||||||
className='space-y-1'
|
className='space-y-1'
|
||||||
@ -97,30 +97,23 @@ const VarList: FC<Props> = ({
|
|||||||
ghostClass='opacity-50'
|
ghostClass='opacity-50'
|
||||||
animation={150}
|
animation={150}
|
||||||
>
|
>
|
||||||
{list.map((item, index) => {
|
{listWithIds.map((itemWithId, index) => (
|
||||||
const canDrag = (() => {
|
<div key={itemWithId.id} className='group relative'>
|
||||||
if (readonly)
|
<VarItem
|
||||||
return false
|
className={cn(canDrag && 'handle')}
|
||||||
return varCount > 1
|
readonly={readonly}
|
||||||
})()
|
payload={itemWithId.variable}
|
||||||
return (
|
onChange={handleVarChange(index)}
|
||||||
<div key={index} className='group relative'>
|
onRemove={handleVarRemove(index)}
|
||||||
<VarItem
|
varKeys={list.map(item => item.variable)}
|
||||||
className={cn(canDrag && 'handle')}
|
canDrag={canDrag}
|
||||||
readonly={readonly}
|
/>
|
||||||
payload={item}
|
{canDrag && <RiDraggable className={cn(
|
||||||
onChange={handleVarChange(index)}
|
'handle absolute left-3 top-2.5 hidden h-3 w-3 cursor-pointer text-text-tertiary',
|
||||||
onRemove={handleVarRemove(index)}
|
'group-hover:block',
|
||||||
varKeys={list.map(item => item.variable)}
|
)} />}
|
||||||
canDrag={canDrag}
|
</div>
|
||||||
/>
|
))}
|
||||||
{canDrag && <RiDraggable className={cn(
|
|
||||||
'handle absolute left-3 top-2.5 hidden h-3 w-3 cursor-pointer text-text-tertiary',
|
|
||||||
'group-hover:block',
|
|
||||||
)} />}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</ReactSortable>
|
</ReactSortable>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user