Files
dify/web/app/components/workflow/nodes/knowledge-base/node.tsx
yyh 0b2ded3227 feat(knowledge-base): add fine-grained embedding model validation with inline warnings
Extract validation logic from default.ts into shared utils.ts, enabling
node card, panel, and checklist to share the same validation rules.
Introduce provider-scoped model list queries to detect non-active model
states (noConfigure, quotaExceeded, credentialRemoved, incompatible).
Expand node card from 2 rows to 4 rows with per-row warning indicators,
and add warningDot support to panel field titles.
2026-03-10 17:25:27 +08:00

203 lines
7.3 KiB
TypeScript

import type { FC } from 'react'
import type { KnowledgeBaseNodeType } from './types'
import type { NodeProps } from '@/app/components/workflow/types'
import { useQuery } from '@tanstack/react-query'
import {
memo,
useMemo,
} from 'react'
import { useTranslation } from 'react-i18next'
import {
ModelTypeEnum,
} from '@/app/components/header/account-setting/model-provider-page/declarations'
import {
useLanguage,
useModelList,
} from '@/app/components/header/account-setting/model-provider-page/hooks'
import { consoleQuery } from '@/service/client'
import { cn } from '@/utils/classnames'
import { useSettingsDisplay } from './hooks/use-settings-display'
import {
IndexMethodEnum,
} from './types'
import {
getKnowledgeBaseValidationIssue,
getKnowledgeBaseValidationMessage,
isKnowledgeBaseEmbeddingIssue,
KnowledgeBaseValidationIssueCode,
} from './utils'
type SettingRowProps = {
label: string
value: string
warning?: boolean
}
const SettingRow = memo(({
label,
value,
warning = false,
}: SettingRowProps) => {
return (
<div
className={cn(
'flex h-6 items-center rounded-md px-1.5',
warning
? 'border-[0.5px] border-state-warning-active bg-state-warning-hover'
: 'bg-workflow-block-parma-bg',
)}
>
<div className={cn('mr-2 shrink-0 system-xs-medium-uppercase', warning ? 'text-text-warning' : 'text-text-tertiary')}>
{label}
</div>
<div
className={cn('grow truncate text-right system-xs-medium', warning ? 'text-text-warning' : 'text-text-secondary')}
title={value}
>
{value}
</div>
</div>
)
})
const RETRIEVAL_WARNING_CODES = new Set<KnowledgeBaseValidationIssueCode>([
KnowledgeBaseValidationIssueCode.retrievalSettingRequired,
KnowledgeBaseValidationIssueCode.rerankingModelRequired,
KnowledgeBaseValidationIssueCode.rerankingModelInvalid,
])
const Node: FC<NodeProps<KnowledgeBaseNodeType>> = ({ data }) => {
const { t } = useTranslation()
const language = useLanguage()
const settingsDisplay = useSettingsDisplay()
const { data: embeddingModelList } = useModelList(ModelTypeEnum.textEmbedding)
const { data: rerankModelList } = useModelList(ModelTypeEnum.rerank)
const chunkStructure = data.chunk_structure
const indexChunkVariableSelector = data.index_chunk_variable_selector
const indexingTechnique = data.indexing_technique
const embeddingModel = data.embedding_model
const retrievalModel = data.retrieval_model
const retrievalSearchMethod = retrievalModel?.search_method
const retrievalRerankingEnable = retrievalModel?.reranking_enable
const embeddingModelProvider = data.embedding_model_provider
const { data: embeddingProviderModelList } = useQuery(
consoleQuery.modelProviders.models.queryOptions({
input: { params: { provider: embeddingModelProvider || '' } },
enabled: indexingTechnique === IndexMethodEnum.QUALIFIED && !!embeddingModelProvider,
refetchOnWindowFocus: false,
select: response => response.data,
}),
)
const validationPayload = useMemo(() => {
return {
chunk_structure: chunkStructure,
index_chunk_variable_selector: indexChunkVariableSelector,
indexing_technique: indexingTechnique,
embedding_model: embeddingModel,
embedding_model_provider: embeddingModelProvider,
retrieval_model: {
search_method: retrievalSearchMethod,
reranking_enable: retrievalRerankingEnable,
reranking_model: retrievalModel?.reranking_model,
},
_embeddingModelList: embeddingModelList,
_embeddingProviderModelList: embeddingProviderModelList,
_rerankModelList: rerankModelList,
}
}, [
chunkStructure,
indexChunkVariableSelector,
indexingTechnique,
embeddingModel,
embeddingModelProvider,
retrievalSearchMethod,
retrievalRerankingEnable,
retrievalModel?.reranking_model,
embeddingModelList,
embeddingProviderModelList,
rerankModelList,
])
const validationIssue = useMemo(() => {
return getKnowledgeBaseValidationIssue({
...validationPayload,
})
}, [validationPayload])
const validationIssueMessage = useMemo(() => {
return getKnowledgeBaseValidationMessage(validationIssue, t)
}, [validationIssue, t])
const chunksDisplayValue = useMemo(() => {
if (!data.index_chunk_variable_selector?.length)
return '-'
const chunkVar = data.index_chunk_variable_selector.at(-1)
return chunkVar || '-'
}, [data.index_chunk_variable_selector])
const embeddingModelDisplay = useMemo(() => {
if (data.indexing_technique !== IndexMethodEnum.QUALIFIED)
return '-'
if (isKnowledgeBaseEmbeddingIssue(validationIssue))
return validationIssueMessage
const currentEmbeddingModelProvider = embeddingModelList.find(provider => provider.provider === data.embedding_model_provider)
const currentEmbeddingModel = currentEmbeddingModelProvider?.models.find(model => model.model === data.embedding_model)
return currentEmbeddingModel?.label[language] || currentEmbeddingModel?.label.en_US || data.embedding_model || '-'
}, [data.embedding_model, data.embedding_model_provider, data.indexing_technique, embeddingModelList, language, validationIssue, validationIssueMessage])
const indexMethodDisplay = settingsDisplay[data.indexing_technique as keyof typeof settingsDisplay] || '-'
const retrievalMethodDisplay = settingsDisplay[data.retrieval_model?.search_method as keyof typeof settingsDisplay] || '-'
const chunksWarning = validationIssue?.code === KnowledgeBaseValidationIssueCode.chunksVariableRequired
const indexMethodWarning = validationIssue?.code === KnowledgeBaseValidationIssueCode.indexMethodRequired
const embeddingWarning = isKnowledgeBaseEmbeddingIssue(validationIssue)
const showEmbeddingModelRow = data.indexing_technique === IndexMethodEnum.QUALIFIED
const retrievalWarning = !!(validationIssue && RETRIEVAL_WARNING_CODES.has(validationIssue.code))
if (!data.chunk_structure) {
return (
<div className="mb-1 space-y-0.5 px-3 py-1">
<div className="flex h-6 items-center rounded-md border-[0.5px] border-state-warning-active bg-state-warning-hover px-1.5">
<span className="mr-1 size-[4px] shrink-0 rounded-[2px] bg-text-warning-secondary" />
<div className="grow truncate text-text-warning system-xs-medium" title={validationIssueMessage}>
{validationIssueMessage}
</div>
</div>
</div>
)
}
return (
<div className="mb-1 space-y-0.5 px-3 py-1">
<SettingRow
label={t('nodes.knowledgeBase.chunksInput', { ns: 'workflow' })}
value={chunksWarning ? validationIssueMessage : chunksDisplayValue}
warning={chunksWarning}
/>
<SettingRow
label={t('stepTwo.indexMode', { ns: 'datasetCreation' })}
value={indexMethodWarning ? validationIssueMessage : indexMethodDisplay}
warning={indexMethodWarning}
/>
{showEmbeddingModelRow && (
<SettingRow
label={t('form.embeddingModel', { ns: 'datasetSettings' })}
value={embeddingModelDisplay}
warning={embeddingWarning}
/>
)}
<SettingRow
label={t('form.retrievalSetting.method', { ns: 'datasetSettings' })}
value={retrievalWarning ? validationIssueMessage : retrievalMethodDisplay}
warning={retrievalWarning}
/>
</div>
)
}
export default memo(Node)