215 lines
6.5 KiB
TypeScript
215 lines
6.5 KiB
TypeScript
/*
|
|
* @Author: ZhaoYing
|
|
* @Date: 2026-02-03 16:25:37
|
|
* @Last Modified by: ZhaoYing
|
|
* @Last Modified time: 2026-02-24 11:47:27
|
|
*/
|
|
/**
|
|
* Knowledge Configuration Modal
|
|
* Configures retrieval settings for individual knowledge bases
|
|
* Supports different retrieval modes: participle, semantic, and hybrid
|
|
*/
|
|
|
|
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
|
import { Form, Select, InputNumber, Flex } from 'antd';
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
import type { KnowledgeConfigModalRef, KnowledgeBase, KnowledgeConfigForm, RetrieveType } from './types'
|
|
import RbModal from '@/components/RbModal'
|
|
import RbSlider from '@/components/RbSlider'
|
|
import { formatDateTime } from '@/utils/format';
|
|
|
|
const FormItem = Form.Item;
|
|
|
|
/**
|
|
* Component props
|
|
*/
|
|
interface KnowledgeConfigModalProps {
|
|
/** Callback to update knowledge configuration */
|
|
refresh: (values: KnowledgeConfigForm, type: 'knowledgeConfig') => void;
|
|
}
|
|
|
|
/**
|
|
* Available retrieval types
|
|
*/
|
|
const retrieveTypes: RetrieveType[] = ['participle', 'semantic', 'hybrid', 'graph']
|
|
|
|
/**
|
|
* Modal for configuring knowledge base retrieval settings
|
|
*/
|
|
const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfigModalProps>(({
|
|
refresh,
|
|
}, ref) => {
|
|
const { t } = useTranslation();
|
|
const [visible, setVisible] = useState(false);
|
|
const [form] = Form.useForm<KnowledgeConfigForm>();
|
|
const [data, setData] = useState<KnowledgeBase | null>(null);
|
|
|
|
const values = Form.useWatch<KnowledgeConfigForm>([], form);
|
|
|
|
/** Close modal and reset form */
|
|
const handleClose = () => {
|
|
setVisible(false);
|
|
form.resetFields();
|
|
setData(null)
|
|
};
|
|
|
|
/** Open modal with knowledge base data */
|
|
const handleOpen = (data: KnowledgeBase) => {
|
|
form.setFieldsValue({
|
|
retrieve_type: data?.config?.retrieve_type || retrieveTypes[0],
|
|
kb_id: data.id,
|
|
top_k: data?.config?.top_k || 5,
|
|
similarity_threshold: data?.config?.similarity_threshold || 0.5,
|
|
vector_similarity_weight: data?.config?.vector_similarity_weight || 0.5,
|
|
...(data || {}),
|
|
...(data?.config || {}),
|
|
})
|
|
setData({...data})
|
|
setVisible(true);
|
|
};
|
|
/** Save knowledge configuration */
|
|
const handleSave = () => {
|
|
form
|
|
.validateFields()
|
|
.then(() => {
|
|
refresh(values, 'knowledgeConfig')
|
|
handleClose()
|
|
})
|
|
.catch((err) => {
|
|
console.log('err', err)
|
|
});
|
|
}
|
|
|
|
/** Expose methods to parent component */
|
|
useImperativeHandle(ref, () => ({
|
|
handleOpen,
|
|
handleClose
|
|
}));
|
|
|
|
useEffect(() => {
|
|
if (values?.retrieve_type) {
|
|
const fieldsToReset = Object.keys(values).filter(key =>
|
|
key !== 'kb_id' && key !== 'retrieve_type' && key !== 'top_k'
|
|
) as (keyof KnowledgeConfigForm)[];
|
|
form.resetFields(fieldsToReset);
|
|
}
|
|
}, [values?.retrieve_type])
|
|
|
|
return (
|
|
<RbModal
|
|
title={t('application.knowledgeConfig')}
|
|
open={visible}
|
|
onCancel={handleClose}
|
|
okText={t('common.save')}
|
|
onOk={handleSave}
|
|
>
|
|
<Form
|
|
form={form}
|
|
layout="vertical"
|
|
>
|
|
{data && (
|
|
<Flex align="center" justify="space-between" className="rb:mb-6! rb-border rb:rounded-lg rb:py-4.25! rb:px-4! rb:cursor-pointer rb:bg-[#F0F3F8] rb:text-[#212332]">
|
|
<div className="rb:text-[16px] rb:leading-5.5">
|
|
{data.name}
|
|
<div className="rb:text-[12px] rb:leading-4 rb:text-[#5B6167] rb:mt-2">{t('application.contains', {include_count: data.doc_num})}</div>
|
|
</div>
|
|
<div className="rb:text-[12px] rb:leading-4 rb:text-[#5B6167]">{formatDateTime(data.updated_at, 'YYYY-MM-DD HH:mm:ss')}</div>
|
|
</Flex>
|
|
)}
|
|
<FormItem name="kb_id" hidden />
|
|
{/* Retrieval mode */}
|
|
<FormItem
|
|
name="retrieve_type"
|
|
label={t('application.retrieve_type')}
|
|
extra={t('application.retrieve_type_desc')}
|
|
rules={[{ required: true, message: t('common.pleaseSelect') }]}
|
|
>
|
|
|
|
<Select
|
|
options={retrieveTypes.map(key => ({
|
|
label: t(`application.${key}`),
|
|
value: key,
|
|
}))}
|
|
/>
|
|
</FormItem>
|
|
{/* Top K */}
|
|
<FormItem
|
|
name="top_k"
|
|
label={t('application.top_k')}
|
|
rules={[{ required: true, message: t('common.pleaseEnter') }]}
|
|
extra={t('application.top_k_desc')}
|
|
initialValue={5}
|
|
>
|
|
<InputNumber
|
|
style={{ width: '100%' }}
|
|
min={1}
|
|
max={20}
|
|
onChange={(value) => form.setFieldValue('top_k', value)}
|
|
/>
|
|
</FormItem>
|
|
{/* Semantic similarity threshold */}
|
|
{values?.retrieve_type === 'semantic' && (
|
|
<FormItem
|
|
name="similarity_threshold"
|
|
label={t('application.similarity_threshold')}
|
|
extra={t('application.similarity_threshold_desc')}
|
|
initialValue={0.5}
|
|
>
|
|
<RbSlider
|
|
max={1.0}
|
|
step={0.1}
|
|
min={0.0}
|
|
/>
|
|
</FormItem>
|
|
)}
|
|
{/* Word segmentation matching threshold */}
|
|
{values?.retrieve_type === 'participle' && (
|
|
<FormItem
|
|
name="vector_similarity_weight"
|
|
label={t('application.vector_similarity_weight')}
|
|
extra={t('application.vector_similarity_weight_desc')}
|
|
initialValue={0.5}
|
|
>
|
|
<RbSlider
|
|
max={1.0}
|
|
step={0.1}
|
|
min={0.0}
|
|
/>
|
|
</FormItem>
|
|
)}
|
|
{/* Hybrid retrieval weight */}
|
|
{values?.retrieve_type === 'hybrid' && (
|
|
<>
|
|
<FormItem
|
|
name="similarity_threshold"
|
|
label={t('application.similarity_threshold')}
|
|
extra={t('application.similarity_threshold_desc1')}
|
|
initialValue={0.5}
|
|
>
|
|
<RbSlider
|
|
max={1.0}
|
|
step={0.1}
|
|
min={0.0}
|
|
/>
|
|
</FormItem>
|
|
<FormItem
|
|
name="vector_similarity_weight"
|
|
label={t('application.vector_similarity_weight')}
|
|
extra={t('application.vector_similarity_weight_desc1')}
|
|
initialValue={0.5}
|
|
>
|
|
<RbSlider
|
|
max={1.0}
|
|
step={0.1}
|
|
min={0.0}
|
|
/>
|
|
</FormItem>
|
|
</>
|
|
)}
|
|
</Form>
|
|
</RbModal>
|
|
);
|
|
});
|
|
|
|
export default KnowledgeConfigModal; |