This commit is contained in:
2025-12-01 17:21:38 +08:00
parent 32fee2b8ab
commit fab8c13cb3
7511 changed files with 996300 additions and 0 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)