dify
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import MemoryConfig from '../../_base/components/memory-config'
|
||||
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
|
||||
import type { Memory, Node, NodeOutPutVar } from '@/app/components/workflow/types'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
const i18nPrefix = 'workflow.nodes.questionClassifiers'
|
||||
|
||||
type Props = {
|
||||
instruction: string
|
||||
onInstructionChange: (instruction: string) => void
|
||||
hideMemorySetting: boolean
|
||||
memory?: Memory
|
||||
onMemoryChange: (memory?: Memory) => void
|
||||
readonly?: boolean
|
||||
isChatModel: boolean
|
||||
isChatApp: boolean
|
||||
hasSetBlockStatus?: {
|
||||
context: boolean
|
||||
history: boolean
|
||||
query: boolean
|
||||
}
|
||||
nodesOutputVars: NodeOutPutVar[]
|
||||
availableNodes: Node[]
|
||||
}
|
||||
|
||||
const AdvancedSetting: FC<Props> = ({
|
||||
instruction,
|
||||
onInstructionChange,
|
||||
hideMemorySetting,
|
||||
memory,
|
||||
onMemoryChange,
|
||||
readonly,
|
||||
isChatModel,
|
||||
isChatApp,
|
||||
hasSetBlockStatus,
|
||||
nodesOutputVars,
|
||||
availableNodes,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Editor
|
||||
title={
|
||||
<div className='flex items-center space-x-1'>
|
||||
<span className='uppercase'>{t(`${i18nPrefix}.instruction`)}</span>
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className='w-[120px]'>
|
||||
{t(`${i18nPrefix}.instructionTip`)}
|
||||
</div>
|
||||
}
|
||||
triggerClassName='w-3.5 h-3.5 ml-0.5'
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
value={instruction}
|
||||
onChange={onInstructionChange}
|
||||
readOnly={readonly}
|
||||
isChatModel={isChatModel}
|
||||
isChatApp={isChatApp}
|
||||
isShowContext={false}
|
||||
hasSetBlockStatus={hasSetBlockStatus}
|
||||
nodesOutputVars={nodesOutputVars}
|
||||
availableNodes={availableNodes}
|
||||
/>
|
||||
{!hideMemorySetting && (
|
||||
<MemoryConfig
|
||||
className='mt-4'
|
||||
readonly={false}
|
||||
config={{ data: memory }}
|
||||
onChange={onMemoryChange}
|
||||
canSetRoleName={false}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default React.memo(AdvancedSetting)
|
||||
@@ -0,0 +1,73 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { Topic } from '../types'
|
||||
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
|
||||
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
|
||||
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import { uniqueId } from 'lodash-es'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.questionClassifiers'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
headerClassName?: string
|
||||
nodeId: string
|
||||
payload: Topic
|
||||
onChange: (payload: Topic) => void
|
||||
onRemove: () => void
|
||||
index: number
|
||||
readonly?: boolean
|
||||
filterVar: (payload: Var, valueSelector: ValueSelector) => boolean
|
||||
}
|
||||
|
||||
const ClassItem: FC<Props> = ({
|
||||
className,
|
||||
headerClassName,
|
||||
nodeId,
|
||||
payload,
|
||||
onChange,
|
||||
onRemove,
|
||||
index,
|
||||
readonly,
|
||||
filterVar,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [instanceId, setInstanceId] = useState(() => uniqueId())
|
||||
|
||||
useEffect(() => {
|
||||
setInstanceId(`${nodeId}-${uniqueId()}`)
|
||||
}, [nodeId])
|
||||
|
||||
const handleNameChange = useCallback((value: string) => {
|
||||
onChange({ ...payload, name: value })
|
||||
}, [onChange, payload])
|
||||
|
||||
const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, {
|
||||
onlyLeafNodeVar: false,
|
||||
hideChatVar: false,
|
||||
hideEnv: false,
|
||||
filterVar,
|
||||
})
|
||||
|
||||
return (
|
||||
<Editor
|
||||
className={className}
|
||||
headerClassName={headerClassName}
|
||||
title={`${t(`${i18nPrefix}.class`)} ${index}`}
|
||||
placeholder={t(`${i18nPrefix}.topicPlaceholder`)!}
|
||||
value={payload.name}
|
||||
onChange={handleNameChange}
|
||||
showRemove
|
||||
onRemove={onRemove}
|
||||
nodesOutputVars={availableVars}
|
||||
availableNodes={availableNodesWithParent}
|
||||
readOnly={readonly} // ?
|
||||
instanceId={instanceId}
|
||||
justVar // ?
|
||||
isSupportFileVar // ?
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default React.memo(ClassItem)
|
||||
@@ -0,0 +1,174 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { produce } from 'immer'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useEdgesInteractions } from '../../../hooks'
|
||||
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 cn from '@/utils/classnames'
|
||||
import { RiDraggable } from '@remixicon/react'
|
||||
import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.questionClassifiers'
|
||||
|
||||
// Layout constants
|
||||
const HANDLE_SIDE_WIDTH = 3 // Width offset for drag handle spacing
|
||||
|
||||
type Props = {
|
||||
nodeId: string
|
||||
list: Topic[]
|
||||
onChange: (list: Topic[]) => void
|
||||
readonly?: boolean
|
||||
filterVar: (payload: Var, valueSelector: ValueSelector) => boolean
|
||||
handleSortTopic?: (newTopics: (Topic & { id: string })[]) => void
|
||||
}
|
||||
|
||||
const ClassList: FC<Props> = ({
|
||||
nodeId,
|
||||
list,
|
||||
onChange,
|
||||
readonly,
|
||||
filterVar,
|
||||
handleSortTopic = noop,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleEdgeDeleteByDeleteBranch } = useEdgesInteractions()
|
||||
const listContainerRef = useRef<HTMLDivElement>(null)
|
||||
const [shouldScrollToEnd, setShouldScrollToEnd] = useState(false)
|
||||
const prevListLength = useRef(list.length)
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
|
||||
const handleClassChange = useCallback((index: number) => {
|
||||
return (value: Topic) => {
|
||||
const newList = produce(list, (draft) => {
|
||||
draft[index] = value
|
||||
})
|
||||
onChange(newList)
|
||||
}
|
||||
}, [list, onChange])
|
||||
|
||||
const handleAddClass = useCallback(() => {
|
||||
const newList = produce(list, (draft) => {
|
||||
draft.push({ id: `${Date.now()}`, name: '' })
|
||||
})
|
||||
onChange(newList)
|
||||
setShouldScrollToEnd(true)
|
||||
if (collapsed)
|
||||
setCollapsed(false)
|
||||
}, [list, onChange, collapsed])
|
||||
|
||||
const handleRemoveClass = useCallback((index: number) => {
|
||||
return () => {
|
||||
handleEdgeDeleteByDeleteBranch(nodeId, list[index].id)
|
||||
const newList = produce(list, (draft) => {
|
||||
draft.splice(index, 1)
|
||||
})
|
||||
onChange(newList)
|
||||
}
|
||||
}, [list, onChange, handleEdgeDeleteByDeleteBranch, nodeId])
|
||||
|
||||
const topicCount = list.length
|
||||
|
||||
// Scroll to the newly added item after the list updates
|
||||
useEffect(() => {
|
||||
if (shouldScrollToEnd && list.length > prevListLength.current)
|
||||
setShouldScrollToEnd(false)
|
||||
prevListLength.current = list.length
|
||||
}, [list.length, shouldScrollToEnd])
|
||||
|
||||
const handleCollapse = useCallback(() => {
|
||||
setCollapsed(!collapsed)
|
||||
}, [collapsed])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='mb-2 flex items-center justify-between' onClick={handleCollapse}>
|
||||
<div className='flex cursor-pointer items-center text-xs font-semibold uppercase text-text-secondary'>
|
||||
{t(`${i18nPrefix}.class`)} <span className='text-text-destructive'>*</span>
|
||||
{list.length > 0 && (
|
||||
<ArrowDownRoundFill
|
||||
className={cn(
|
||||
'h-4 w-4 text-text-quaternary transition-transform duration-200',
|
||||
collapsed && '-rotate-90',
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!collapsed && (
|
||||
<div
|
||||
ref={listContainerRef}
|
||||
className={cn('overflow-y-visible', `pl-${HANDLE_SIDE_WIDTH}`)}
|
||||
>
|
||||
<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) => {
|
||||
const canDrag = (() => {
|
||||
if (readonly)
|
||||
return false
|
||||
|
||||
return topicCount >= 2
|
||||
})()
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
className={cn(
|
||||
'group relative rounded-[10px] bg-components-panel-bg',
|
||||
`-ml-${HANDLE_SIDE_WIDTH} min-h-[40px] px-0 py-0`,
|
||||
)}
|
||||
style={{
|
||||
// Performance hint for browser
|
||||
contain: 'layout style paint',
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
{canDrag && <RiDraggable className={cn(
|
||||
'handle absolute left-2 top-3 hidden h-3 w-3 cursor-pointer text-text-tertiary',
|
||||
'group-hover:block',
|
||||
)} />}
|
||||
<Item
|
||||
className={cn(canDrag && 'handle')}
|
||||
headerClassName={cn(canDrag && 'cursor-grab group-hover:pl-5')}
|
||||
nodeId={nodeId}
|
||||
key={list[index].id}
|
||||
payload={item}
|
||||
onChange={handleClassChange(index)}
|
||||
onRemove={handleRemoveClass(index)}
|
||||
index={index + 1}
|
||||
readonly={readonly}
|
||||
filterVar={filterVar}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ReactSortable>
|
||||
</div>
|
||||
)}
|
||||
{!readonly && !collapsed && (
|
||||
<div className='mt-2'>
|
||||
<AddButton
|
||||
onClick={handleAddClass}
|
||||
text={t(`${i18nPrefix}.addClass`)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default React.memo(ClassList)
|
||||
Reference in New Issue
Block a user