mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 10:28:10 +08:00
Merge branch 'feat/rag-pipeline' of https://github.com/langgenius/dify into feat/rag-pipeline
This commit is contained in:
@ -1,25 +1,35 @@
|
|||||||
import {
|
import {
|
||||||
memo,
|
memo,
|
||||||
|
useCallback,
|
||||||
useMemo,
|
useMemo,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import type { HeaderProps } from '@/app/components/workflow/header'
|
import type { HeaderProps } from '@/app/components/workflow/header'
|
||||||
import Header from '@/app/components/workflow/header'
|
import Header from '@/app/components/workflow/header'
|
||||||
import { fetchWorkflowRunHistory } from '@/service/workflow'
|
import { fetchWorkflowRunHistory } from '@/service/workflow'
|
||||||
import { useStore } from '@/app/components/workflow/store'
|
import {
|
||||||
|
useStore,
|
||||||
|
useWorkflowStore,
|
||||||
|
} from '@/app/components/workflow/store'
|
||||||
import InputFieldButton from './input-field-button'
|
import InputFieldButton from './input-field-button'
|
||||||
import Publisher from './publisher'
|
import Publisher from './publisher'
|
||||||
|
|
||||||
const RagPipelineHeader = () => {
|
const RagPipelineHeader = () => {
|
||||||
|
const workflowStore = useWorkflowStore()
|
||||||
const pipelineId = useStore(s => s.pipelineId)
|
const pipelineId = useStore(s => s.pipelineId)
|
||||||
|
const showDebugAndPreviewPanel = useStore(s => s.showDebugAndPreviewPanel)
|
||||||
|
|
||||||
const viewHistoryProps = useMemo(() => {
|
const viewHistoryProps = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
historyUrl: '',
|
historyUrl: `/rag/pipelines/${pipelineId}/workflow-runs`,
|
||||||
// historyUrl: `/rag/pipeline/${pipelineId}/workflow-runs`,
|
|
||||||
historyFetcher: fetchWorkflowRunHistory,
|
historyFetcher: fetchWorkflowRunHistory,
|
||||||
}
|
}
|
||||||
}, [pipelineId])
|
}, [pipelineId])
|
||||||
|
|
||||||
|
const handleStopRun = useCallback(() => {
|
||||||
|
const { setShowDebugAndPreviewPanel } = workflowStore.getState()
|
||||||
|
setShowDebugAndPreviewPanel(false)
|
||||||
|
}, [workflowStore])
|
||||||
|
|
||||||
const headerProps: HeaderProps = useMemo(() => {
|
const headerProps: HeaderProps = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
normal: {
|
normal: {
|
||||||
@ -31,13 +41,15 @@ const RagPipelineHeader = () => {
|
|||||||
showRunButton: true,
|
showRunButton: true,
|
||||||
runButtonText: 'Test Run',
|
runButtonText: 'Test Run',
|
||||||
viewHistoryProps,
|
viewHistoryProps,
|
||||||
|
isRunning: showDebugAndPreviewPanel,
|
||||||
|
onStopRun: handleStopRun,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
viewHistory: {
|
viewHistory: {
|
||||||
viewHistoryProps,
|
viewHistoryProps,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}, [viewHistoryProps])
|
}, [viewHistoryProps, showDebugAndPreviewPanel, handleStopRun])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Header {...headerProps} />
|
<Header {...headerProps} />
|
||||||
|
|||||||
@ -93,8 +93,8 @@ export const useNodesSyncDraft = () => {
|
|||||||
) => {
|
) => {
|
||||||
if (getNodesReadOnly())
|
if (getNodesReadOnly())
|
||||||
return
|
return
|
||||||
const postParams = getPostParams()
|
|
||||||
|
|
||||||
|
const postParams = getPostParams()
|
||||||
if (postParams) {
|
if (postParams) {
|
||||||
const {
|
const {
|
||||||
setSyncWorkflowDraftHash,
|
setSyncWorkflowDraftHash,
|
||||||
|
|||||||
@ -1,21 +1,16 @@
|
|||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { useStoreApi } from 'reactflow'
|
|
||||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||||
import {
|
import {
|
||||||
BlockEnum,
|
|
||||||
WorkflowRunningStatus,
|
WorkflowRunningStatus,
|
||||||
} from '@/app/components/workflow/types'
|
} from '@/app/components/workflow/types'
|
||||||
import { useWorkflowInteractions } from '@/app/components/workflow/hooks'
|
import { useWorkflowInteractions } from '@/app/components/workflow/hooks'
|
||||||
import {
|
import {
|
||||||
useNodesSyncDraft,
|
useNodesSyncDraft,
|
||||||
usePipelineRun,
|
|
||||||
} from '.'
|
} from '.'
|
||||||
|
|
||||||
export const usePipelineStartRun = () => {
|
export const usePipelineStartRun = () => {
|
||||||
const store = useStoreApi()
|
|
||||||
const workflowStore = useWorkflowStore()
|
const workflowStore = useWorkflowStore()
|
||||||
const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions()
|
const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions()
|
||||||
const { handleRun } = usePipelineRun()
|
|
||||||
const { doSyncWorkflowDraft } = useNodesSyncDraft()
|
const { doSyncWorkflowDraft } = useNodesSyncDraft()
|
||||||
|
|
||||||
const handleWorkflowStartRunInWorkflow = useCallback(async () => {
|
const handleWorkflowStartRunInWorkflow = useCallback(async () => {
|
||||||
@ -26,13 +21,8 @@ export const usePipelineStartRun = () => {
|
|||||||
if (workflowRunningData?.result.status === WorkflowRunningStatus.Running)
|
if (workflowRunningData?.result.status === WorkflowRunningStatus.Running)
|
||||||
return
|
return
|
||||||
|
|
||||||
const { getNodes } = store.getState()
|
|
||||||
const nodes = getNodes()
|
|
||||||
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
|
|
||||||
const startVariables = startNode?.data.variables || []
|
|
||||||
const {
|
const {
|
||||||
showDebugAndPreviewPanel,
|
showDebugAndPreviewPanel,
|
||||||
setShowInputsPanel,
|
|
||||||
setShowEnvPanel,
|
setShowEnvPanel,
|
||||||
setShowDebugAndPreviewPanel,
|
setShowDebugAndPreviewPanel,
|
||||||
} = workflowStore.getState()
|
} = workflowStore.getState()
|
||||||
@ -44,17 +34,9 @@ export const usePipelineStartRun = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!startVariables.length) {
|
await doSyncWorkflowDraft()
|
||||||
await doSyncWorkflowDraft()
|
setShowDebugAndPreviewPanel(true)
|
||||||
handleRun({ inputs: {}, files: [] })
|
}, [workflowStore, handleCancelDebugAndPreviewPanel, doSyncWorkflowDraft])
|
||||||
setShowDebugAndPreviewPanel(true)
|
|
||||||
setShowInputsPanel(false)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setShowDebugAndPreviewPanel(true)
|
|
||||||
setShowInputsPanel(true)
|
|
||||||
}
|
|
||||||
}, [store, workflowStore, handleCancelDebugAndPreviewPanel, handleRun, doSyncWorkflowDraft])
|
|
||||||
|
|
||||||
const handleStartWorkflowRun = useCallback(() => {
|
const handleStartWorkflowRun = useCallback(() => {
|
||||||
handleWorkflowStartRunInWorkflow()
|
handleWorkflowStartRunInWorkflow()
|
||||||
|
|||||||
@ -21,30 +21,36 @@ import {
|
|||||||
|
|
||||||
type RunModeProps = {
|
type RunModeProps = {
|
||||||
text?: string
|
text?: string
|
||||||
|
isRunning?: boolean
|
||||||
|
onStopRun?: () => void
|
||||||
}
|
}
|
||||||
const RunMode = memo(({
|
const RunMode = memo(({
|
||||||
text,
|
text,
|
||||||
|
isRunning: running,
|
||||||
|
onStopRun,
|
||||||
}: RunModeProps) => {
|
}: RunModeProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { handleWorkflowStartRunInWorkflow } = useWorkflowStartRun()
|
const { handleWorkflowStartRunInWorkflow } = useWorkflowStartRun()
|
||||||
const { handleStopRun } = useWorkflowRun()
|
const { handleStopRun } = useWorkflowRun()
|
||||||
const workflowRunningData = useStore(s => s.workflowRunningData)
|
const workflowRunningData = useStore(s => s.workflowRunningData)
|
||||||
const isRunning = workflowRunningData?.result.status === WorkflowRunningStatus.Running
|
const isRunning = workflowRunningData?.result.status === WorkflowRunningStatus.Running
|
||||||
|
const mergedRunning = isRunning || running
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex h-7 items-center rounded-md px-2.5 text-[13px] font-medium text-components-button-secondary-accent-text',
|
'flex h-7 items-center px-2.5 text-[13px] font-medium text-components-button-secondary-accent-text',
|
||||||
'cursor-pointer hover:bg-state-accent-hover',
|
'cursor-pointer hover:bg-state-accent-hover',
|
||||||
isRunning && '!cursor-not-allowed bg-state-accent-hover',
|
mergedRunning && 'cursor-not-allowed bg-state-accent-hover',
|
||||||
|
mergedRunning ? 'rounded-l-md' : 'rounded-md',
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleWorkflowStartRunInWorkflow()
|
handleWorkflowStartRunInWorkflow()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
isRunning
|
mergedRunning
|
||||||
? (
|
? (
|
||||||
<>
|
<>
|
||||||
<RiLoader2Line className='mr-1 h-4 w-4 animate-spin' />
|
<RiLoader2Line className='mr-1 h-4 w-4 animate-spin' />
|
||||||
@ -60,12 +66,14 @@ const RunMode = memo(({
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
isRunning && (
|
mergedRunning && (
|
||||||
<div
|
<div
|
||||||
className='ml-0.5 flex h-7 w-7 cursor-pointer items-center justify-center rounded-md hover:bg-black/5'
|
className={cn(
|
||||||
onClick={() => handleStopRun(workflowRunningData?.task_id || '')}
|
'ml-[1px] flex h-7 w-7 cursor-pointer items-center justify-center rounded-r-md bg-state-accent-active',
|
||||||
|
)}
|
||||||
|
onClick={() => onStopRun ? onStopRun() : handleStopRun(workflowRunningData?.task_id || '')}
|
||||||
>
|
>
|
||||||
<StopCircle className='h-4 w-4 text-components-button-ghost-text' />
|
<StopCircle className='h-4 w-4 text-text-accent' />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -94,12 +102,16 @@ const PreviewMode = memo(() => {
|
|||||||
export type RunAndHistoryProps = {
|
export type RunAndHistoryProps = {
|
||||||
showRunButton?: boolean
|
showRunButton?: boolean
|
||||||
runButtonText?: string
|
runButtonText?: string
|
||||||
|
isRunning?: boolean
|
||||||
|
onStopRun?: () => void
|
||||||
showPreviewButton?: boolean
|
showPreviewButton?: boolean
|
||||||
viewHistoryProps?: ViewHistoryProps
|
viewHistoryProps?: ViewHistoryProps
|
||||||
}
|
}
|
||||||
const RunAndHistory = ({
|
const RunAndHistory = ({
|
||||||
showRunButton,
|
showRunButton,
|
||||||
runButtonText,
|
runButtonText,
|
||||||
|
isRunning,
|
||||||
|
onStopRun,
|
||||||
showPreviewButton,
|
showPreviewButton,
|
||||||
viewHistoryProps,
|
viewHistoryProps,
|
||||||
}: RunAndHistoryProps) => {
|
}: RunAndHistoryProps) => {
|
||||||
@ -108,7 +120,7 @@ const RunAndHistory = ({
|
|||||||
return (
|
return (
|
||||||
<div className='flex h-8 items-center rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-0.5 shadow-xs'>
|
<div className='flex h-8 items-center rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-0.5 shadow-xs'>
|
||||||
{
|
{
|
||||||
showRunButton && <RunMode text={runButtonText} />
|
showRunButton && <RunMode text={runButtonText} isRunning={isRunning} onStopRun={onStopRun} />
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
showPreviewButton && <PreviewMode />
|
showPreviewButton && <PreviewMode />
|
||||||
|
|||||||
@ -3,25 +3,37 @@ import {
|
|||||||
ParentChildChunk,
|
ParentChildChunk,
|
||||||
QuestionAndAnswer,
|
QuestionAndAnswer,
|
||||||
} from '@/app/components/base/icons/src/vender/knowledge'
|
} from '@/app/components/base/icons/src/vender/knowledge'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
import { ChunkStructureEnum } from '../../types'
|
import { ChunkStructureEnum } from '../../types'
|
||||||
import type { Option } from './type'
|
import type { Option } from './type'
|
||||||
|
|
||||||
export const useChunkStructure = () => {
|
export const useChunkStructure = () => {
|
||||||
const GeneralOption: Option = {
|
const GeneralOption: Option = {
|
||||||
id: ChunkStructureEnum.general,
|
id: ChunkStructureEnum.general,
|
||||||
icon: <GeneralChunk className='h-[18px] w-[18px] text-util-colors-indigo-indigo-600' />,
|
icon: (isActive: boolean) => (
|
||||||
|
<GeneralChunk
|
||||||
|
className={cn(
|
||||||
|
'h-[18px] w-[18px] text-text-tertiary group-hover:text-util-colors-indigo-indigo-600',
|
||||||
|
isActive && 'text-util-colors-indigo-indigo-600',
|
||||||
|
)} />
|
||||||
|
),
|
||||||
title: 'General',
|
title: 'General',
|
||||||
description: 'General text chunking mode, the chunks retrieved and recalled are the same.',
|
description: 'General text chunking mode, the chunks retrieved and recalled are the same.',
|
||||||
effectColor: 'blue',
|
effectColor: 'blue',
|
||||||
showEffectColor: true,
|
|
||||||
}
|
}
|
||||||
const ParentChildOption: Option = {
|
const ParentChildOption: Option = {
|
||||||
id: ChunkStructureEnum.parent_child,
|
id: ChunkStructureEnum.parent_child,
|
||||||
icon: <ParentChildChunk className='h-[18px] w-[18px] text-util-colors-blue-light-blue-light-500' />,
|
icon: (isActive: boolean) => (
|
||||||
|
<ParentChildChunk
|
||||||
|
className={cn(
|
||||||
|
'h-[18px] w-[18px] text-text-tertiary group-hover:text-util-colors-blue-light-blue-light-500',
|
||||||
|
isActive && 'text-util-colors-blue-light-blue-light-500',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
title: 'Parent-Child',
|
title: 'Parent-Child',
|
||||||
description: 'Parent-child text chunking mode, the chunks retrieved and recalled are different.',
|
description: 'Parent-child text chunking mode, the chunks retrieved and recalled are different.',
|
||||||
effectColor: 'blue-light',
|
effectColor: 'blue-light',
|
||||||
showEffectColor: true,
|
|
||||||
}
|
}
|
||||||
const QuestionAnswerOption: Option = {
|
const QuestionAnswerOption: Option = {
|
||||||
id: ChunkStructureEnum.question_answer,
|
id: ChunkStructureEnum.question_answer,
|
||||||
@ -39,7 +51,7 @@ export const useChunkStructure = () => {
|
|||||||
const options = [
|
const options = [
|
||||||
GeneralOption,
|
GeneralOption,
|
||||||
ParentChildOption,
|
ParentChildOption,
|
||||||
QuestionAnswerOption,
|
// QuestionAnswerOption,
|
||||||
]
|
]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -35,7 +35,12 @@ const ChunkStructure = ({
|
|||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<OptionCard {...optionMap[chunkStructure]} />
|
<OptionCard
|
||||||
|
{...optionMap[chunkStructure]}
|
||||||
|
selectedId={chunkStructure}
|
||||||
|
enableSelect={false}
|
||||||
|
enableHighlightBorder={false}
|
||||||
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
PortalToFollowElem,
|
PortalToFollowElem,
|
||||||
PortalToFollowElemContent,
|
PortalToFollowElemContent,
|
||||||
@ -23,6 +23,11 @@ const Selector = ({
|
|||||||
}: SelectorProps) => {
|
}: SelectorProps) => {
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
|
const handleSelect = useCallback((optionId: ChunkStructureEnum) => {
|
||||||
|
onChange(optionId)
|
||||||
|
setOpen(false)
|
||||||
|
}, [onChange])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PortalToFollowElem
|
<PortalToFollowElem
|
||||||
placement='bottom-end'
|
placement='bottom-end'
|
||||||
@ -56,16 +61,13 @@ const Selector = ({
|
|||||||
<OptionCard
|
<OptionCard
|
||||||
key={option.id}
|
key={option.id}
|
||||||
id={option.id}
|
id={option.id}
|
||||||
|
selectedId={value}
|
||||||
icon={option.icon}
|
icon={option.icon}
|
||||||
title={option.title}
|
title={option.title}
|
||||||
description={option.description}
|
description={option.description}
|
||||||
onClick={() => {
|
readonly={readonly}
|
||||||
if (readonly)
|
onClick={handleSelect}
|
||||||
return
|
effectColor={option.effectColor}
|
||||||
onChange(option.id)
|
|
||||||
setOpen(false)
|
|
||||||
}}
|
|
||||||
showHighlightBorder={value === option.id}
|
|
||||||
></OptionCard>
|
></OptionCard>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,9 +3,8 @@ import type { ChunkStructureEnum } from '../../types'
|
|||||||
|
|
||||||
export type Option = {
|
export type Option = {
|
||||||
id: ChunkStructureEnum
|
id: ChunkStructureEnum
|
||||||
icon: ReactNode
|
icon: ReactNode | ((isActive: boolean) => ReactNode)
|
||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
effectColor?: string
|
effectColor?: string
|
||||||
showEffectColor?: boolean,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,9 +14,13 @@ import Input from '@/app/components/base/input'
|
|||||||
import { Field } from '@/app/components/workflow/nodes/_base/components/layout'
|
import { Field } from '@/app/components/workflow/nodes/_base/components/layout'
|
||||||
import OptionCard from './option-card'
|
import OptionCard from './option-card'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import { IndexMethodEnum } from '../types'
|
import {
|
||||||
|
ChunkStructureEnum,
|
||||||
|
IndexMethodEnum,
|
||||||
|
} from '../types'
|
||||||
|
|
||||||
type IndexMethodProps = {
|
type IndexMethodProps = {
|
||||||
|
chunkStructure: ChunkStructureEnum
|
||||||
indexMethod: IndexMethodEnum
|
indexMethod: IndexMethodEnum
|
||||||
onIndexMethodChange: (value: IndexMethodEnum) => void
|
onIndexMethodChange: (value: IndexMethodEnum) => void
|
||||||
keywordNumber: number
|
keywordNumber: number
|
||||||
@ -24,6 +28,7 @@ type IndexMethodProps = {
|
|||||||
readonly?: boolean
|
readonly?: boolean
|
||||||
}
|
}
|
||||||
const IndexMethod = ({
|
const IndexMethod = ({
|
||||||
|
chunkStructure,
|
||||||
indexMethod,
|
indexMethod,
|
||||||
onIndexMethodChange,
|
onIndexMethodChange,
|
||||||
keywordNumber,
|
keywordNumber,
|
||||||
@ -53,65 +58,68 @@ const IndexMethod = ({
|
|||||||
<div className='space-y-1'>
|
<div className='space-y-1'>
|
||||||
<OptionCard<IndexMethodEnum>
|
<OptionCard<IndexMethodEnum>
|
||||||
id={IndexMethodEnum.QUALIFIED}
|
id={IndexMethodEnum.QUALIFIED}
|
||||||
|
selectedId={indexMethod}
|
||||||
icon={
|
icon={
|
||||||
<HighQuality
|
<HighQuality
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-[15px] w-[15px] text-text-tertiary',
|
'h-[15px] w-[15px] text-text-tertiary group-hover:text-util-colors-orange-orange-500',
|
||||||
isHighQuality && 'text-util-colors-orange-orange-500',
|
isHighQuality && 'text-util-colors-orange-orange-500',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
title={t('datasetCreation.stepTwo.qualified')}
|
title={t('datasetCreation.stepTwo.qualified')}
|
||||||
description={t('datasetSettings.form.indexMethodHighQualityTip')}
|
description={t('datasetSettings.form.indexMethodHighQualityTip')}
|
||||||
showHighlightBorder={isHighQuality}
|
|
||||||
onClick={handleIndexMethodChange}
|
onClick={handleIndexMethodChange}
|
||||||
isRecommended
|
isRecommended
|
||||||
|
effectColor='orange'
|
||||||
></OptionCard>
|
></OptionCard>
|
||||||
<OptionCard
|
{
|
||||||
id={IndexMethodEnum.ECONOMICAL}
|
chunkStructure !== ChunkStructureEnum.parent_child && (
|
||||||
icon={
|
<OptionCard
|
||||||
<Economic
|
id={IndexMethodEnum.ECONOMICAL}
|
||||||
className={cn(
|
selectedId={indexMethod}
|
||||||
'h-[15px] w-[15px] text-text-tertiary',
|
icon={
|
||||||
isEconomy && 'text-util-colors-indigo-indigo-500',
|
<Economic
|
||||||
)}
|
className={cn(
|
||||||
/>
|
'h-[15px] w-[15px] text-text-tertiary group-hover:text-util-colors-indigo-indigo-500',
|
||||||
}
|
isEconomy && 'text-util-colors-indigo-indigo-500',
|
||||||
title={t('datasetSettings.form.indexMethodEconomy')}
|
)}
|
||||||
description={t('datasetSettings.form.indexMethodEconomyTip')}
|
/>
|
||||||
showChildren={isEconomy}
|
}
|
||||||
showHighlightBorder={isEconomy}
|
title={t('datasetSettings.form.indexMethodEconomy')}
|
||||||
onClick={handleIndexMethodChange}
|
description={t('datasetSettings.form.indexMethodEconomyTip')}
|
||||||
effectColor='blue'
|
onClick={handleIndexMethodChange}
|
||||||
showEffectColor={isEconomy}
|
effectColor='blue'
|
||||||
>
|
>
|
||||||
<div className='flex items-center'>
|
<div className='flex items-center'>
|
||||||
<div className='flex grow items-center'>
|
<div className='flex grow items-center'>
|
||||||
<div className='system-xs-medium truncate text-text-secondary'>
|
<div className='system-xs-medium truncate text-text-secondary'>
|
||||||
Number of Keywords
|
Number of Keywords
|
||||||
|
</div>
|
||||||
|
<Tooltip
|
||||||
|
popupContent='number of keywords'
|
||||||
|
>
|
||||||
|
<RiQuestionLine className='ml-0.5 h-3.5 w-3.5 text-text-quaternary' />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<Slider
|
||||||
|
disabled={readonly}
|
||||||
|
className='mr-3 w-24 shrink-0'
|
||||||
|
value={keywordNumber}
|
||||||
|
onChange={onKeywordNumberChange}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
disabled={readonly}
|
||||||
|
className='shrink-0'
|
||||||
|
wrapperClassName='shrink-0 w-[72px]'
|
||||||
|
type='number'
|
||||||
|
value={keywordNumber}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Tooltip
|
</OptionCard>
|
||||||
popupContent='number of keywords'
|
)
|
||||||
>
|
}
|
||||||
<RiQuestionLine className='ml-0.5 h-3.5 w-3.5 text-text-quaternary' />
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
<Slider
|
|
||||||
disabled={readonly}
|
|
||||||
className='mr-3 w-24 shrink-0'
|
|
||||||
value={keywordNumber}
|
|
||||||
onChange={onKeywordNumberChange}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
disabled={readonly}
|
|
||||||
className='shrink-0'
|
|
||||||
wrapperClassName='shrink-0 w-[72px]'
|
|
||||||
type='number'
|
|
||||||
value={keywordNumber}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</OptionCard>
|
|
||||||
</div>
|
</div>
|
||||||
</Field>
|
</Field>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
import { memo } from 'react'
|
import {
|
||||||
|
memo,
|
||||||
|
useMemo,
|
||||||
|
} from 'react'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import Badge from '@/app/components/base/badge'
|
import Badge from '@/app/components/base/badge'
|
||||||
import {
|
import {
|
||||||
@ -17,63 +20,79 @@ const HEADER_EFFECT_MAP: Record<string, ReactNode> = {
|
|||||||
'purple': <OptionCardEffectPurple />,
|
'purple': <OptionCardEffectPurple />,
|
||||||
}
|
}
|
||||||
type OptionCardProps<T> = {
|
type OptionCardProps<T> = {
|
||||||
id: T
|
id?: T
|
||||||
className?: string
|
selectedId?: T
|
||||||
showHighlightBorder?: boolean
|
enableSelect?: boolean
|
||||||
showRadio?: boolean
|
enableHighlightBorder?: boolean
|
||||||
radioIsActive?: boolean
|
enableRadio?: boolean
|
||||||
icon?: ReactNode
|
wrapperClassName?: string | ((isActive: boolean) => string)
|
||||||
|
className?: string | ((isActive: boolean) => string)
|
||||||
|
icon?: ReactNode | ((isActive: boolean) => ReactNode)
|
||||||
title: string
|
title: string
|
||||||
description?: string
|
description?: string
|
||||||
isRecommended?: boolean
|
isRecommended?: boolean
|
||||||
children?: ReactNode
|
children?: ReactNode
|
||||||
showChildren?: boolean
|
|
||||||
effectColor?: string
|
effectColor?: string
|
||||||
showEffectColor?: boolean
|
|
||||||
onClick?: (id: T) => void
|
onClick?: (id: T) => void
|
||||||
readonly?: boolean
|
readonly?: boolean
|
||||||
}
|
}
|
||||||
const OptionCard = memo(({
|
const OptionCard = memo(({
|
||||||
id,
|
id,
|
||||||
|
selectedId,
|
||||||
|
enableSelect = true,
|
||||||
|
enableHighlightBorder = true,
|
||||||
|
enableRadio,
|
||||||
|
wrapperClassName,
|
||||||
className,
|
className,
|
||||||
showHighlightBorder,
|
|
||||||
showRadio,
|
|
||||||
radioIsActive,
|
|
||||||
icon,
|
icon,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
isRecommended,
|
isRecommended,
|
||||||
children,
|
children,
|
||||||
showChildren,
|
|
||||||
effectColor,
|
effectColor,
|
||||||
showEffectColor,
|
|
||||||
onClick,
|
onClick,
|
||||||
readonly,
|
readonly,
|
||||||
}) => {
|
}) => {
|
||||||
|
const isActive = useMemo(() => {
|
||||||
|
return id === selectedId
|
||||||
|
}, [id, selectedId])
|
||||||
|
|
||||||
|
const effectElement = useMemo(() => {
|
||||||
|
if (effectColor) {
|
||||||
|
return (
|
||||||
|
<div className={cn(
|
||||||
|
'absolute left-[-2px] top-[-2px] hidden h-14 w-14 rounded-full',
|
||||||
|
'group-hover:block',
|
||||||
|
isActive && 'block',
|
||||||
|
)}>
|
||||||
|
{HEADER_EFFECT_MAP[effectColor]}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}, [effectColor, isActive])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'cursor-pointer rounded-xl border border-components-option-card-option-border bg-components-option-card-option-bg',
|
'group overflow-hidden rounded-xl border border-components-option-card-option-border bg-components-option-card-option-bg',
|
||||||
showHighlightBorder && 'border-[2px] border-components-option-card-option-selected-border',
|
isActive && enableHighlightBorder && 'border-[1.5px] border-components-option-card-option-selected-border',
|
||||||
|
enableSelect && 'cursor-pointer hover:shadow-xs',
|
||||||
readonly && 'cursor-not-allowed',
|
readonly && 'cursor-not-allowed',
|
||||||
|
wrapperClassName && (typeof wrapperClassName === 'function' ? wrapperClassName(isActive) : wrapperClassName),
|
||||||
)}
|
)}
|
||||||
onClick={() => !readonly && onClick?.(id)}
|
onClick={() => !readonly && enableSelect && id && onClick?.(id)}
|
||||||
>
|
>
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
'relative flex rounded-t-xl p-2',
|
'relative flex rounded-t-xl p-2',
|
||||||
className,
|
className && (typeof className === 'function' ? className(isActive) : className),
|
||||||
)}>
|
)}>
|
||||||
{
|
{effectElement}
|
||||||
effectColor && showEffectColor && (
|
|
||||||
<div className='absolute left-[-2px] top-[-2px] h-14 w-14 rounded-full'>
|
|
||||||
{HEADER_EFFECT_MAP[effectColor]}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
icon && (
|
icon && (
|
||||||
<div className='mr-1 flex h-[18px] w-[18px] shrink-0 items-center justify-center'>
|
<div className='mr-1 flex h-[18px] w-[18px] shrink-0 items-center justify-center'>
|
||||||
{icon}
|
{typeof icon === 'function' ? icon(isActive) : icon}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -90,10 +109,10 @@ const OptionCard = memo(({
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
showRadio && (
|
enableRadio && (
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
'ml-2 h-4 w-4 shrink-0 rounded-full border border-components-radio-border bg-components-radio-bg',
|
'ml-2 h-4 w-4 shrink-0 rounded-full border border-components-radio-border bg-components-radio-bg',
|
||||||
radioIsActive && 'border-[5px] border-components-radio-border-checked',
|
isActive && 'border-[5px] border-components-radio-border-checked',
|
||||||
)}>
|
)}>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -109,7 +128,7 @@ const OptionCard = memo(({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
children && showChildren && (
|
children && isActive && (
|
||||||
<div className='relative rounded-b-xl bg-components-panel-bg p-3'>
|
<div className='relative rounded-b-xl bg-components-panel-bg p-3'>
|
||||||
<ArrowShape className='absolute left-[14px] top-[-11px] h-4 w-4 text-components-panel-bg' />
|
<ArrowShape className='absolute left-[14px] top-[-11px] h-4 w-4 text-components-panel-bg' />
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
} from '@/app/components/base/icons/src/vender/knowledge'
|
} from '@/app/components/base/icons/src/vender/knowledge'
|
||||||
import {
|
import {
|
||||||
HybridSearchModeEnum,
|
HybridSearchModeEnum,
|
||||||
|
IndexMethodEnum,
|
||||||
RetrievalSearchMethodEnum,
|
RetrievalSearchMethodEnum,
|
||||||
} from '../../types'
|
} from '../../types'
|
||||||
import type {
|
import type {
|
||||||
@ -13,7 +14,7 @@ import type {
|
|||||||
Option,
|
Option,
|
||||||
} from './type'
|
} from './type'
|
||||||
|
|
||||||
export const useRetrievalSetting = () => {
|
export const useRetrievalSetting = (indexMethod: IndexMethodEnum) => {
|
||||||
const VectorSearchOption: Option = useMemo(() => {
|
const VectorSearchOption: Option = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
id: RetrievalSearchMethodEnum.semantic,
|
id: RetrievalSearchMethodEnum.semantic,
|
||||||
@ -41,6 +42,15 @@ export const useRetrievalSetting = () => {
|
|||||||
effectColor: 'purple',
|
effectColor: 'purple',
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
const InvertedIndexOption: Option = useMemo(() => {
|
||||||
|
return {
|
||||||
|
id: RetrievalSearchMethodEnum.invertedIndex,
|
||||||
|
icon: HybridSearch as any,
|
||||||
|
title: 'Inverted Index',
|
||||||
|
description: 'Use inverted index to search for the most relevant text chunks.',
|
||||||
|
effectColor: 'purple',
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
const WeightedScoreModeOption: HybridSearchModeOption = useMemo(() => {
|
const WeightedScoreModeOption: HybridSearchModeOption = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
@ -58,7 +68,9 @@ export const useRetrievalSetting = () => {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return useMemo(() => ({
|
return useMemo(() => ({
|
||||||
options: [
|
options: indexMethod === IndexMethodEnum.ECONOMICAL ? [
|
||||||
|
InvertedIndexOption,
|
||||||
|
] : [
|
||||||
VectorSearchOption,
|
VectorSearchOption,
|
||||||
FullTextSearchOption,
|
FullTextSearchOption,
|
||||||
HybridSearchOption,
|
HybridSearchOption,
|
||||||
@ -71,6 +83,8 @@ export const useRetrievalSetting = () => {
|
|||||||
VectorSearchOption,
|
VectorSearchOption,
|
||||||
FullTextSearchOption,
|
FullTextSearchOption,
|
||||||
HybridSearchOption,
|
HybridSearchOption,
|
||||||
|
InvertedIndexOption,
|
||||||
|
indexMethod,
|
||||||
WeightedScoreModeOption,
|
WeightedScoreModeOption,
|
||||||
RerankModelModeOption,
|
RerankModelModeOption,
|
||||||
])
|
])
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import type {
|
|||||||
RetrievalSearchMethodEnum,
|
RetrievalSearchMethodEnum,
|
||||||
} from '../../types'
|
} from '../../types'
|
||||||
import type {
|
import type {
|
||||||
|
IndexMethodEnum,
|
||||||
WeightedScore,
|
WeightedScore,
|
||||||
} from '../../types'
|
} from '../../types'
|
||||||
import { useRetrievalSetting } from './hooks'
|
import { useRetrievalSetting } from './hooks'
|
||||||
@ -15,6 +16,7 @@ import type { RerankingModelSelectorProps } from './reranking-model-selector'
|
|||||||
import SearchMethodOption from './search-method-option'
|
import SearchMethodOption from './search-method-option'
|
||||||
|
|
||||||
type RetrievalSettingProps = {
|
type RetrievalSettingProps = {
|
||||||
|
indexMethod: IndexMethodEnum
|
||||||
readonly?: boolean
|
readonly?: boolean
|
||||||
searchMethod: RetrievalSearchMethodEnum
|
searchMethod: RetrievalSearchMethodEnum
|
||||||
onRetrievalSearchMethodChange: (value: RetrievalSearchMethodEnum) => void
|
onRetrievalSearchMethodChange: (value: RetrievalSearchMethodEnum) => void
|
||||||
@ -25,6 +27,7 @@ type RetrievalSettingProps = {
|
|||||||
} & RerankingModelSelectorProps & TopKAndScoreThresholdProps
|
} & RerankingModelSelectorProps & TopKAndScoreThresholdProps
|
||||||
|
|
||||||
const RetrievalSetting = ({
|
const RetrievalSetting = ({
|
||||||
|
indexMethod,
|
||||||
readonly,
|
readonly,
|
||||||
searchMethod,
|
searchMethod,
|
||||||
onRetrievalSearchMethodChange,
|
onRetrievalSearchMethodChange,
|
||||||
@ -44,7 +47,7 @@ const RetrievalSetting = ({
|
|||||||
const {
|
const {
|
||||||
options,
|
options,
|
||||||
hybridSearchModeOptions,
|
hybridSearchModeOptions,
|
||||||
} = useRetrievalSetting()
|
} = useRetrievalSetting(indexMethod)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Field
|
<Field
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
memo,
|
memo,
|
||||||
|
useCallback,
|
||||||
useMemo,
|
useMemo,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
@ -53,7 +54,6 @@ const SearchMethodOption = ({
|
|||||||
onScoreThresholdEnabledChange,
|
onScoreThresholdEnabledChange,
|
||||||
}: SearchMethodOptionProps) => {
|
}: SearchMethodOptionProps) => {
|
||||||
const Icon = option.icon
|
const Icon = option.icon
|
||||||
const isActive = searchMethod === option.id
|
|
||||||
const isHybridSearch = option.id === RetrievalSearchMethodEnum.hybrid
|
const isHybridSearch = option.id === RetrievalSearchMethodEnum.hybrid
|
||||||
const isHybridSearchWeightedScoreMode = hybridSearchMode === HybridSearchModeEnum.WeightedScore
|
const isHybridSearchWeightedScoreMode = hybridSearchMode === HybridSearchModeEnum.WeightedScore
|
||||||
|
|
||||||
@ -67,30 +67,32 @@ const SearchMethodOption = ({
|
|||||||
}
|
}
|
||||||
}, [weightedScore])
|
}, [weightedScore])
|
||||||
|
|
||||||
const icon = useMemo(() => {
|
const icon = useCallback((isActive: boolean) => {
|
||||||
return (
|
return (
|
||||||
<Icon
|
<Icon
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-[15px] w-[15px] text-text-tertiary',
|
'h-[15px] w-[15px] text-text-tertiary group-hover:text-util-colors-purple-purple-600',
|
||||||
isActive && 'text-util-colors-purple-purple-600',
|
isActive && 'text-util-colors-purple-purple-600',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}, [isActive, Icon])
|
}, [Icon])
|
||||||
|
|
||||||
|
const hybridSearchModeWrapperClassName = useCallback((isActive: boolean) => {
|
||||||
|
return isActive ? 'border-[1.5px] bg-components-option-card-option-selected-bg' : ''
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OptionCard
|
<OptionCard
|
||||||
key={option.id}
|
key={option.id}
|
||||||
id={option.id}
|
id={option.id}
|
||||||
|
selectedId={searchMethod}
|
||||||
icon={icon}
|
icon={icon}
|
||||||
title={option.title}
|
title={option.title}
|
||||||
description={option.description}
|
description={option.description}
|
||||||
effectColor={option.effectColor}
|
effectColor={option.effectColor}
|
||||||
isRecommended={option.id === RetrievalSearchMethodEnum.hybrid}
|
isRecommended={option.id === RetrievalSearchMethodEnum.hybrid}
|
||||||
onClick={onRetrievalSearchMethodChange}
|
onClick={onRetrievalSearchMethodChange}
|
||||||
showChildren={isActive}
|
|
||||||
showHighlightBorder={isActive}
|
|
||||||
showEffectColor={isActive}
|
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
>
|
>
|
||||||
<div className='space-y-3'>
|
<div className='space-y-3'>
|
||||||
@ -102,11 +104,13 @@ const SearchMethodOption = ({
|
|||||||
<OptionCard
|
<OptionCard
|
||||||
key={hybridOption.id}
|
key={hybridOption.id}
|
||||||
id={hybridOption.id}
|
id={hybridOption.id}
|
||||||
|
selectedId={hybridSearchMode}
|
||||||
|
enableHighlightBorder={false}
|
||||||
|
enableRadio
|
||||||
|
wrapperClassName={hybridSearchModeWrapperClassName}
|
||||||
className='p-3'
|
className='p-3'
|
||||||
title={hybridOption.title}
|
title={hybridOption.title}
|
||||||
description={hybridOption.description}
|
description={hybridOption.description}
|
||||||
showRadio
|
|
||||||
radioIsActive={hybridOption.id === hybridSearchMode}
|
|
||||||
onClick={onHybridSearchModeChange}
|
onClick={onHybridSearchModeChange}
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -4,10 +4,12 @@ import {
|
|||||||
import { useStoreApi } from 'reactflow'
|
import { useStoreApi } from 'reactflow'
|
||||||
import { useNodeDataUpdate } from '@/app/components/workflow/hooks'
|
import { useNodeDataUpdate } from '@/app/components/workflow/hooks'
|
||||||
import type { ValueSelector } from '@/app/components/workflow/types'
|
import type { ValueSelector } from '@/app/components/workflow/types'
|
||||||
import type {
|
import {
|
||||||
ChunkStructureEnum,
|
ChunkStructureEnum,
|
||||||
HybridSearchModeEnum,
|
|
||||||
IndexMethodEnum,
|
IndexMethodEnum,
|
||||||
|
} from '../types'
|
||||||
|
import type {
|
||||||
|
HybridSearchModeEnum,
|
||||||
KnowledgeBaseNodeType,
|
KnowledgeBaseNodeType,
|
||||||
RerankingModel,
|
RerankingModel,
|
||||||
RetrievalSearchMethodEnum,
|
RetrievalSearchMethodEnum,
|
||||||
@ -32,8 +34,13 @@ export const useConfig = (id: string) => {
|
|||||||
}, [id, handleNodeDataUpdateWithSyncDraft])
|
}, [id, handleNodeDataUpdateWithSyncDraft])
|
||||||
|
|
||||||
const handleChunkStructureChange = useCallback((chunkStructure: ChunkStructureEnum) => {
|
const handleChunkStructureChange = useCallback((chunkStructure: ChunkStructureEnum) => {
|
||||||
handleNodeDataUpdate({ chunk_structure: chunkStructure })
|
const nodeData = getNodeData()
|
||||||
}, [handleNodeDataUpdate])
|
const { indexing_technique } = nodeData?.data
|
||||||
|
handleNodeDataUpdate({
|
||||||
|
chunk_structure: chunkStructure,
|
||||||
|
indexing_technique: chunkStructure === ChunkStructureEnum.parent_child ? IndexMethodEnum.QUALIFIED : indexing_technique,
|
||||||
|
})
|
||||||
|
}, [handleNodeDataUpdate, getNodeData])
|
||||||
|
|
||||||
const handleIndexMethodChange = useCallback((indexMethod: IndexMethodEnum) => {
|
const handleIndexMethodChange = useCallback((indexMethod: IndexMethodEnum) => {
|
||||||
handleNodeDataUpdate({ indexing_technique: indexMethod })
|
handleNodeDataUpdate({ indexing_technique: indexMethod })
|
||||||
|
|||||||
@ -63,6 +63,7 @@ const Panel: FC<NodePanelProps<KnowledgeBaseNodeType>> = ({
|
|||||||
<GroupWithBox>
|
<GroupWithBox>
|
||||||
<div className='space-y-3'>
|
<div className='space-y-3'>
|
||||||
<IndexMethod
|
<IndexMethod
|
||||||
|
chunkStructure={data.chunk_structure}
|
||||||
indexMethod={data.indexing_technique}
|
indexMethod={data.indexing_technique}
|
||||||
onIndexMethodChange={handleIndexMethodChange}
|
onIndexMethodChange={handleIndexMethodChange}
|
||||||
keywordNumber={data.keyword_number}
|
keywordNumber={data.keyword_number}
|
||||||
@ -83,6 +84,7 @@ const Panel: FC<NodePanelProps<KnowledgeBaseNodeType>> = ({
|
|||||||
<Split className='h-[1px]' />
|
<Split className='h-[1px]' />
|
||||||
</div>
|
</div>
|
||||||
<RetrievalSetting
|
<RetrievalSetting
|
||||||
|
indexMethod={data.indexing_technique}
|
||||||
searchMethod={data.retrieval_model.search_method}
|
searchMethod={data.retrieval_model.search_method}
|
||||||
onRetrievalSearchMethodChange={handleRetrievalSearchMethodChange}
|
onRetrievalSearchMethodChange={handleRetrievalSearchMethodChange}
|
||||||
hybridSearchMode={data.retrieval_model.hybridSearchMode}
|
hybridSearchMode={data.retrieval_model.hybridSearchMode}
|
||||||
|
|||||||
Reference in New Issue
Block a user