feat(web): Add Feishu and Yuque knowledge base sync support
- Add API endpoints for creating sync tasks and checking Feishu/Yuque authentication - Add new sync-related UI components for Feishu and Yuque platform integration - Add internationalization strings for sync operations and authentication messages in English and Chinese - Add form fields for Feishu (App ID, App Secret, Folder Token) and Yuque (User ID, Token) credentials - Add web crawler configuration fields (entry URL, max pages, delay, timeout, user agent) - Add sync status messages (syncing, success, completed, timeout, failed, error states) - Update CreateDataset component to support new data source types - Update KnowledgeBase types to include new sync-related properties - Enable users to synchronize knowledge base content from Feishu and Yuque platforms with proper authentication and error handling
This commit is contained in:
@@ -220,6 +220,21 @@ export const createDocumentAndUpload = async ( data: any, params: PathQuery) =>
|
||||
const response = await request.post(`${apiPrefix}/files/customtext`, data, { params } );
|
||||
return response as any;
|
||||
};
|
||||
// web feishu yuque
|
||||
export const createSync = async (knowledge_id: string) => {
|
||||
const response = await request.post(`${apiPrefix}/knowledges/${knowledge_id}/sync`);
|
||||
return response as any;
|
||||
};
|
||||
// check feishu
|
||||
export const checkFeishuSync = async (params: any) => {
|
||||
const response = await request.get(`${apiPrefix}/knowledges/check/feishu/auth`, undefined, { params });
|
||||
return response as any;
|
||||
};
|
||||
// check yuque
|
||||
export const checkYuqueSync = async (params: any) => {
|
||||
const response = await request.get(`${apiPrefix}/knowledges/check/yuque/auth`, undefined, { params });
|
||||
return response as any;
|
||||
};
|
||||
// 更新文档
|
||||
export const updateDocument = async (id: string, data: KnowledgeBaseDocumentData) => {
|
||||
const response = await request.put(`${apiPrefix}/documents/${id}`, data);
|
||||
|
||||
@@ -597,6 +597,18 @@ export const en = {
|
||||
last_at: 'Last Updated',
|
||||
private: 'Private',
|
||||
share: 'Share',
|
||||
syncNow: 'Sync Now',
|
||||
syncing: 'Syncing...',
|
||||
syncSuccess: 'Sync started successfully',
|
||||
syncCompleted: 'Sync completed, data loaded successfully',
|
||||
syncTimeout: 'Sync timeout (1 minute), please try again later or check the sync status',
|
||||
syncFailed: 'Sync failed',
|
||||
syncError: 'Unable to sync, knowledge base not found',
|
||||
yuqueAuthRequired: 'Please enter Yuque User ID and Token',
|
||||
yuqueAuthSuccess: 'Yuque authentication successful',
|
||||
feishuAuthRequired: 'Please enter Feishu App ID, App Secret and Folder Token',
|
||||
feishuAuthSuccess: 'Feishu authentication successful',
|
||||
authFailed: 'Authentication failed, please check your credentials',
|
||||
recallTest: 'Recall Test',
|
||||
testQuestion: 'Test Question',
|
||||
doc_num: 'Document Number',
|
||||
@@ -636,6 +648,7 @@ export const en = {
|
||||
customText: 'Custom Text',
|
||||
customContent: 'Custom Content',
|
||||
createContentError: 'Failed to create custom content',
|
||||
createLinkError: 'Failed to create link content',
|
||||
manuallyInputText: 'Manually input a text as dataset',
|
||||
importTemplate: 'Template Import',
|
||||
importBackup: 'Backup Import',
|
||||
@@ -835,6 +848,36 @@ export const en = {
|
||||
embeddingRequired: 'Please select an embedding model',
|
||||
nameRequired: 'Please enter a name',
|
||||
modelRequired: 'Please select a model',
|
||||
entryUrl: 'Entry URL',
|
||||
entryUrlRequired: 'Please enter entry URL',
|
||||
entryUrlInvalid: 'Please enter a valid URL',
|
||||
maxPages: 'Max Pages',
|
||||
maxPagesRequired: 'Please enter max pages',
|
||||
delaySeconds: 'Delay Seconds',
|
||||
delaySecondsRequired: 'Please enter delay seconds',
|
||||
timeoutSeconds: 'Timeout Seconds',
|
||||
timeoutSecondsRequired: 'Please enter timeout seconds',
|
||||
userAgent: 'User Agent',
|
||||
userAgentRequired: 'Please enter user agent',
|
||||
platform: 'Platform',
|
||||
platformRequired: 'Please select a platform',
|
||||
yuque: 'Yuque',
|
||||
feishu: 'Feishu',
|
||||
yuqueUserId: 'User ID',
|
||||
yuqueUserIdRequired: 'Please enter Yuque User ID',
|
||||
yuqueUserIdPlaceholder: 'Enter your Yuque User ID',
|
||||
yuqueToken: 'Token',
|
||||
yuqueTokenRequired: 'Please enter Yuque Token',
|
||||
yuqueTokenPlaceholder: 'Enter your Yuque Token',
|
||||
feishuAppId: 'Feishu Application ID',
|
||||
feishuAppIdRequired: 'Please enter Feishu Application ID',
|
||||
feishuAppIdPlaceholder: 'Enter your Feishu Application ID',
|
||||
feishuAppSecret: 'Feishu Application Secret',
|
||||
feishuAppSecretRequired: 'Please enter Feishu Application Secret',
|
||||
feishuAppSecretPlaceholder: 'Enter your Feishu Application Secret',
|
||||
feishuFolderToken: 'Folder Token',
|
||||
feishuFolderTokenRequired: 'Please enter Folder Token',
|
||||
feishuFolderTokenPlaceholder: 'Enter your Feishu Folder Token',
|
||||
}
|
||||
},
|
||||
api: {
|
||||
|
||||
@@ -139,6 +139,18 @@ export const zh = {
|
||||
last_at: '最后更新',
|
||||
private: '私有',
|
||||
share: '分享',
|
||||
syncNow: '立即同步',
|
||||
syncing: '同步中...',
|
||||
syncSuccess: '同步已启动',
|
||||
syncCompleted: '同步完成,数据加载成功',
|
||||
syncTimeout: '同步超时(1分钟),请稍后重试或检查同步状态',
|
||||
syncFailed: '同步失败',
|
||||
syncError: '无法同步,未找到知识库',
|
||||
yuqueAuthRequired: '请输入语雀用户 ID 和 Token',
|
||||
yuqueAuthSuccess: '语雀认证成功',
|
||||
feishuAuthRequired: '请输入飞书应用 ID、应用密钥和文件夹 Token',
|
||||
feishuAuthSuccess: '飞书认证成功',
|
||||
authFailed: '认证失败,请检查您的凭证',
|
||||
recallTest: '召回测试',
|
||||
testQuestion: '测试问题',
|
||||
doc_num: '文档数量',
|
||||
@@ -375,6 +387,36 @@ export const zh = {
|
||||
embeddingRequired: '请选择嵌入模型',
|
||||
nameRequired: '请输入名称',
|
||||
modelRequired: '请选择模型',
|
||||
entryUrl: '入口网址',
|
||||
entryUrlRequired: '请输入入口网址',
|
||||
entryUrlInvalid: '请输入有效的网址',
|
||||
maxPages: '最大页面数',
|
||||
maxPagesRequired: '请输入最大页面数',
|
||||
delaySeconds: '延迟秒数',
|
||||
delaySecondsRequired: '请输入延迟秒数',
|
||||
timeoutSeconds: '超时秒数',
|
||||
timeoutSecondsRequired: '请输入超时秒数',
|
||||
userAgent: '用户代理',
|
||||
userAgentRequired: '请输入用户代理',
|
||||
platform: '平台',
|
||||
platformRequired: '请选择平台',
|
||||
yuque: '语雀',
|
||||
feishu: '飞书',
|
||||
yuqueUserId: '用户 ID',
|
||||
yuqueUserIdRequired: '请输入语雀用户 ID',
|
||||
yuqueUserIdPlaceholder: '请输入您的语雀用户 ID',
|
||||
yuqueToken: 'Token',
|
||||
yuqueTokenRequired: '请输入语雀 Token',
|
||||
yuqueTokenPlaceholder: '请输入您的语雀 Token',
|
||||
feishuAppId: '飞书应用 ID',
|
||||
feishuAppIdRequired: '请输入飞书应用 ID',
|
||||
feishuAppIdPlaceholder: '请输入您的飞书应用 ID',
|
||||
feishuAppSecret: '飞书应用密钥',
|
||||
feishuAppSecretRequired: '请输入飞书应用密钥',
|
||||
feishuAppSecretPlaceholder: '请输入您的飞书应用密钥',
|
||||
feishuFolderToken: '文件夹 Token',
|
||||
feishuFolderTokenRequired: '请输入文件夹 Token',
|
||||
feishuFolderTokenPlaceholder: '请输入您的飞书文件夹 Token',
|
||||
}
|
||||
},
|
||||
application: {
|
||||
|
||||
@@ -613,8 +613,8 @@ const CreateDataset = () => {
|
||||
{t('knowledgeBase.webLink')}
|
||||
</div>
|
||||
<TextArea rows={6} placeholder={t('knowledgeBase.webLinkPlaceholder')} />
|
||||
<div className='rb:text-sm rb:text-gray-500 rb:mt-3 rb:max-w-[558px]'>
|
||||
{t('knowledgeBase.webLinkDesc')}
|
||||
<div className='rb:text-sm rb:text-gray-500 rb:mt-3'>
|
||||
{t('knowledgeBase.webLinkDesc',{count: 5})}
|
||||
</div>
|
||||
<div className='rb:text-sm rb:font-medium rb:text-gray-800 rb:mt-10 rb:mb-3'>
|
||||
{t('knowledgeBase.selectorTutorial')}
|
||||
|
||||
@@ -14,7 +14,7 @@ import textIcon from '@/assets/images/knowledgeBase/text.png';
|
||||
import editIcon from '@/assets/images/knowledgeBase/edit.png';
|
||||
// import blankIcon from '@/assets/images/knowledgeBase/blankDocument.png';
|
||||
// import imageIcon from '@/assets/images/knowledgeBase/image.png'
|
||||
import { getKnowledgeBaseDetail, deleteDocument, downloadFile, updateKnowledgeBase } from '@/api/knowledgeBase';
|
||||
import { getKnowledgeBaseDetail, deleteDocument, downloadFile, updateKnowledgeBase, createSync } from '@/api/knowledgeBase';
|
||||
import {
|
||||
type CreateModalRef,
|
||||
type KnowledgeBaseListItem,
|
||||
@@ -71,6 +71,9 @@ const Private: FC = () => {
|
||||
const [folderTreeRefreshKey, setFolderTreeRefreshKey] = useState(0);
|
||||
const [autoExpandPath, setAutoExpandPath] = useState<Array<{ id: string; name: string }>>([]);
|
||||
const [isGraph, setIsGraph] = useState(false);
|
||||
const [isSyncing, setIsSyncing] = useState(false);
|
||||
const syncIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
const syncStartTimeRef = useRef<number | null>(null);
|
||||
const { updateBreadcrumbs } = useBreadcrumbManager({
|
||||
breadcrumbType: 'detail',
|
||||
// Don't provide onKnowledgeBaseMenuClick, let it use default navigation behavior (return to list page)
|
||||
@@ -111,6 +114,7 @@ const Private: FC = () => {
|
||||
// Convert KnowledgeBase to KnowledgeBaseListItem
|
||||
const listItem = res as unknown as KnowledgeBaseListItem;
|
||||
setKnowledgeBase(listItem);
|
||||
return listItem;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -257,6 +261,15 @@ const Private: FC = () => {
|
||||
}
|
||||
}, [location.state, knowledgeBaseId, navigate, location.pathname]);
|
||||
|
||||
// Cleanup sync interval on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (syncIntervalRef.current) {
|
||||
clearInterval(syncIntervalRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Handle tree node selection
|
||||
const onSelect = (keys: React.Key[]) => {
|
||||
if (!keys.length) {
|
||||
@@ -686,11 +699,69 @@ const Private: FC = () => {
|
||||
createFolderModalRef?.current?.handleOpen(f,'edit');
|
||||
}
|
||||
|
||||
const handleRefreshTable = () => {
|
||||
// Refresh table data
|
||||
fetchKnowledgeBaseDetail(knowledgeBase.id)
|
||||
const handleRefreshTable = async () => {
|
||||
// Check if sync has timed out (1 minute = 60000ms)
|
||||
if (syncStartTimeRef.current) {
|
||||
const elapsedTime = Date.now() - syncStartTimeRef.current;
|
||||
if (elapsedTime > 60000) {
|
||||
stopSyncing();
|
||||
messageApi.warning(t('knowledgeBase.syncTimeout'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh table data and get updated knowledge base info
|
||||
const updatedKnowledgeBase = await fetchKnowledgeBaseDetail(knowledgeBase.id);
|
||||
tableRef.current?.loadData();
|
||||
|
||||
// Check if there are documents and stop syncing if so
|
||||
if (syncStartTimeRef.current && updatedKnowledgeBase?.doc_num && updatedKnowledgeBase.doc_num > 0) {
|
||||
stopSyncing();
|
||||
messageApi.success(t('knowledgeBase.syncCompleted'));
|
||||
}
|
||||
}
|
||||
|
||||
// Handle sync for Web and Third-party knowledge bases
|
||||
const handleSync = async () => {
|
||||
if (!knowledgeBase?.id) {
|
||||
messageApi.error(t('knowledgeBase.syncError'));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsSyncing(true);
|
||||
syncStartTimeRef.current = Date.now(); // Record start time
|
||||
await createSync(knowledgeBase.id);
|
||||
messageApi.success(t('knowledgeBase.syncSuccess'));
|
||||
|
||||
// Start polling: refresh table every 5 seconds and check for data
|
||||
syncIntervalRef.current = setInterval(async () => {
|
||||
await handleRefreshTable();
|
||||
}, 5000);
|
||||
|
||||
// Initial refresh after 1 second
|
||||
setTimeout(async () => {
|
||||
await handleRefreshTable();
|
||||
}, 1000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Sync failed:', error);
|
||||
messageApi.error(t('knowledgeBase.syncFailed'));
|
||||
setIsSyncing(false);
|
||||
syncStartTimeRef.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
// Stop syncing and clear interval
|
||||
const stopSyncing = () => {
|
||||
if (syncIntervalRef.current) {
|
||||
clearInterval(syncIntervalRef.current);
|
||||
syncIntervalRef.current = null;
|
||||
}
|
||||
syncStartTimeRef.current = null;
|
||||
setIsSyncing(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{contextHolder}
|
||||
@@ -749,9 +820,20 @@ const Private: FC = () => {
|
||||
<Button onClick={handleShare}>{t('knowledgeBase.share')}</Button>
|
||||
<Button onClick={handleRecallTest}>{t('knowledgeBase.recallTest')}</Button>
|
||||
<Button onClick={handleSetting}>{t('knowledgeBase.knowledgeBase')} {t('knowledgeBase.setting')}</Button>
|
||||
<Dropdown menu={{ items: createItems }} trigger={['click']}>
|
||||
{(knowledgeBase?.type === 'Web' || knowledgeBase?.type === 'Third-party') && (
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={isSyncing ? stopSyncing : handleSync}
|
||||
loading={isSyncing}
|
||||
>
|
||||
{isSyncing ? t('knowledgeBase.syncing') : t('knowledgeBase.syncNow')}
|
||||
</Button>
|
||||
)}
|
||||
{knowledgeBase?.type !== 'Web' && knowledgeBase?.type !== 'Third-party' && (
|
||||
<Dropdown menu={{ items: createItems }} trigger={['click']}>
|
||||
<Button type="primary" onClick={handelCreateOrImport} >+ {t('knowledgeBase.createImport')}</Button>
|
||||
</Dropdown>
|
||||
</Dropdown>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react';
|
||||
import { Form, Input, Select, Modal, Tabs, Switch, Radio, Button,message } from 'antd';
|
||||
import { Form, Input, Select, Modal, Tabs, Switch, Radio, Button, message } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { KnowledgeBaseListItem, KnowledgeBaseFormData, CreateModalRef, CreateModalRefProps } from '@/views/KnowledgeBase/types';
|
||||
import {
|
||||
@@ -9,9 +9,12 @@ import {
|
||||
updateKnowledgeBase,
|
||||
getKnowledgeGraphEntityTypes,
|
||||
deleteKnowledgeGraph,
|
||||
rebuildKnowledgeGraph
|
||||
rebuildKnowledgeGraph,
|
||||
checkFeishuSync,
|
||||
checkYuqueSync
|
||||
} from '@/api/knowledgeBase'
|
||||
import RbModal from '@/components/RbModal'
|
||||
import SliderInput from '@/components/SliderInput'
|
||||
const { TextArea } = Input;
|
||||
const { confirm } = Modal
|
||||
|
||||
@@ -28,6 +31,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
const [modelOptionsByType, setModelOptionsByType] = useState<Record<string, { label: string; value: string }[]>>({});
|
||||
const [datasets, setDatasets] = useState<KnowledgeBaseListItem | null>(null);
|
||||
const [currentType, setCurrentType] = useState<'General' | 'Web' | 'Third-party' | 'Folder'>('General');
|
||||
const [thirdPartyPlatform, setThirdPartyPlatform] = useState<'yuque' | 'feishu'>('yuque');
|
||||
const [form] = Form.useForm<KnowledgeBaseFormData>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState('basic');
|
||||
@@ -51,6 +55,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
setActiveTab('basic');
|
||||
setIsRebuildMode(false); // Reset rebuild mode flag
|
||||
setOriginalType(''); // Reset original type
|
||||
setThirdPartyPlatform('yuque'); // Reset third party platform
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
@@ -175,13 +180,13 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
: next[tp] || [];
|
||||
|
||||
// If there are options and current field has no value, set first option as default
|
||||
if (options.length > 0 && !form.getFieldValue(fieldKey)) {
|
||||
if (options.length > 0 && !form.getFieldValue(fieldKey as any)) {
|
||||
defaultValues[fieldKey] = options[0].value;
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(defaultValues).length > 0) {
|
||||
form.setFieldsValue(defaultValues as Partial<KnowledgeBaseFormData>);
|
||||
form.setFieldsValue(defaultValues as any);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -205,6 +210,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
};
|
||||
|
||||
// Process parser_config data, set default values if not present
|
||||
const recordAny = record as any;
|
||||
baseValues.parser_config = record.parser_config || {
|
||||
graphrag: {
|
||||
use_graphrag: false,
|
||||
@@ -216,6 +222,43 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
}
|
||||
};
|
||||
|
||||
// Add Third-party specific fields to parser_config if exists
|
||||
if (recordAny.parser_config?.third_party_platform) {
|
||||
baseValues.parser_config.third_party_platform = recordAny.parser_config.third_party_platform;
|
||||
}
|
||||
if (recordAny.parser_config?.yuque_user_id) {
|
||||
baseValues.parser_config.yuque_user_id = recordAny.parser_config.yuque_user_id;
|
||||
}
|
||||
if (recordAny.parser_config?.yuque_token) {
|
||||
baseValues.parser_config.yuque_token = recordAny.parser_config.yuque_token;
|
||||
}
|
||||
if (recordAny.parser_config?.app_id) {
|
||||
baseValues.parser_config.app_id = recordAny.parser_config.app_id;
|
||||
}
|
||||
if (recordAny.parser_config?.app_secret) {
|
||||
baseValues.parser_config.app_secret = recordAny.parser_config.app_secret;
|
||||
}
|
||||
if (recordAny.parser_config?.folder_token) {
|
||||
baseValues.parser_config.folder_token = recordAny.parser_config.folder_token;
|
||||
}
|
||||
|
||||
// Add Web specific fields to parser_config if exists
|
||||
if (recordAny.parser_config?.entry_url) {
|
||||
baseValues.parser_config.entry_url = recordAny.parser_config.entry_url;
|
||||
}
|
||||
if (recordAny.parser_config?.max_pages) {
|
||||
baseValues.parser_config.max_pages = recordAny.parser_config.max_pages;
|
||||
}
|
||||
if (recordAny.parser_config?.delay_seconds) {
|
||||
baseValues.parser_config.delay_seconds = recordAny.parser_config.delay_seconds;
|
||||
}
|
||||
if (recordAny.parser_config?.timeout_seconds) {
|
||||
baseValues.parser_config.timeout_seconds = recordAny.parser_config.timeout_seconds;
|
||||
}
|
||||
if (recordAny.parser_config?.user_agent) {
|
||||
baseValues.parser_config.user_agent = recordAny.parser_config.user_agent;
|
||||
}
|
||||
|
||||
// If entity_types exists, convert to newline-separated format for TextArea display
|
||||
if (baseValues.parser_config.graphrag.entity_types) {
|
||||
if (Array.isArray(baseValues.parser_config.graphrag.entity_types)) {
|
||||
@@ -255,6 +298,16 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
setIsRebuildMode(type === 'rebuild'); // Set rebuild mode flag
|
||||
setOriginalType(type || ''); // Save original type parameter
|
||||
|
||||
// Set third party platform if editing Third-party type
|
||||
if (actualType === 'Third-party' && record) {
|
||||
const platform = (record as any).parser_config?.third_party_platform;
|
||||
if (platform === 'yuque' || platform === 'feishu') {
|
||||
setThirdPartyPlatform(platform);
|
||||
}
|
||||
} else {
|
||||
setThirdPartyPlatform('yuque'); // Reset to default
|
||||
}
|
||||
|
||||
// If rebuild mode, default to knowledge graph tab
|
||||
if (type === 'rebuild') {
|
||||
setActiveTab('knowledgeGraph');
|
||||
@@ -319,52 +372,95 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
}
|
||||
};
|
||||
// Actual save logic
|
||||
const performSave = () => {
|
||||
form
|
||||
.validateFields()
|
||||
.then(() => {
|
||||
setLoading(true)
|
||||
const formValues = form.getFieldsValue();
|
||||
const performSave = async () => {
|
||||
try {
|
||||
await form.validateFields();
|
||||
setLoading(true);
|
||||
const formValues = form.getFieldsValue();
|
||||
|
||||
// Check Third-party authentication before saving
|
||||
if (formValues.type === 'Third-party' || currentType === 'Third-party') {
|
||||
const platform = formValues.parser_config?.third_party_platform || thirdPartyPlatform;
|
||||
|
||||
// Process entity_types format conversion: from newline-separated string to string array
|
||||
if (formValues.parser_config && formValues.parser_config.graphrag && formValues.parser_config.graphrag.entity_types) {
|
||||
const entityTypesString = formValues.parser_config.graphrag.entity_types as any as string;
|
||||
const entityTypesArray = entityTypesString
|
||||
.split('\n')
|
||||
.map((item: string) => item.trim())
|
||||
.filter((item: string) => item.length > 0);
|
||||
formValues.parser_config.graphrag.entity_types = entityTypesArray;
|
||||
}
|
||||
|
||||
// Ensure correct type is used when saving (not 'rebuild')
|
||||
const saveType = originalType === 'rebuild' ? currentType : (formValues.type || currentType);
|
||||
|
||||
const payload: KnowledgeBaseFormData = {
|
||||
...formValues,
|
||||
type: saveType,
|
||||
permission_id: formValues.permission_id || 'Private',
|
||||
parent_id: datasets?.parent_id || undefined,
|
||||
};
|
||||
|
||||
console.log('Saving payload:', payload); // Debug log
|
||||
|
||||
const submit = datasets?.id
|
||||
? updateKnowledgeBase(datasets.id, payload)
|
||||
: createKnowledgeBase(payload);
|
||||
submit
|
||||
.then(() => {
|
||||
if (refreshTable) {
|
||||
refreshTable();
|
||||
try {
|
||||
if (platform === 'yuque') {
|
||||
// Validate Yuque credentials
|
||||
const yuqueParams = {
|
||||
yuque_user_id: formValues.parser_config?.yuque_user_id,
|
||||
yuque_token: formValues.parser_config?.yuque_token
|
||||
};
|
||||
|
||||
if (!yuqueParams.yuque_user_id || !yuqueParams.yuque_token) {
|
||||
messageApi.error(t('knowledgeBase.yuqueAuthRequired'));
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
handleClose();
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
}).catch((err) => {
|
||||
console.log('Validation failed:', err)
|
||||
});
|
||||
|
||||
await checkYuqueSync(yuqueParams);
|
||||
messageApi.success(t('knowledgeBase.yuqueAuthSuccess'));
|
||||
|
||||
} else if (platform === 'feishu') {
|
||||
// Validate Feishu credentials
|
||||
const feishuParams = {
|
||||
app_id: formValues.parser_config?.app_id,
|
||||
app_secret: formValues.parser_config?.app_secret,
|
||||
folder_token: formValues.parser_config?.folder_token
|
||||
};
|
||||
|
||||
if (!feishuParams.app_id || !feishuParams.app_secret || !feishuParams.folder_token) {
|
||||
messageApi.error(t('knowledgeBase.feishuAuthRequired'));
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await checkFeishuSync(feishuParams);
|
||||
messageApi.success(t('knowledgeBase.feishuAuthSuccess'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Authentication failed:', error);
|
||||
messageApi.error(t('knowledgeBase.authFailed'));
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Process entity_types format conversion: from newline-separated string to string array
|
||||
if (formValues.parser_config && formValues.parser_config.graphrag && formValues.parser_config.graphrag.entity_types) {
|
||||
const entityTypesString = formValues.parser_config.graphrag.entity_types as any as string;
|
||||
const entityTypesArray = entityTypesString
|
||||
.split('\n')
|
||||
.map((item: string) => item.trim())
|
||||
.filter((item: string) => item.length > 0);
|
||||
formValues.parser_config.graphrag.entity_types = entityTypesArray;
|
||||
}
|
||||
|
||||
// Ensure correct type is used when saving (not 'rebuild')
|
||||
const saveType = originalType === 'rebuild' ? currentType : (formValues.type || currentType);
|
||||
|
||||
const payload: KnowledgeBaseFormData = {
|
||||
...formValues,
|
||||
type: saveType,
|
||||
permission_id: formValues.permission_id || 'Private',
|
||||
parent_id: datasets?.parent_id || undefined,
|
||||
};
|
||||
|
||||
console.log('Saving payload:', payload); // Debug log
|
||||
|
||||
const submit = datasets?.id
|
||||
? updateKnowledgeBase(datasets.id, payload)
|
||||
: createKnowledgeBase(payload);
|
||||
|
||||
await submit;
|
||||
|
||||
if (refreshTable) {
|
||||
refreshTable();
|
||||
}
|
||||
handleClose();
|
||||
|
||||
} catch (err) {
|
||||
console.log('Validation or save failed:', err);
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
const handleChange = (_value: string, tp: string) => {
|
||||
// Only trigger prompt in edit mode and when type is embedding
|
||||
@@ -424,6 +520,139 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
<TextArea rows={2} placeholder={t('knowledgeBase.createForm.description')} />
|
||||
</Form.Item>
|
||||
|
||||
{/* Web type specific fields */}
|
||||
{currentType === 'Web' && (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['parser_config', 'entry_url']}
|
||||
label={t('knowledgeBase.createForm.entryUrl')}
|
||||
rules={[
|
||||
{ required: true, message: t('knowledgeBase.createForm.entryUrlRequired') },
|
||||
{ type: 'url', message: t('knowledgeBase.createForm.entryUrlInvalid') }
|
||||
]}
|
||||
>
|
||||
<Input placeholder="https://ai.redbearai.com" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={['parser_config', 'max_pages']}
|
||||
label={t('knowledgeBase.createForm.maxPages')}
|
||||
rules={[{ required: true, message: t('knowledgeBase.createForm.maxPagesRequired') }]}
|
||||
initialValue={20}
|
||||
>
|
||||
<SliderInput
|
||||
min={10}
|
||||
max={200}
|
||||
step={1}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={['parser_config', 'delay_seconds']}
|
||||
label={t('knowledgeBase.createForm.delaySeconds')}
|
||||
rules={[{ required: true, message: t('knowledgeBase.createForm.delaySecondsRequired') }]}
|
||||
initialValue={1.0}
|
||||
>
|
||||
<SliderInput
|
||||
min={1}
|
||||
max={3}
|
||||
step={0.1}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={['parser_config', 'timeout_seconds']}
|
||||
label={t('knowledgeBase.createForm.timeoutSeconds')}
|
||||
rules={[{ required: true, message: t('knowledgeBase.createForm.timeoutSecondsRequired') }]}
|
||||
initialValue={10}
|
||||
>
|
||||
<SliderInput
|
||||
min={5}
|
||||
max={15}
|
||||
step={1}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={['parser_config', 'user_agent']}
|
||||
label={t('knowledgeBase.createForm.userAgent')}
|
||||
rules={[{ required: true, message: t('knowledgeBase.createForm.userAgentRequired') }]}
|
||||
initialValue="KnowledgeBaseCrawler/1.0"
|
||||
>
|
||||
<Input placeholder="KnowledgeBaseCrawler/1.0" />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Third-party type specific fields */}
|
||||
{currentType === 'Third-party' && (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['parser_config', 'third_party_platform']}
|
||||
label={t('knowledgeBase.createForm.platform')}
|
||||
rules={[{ required: true, message: t('knowledgeBase.createForm.platformRequired') }]}
|
||||
initialValue="yuque"
|
||||
>
|
||||
<Select
|
||||
value={thirdPartyPlatform}
|
||||
onChange={(value) => setThirdPartyPlatform(value)}
|
||||
options={[
|
||||
{ value: 'yuque', label: t('knowledgeBase.createForm.yuque') },
|
||||
{ value: 'feishu', label: t('knowledgeBase.createForm.feishu') }
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
{thirdPartyPlatform === 'yuque' && (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['parser_config', 'yuque_user_id']}
|
||||
label={t('knowledgeBase.createForm.yuqueUserId')}
|
||||
rules={[{ required: true, message: t('knowledgeBase.createForm.yuqueUserIdRequired') }]}
|
||||
>
|
||||
<Input placeholder={t('knowledgeBase.createForm.yuqueUserIdPlaceholder')} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={['parser_config', 'yuque_token']}
|
||||
label={t('knowledgeBase.createForm.yuqueToken')}
|
||||
rules={[{ required: true, message: t('knowledgeBase.createForm.yuqueTokenRequired') }]}
|
||||
>
|
||||
<Input.Password placeholder={t('knowledgeBase.createForm.yuqueTokenPlaceholder')} />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
{thirdPartyPlatform === 'feishu' && (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['parser_config', 'app_id']}
|
||||
label={t('knowledgeBase.createForm.feishuAppId')}
|
||||
rules={[{ required: true, message: t('knowledgeBase.createForm.feishuAppIdRequired') }]}
|
||||
>
|
||||
<Input placeholder={t('knowledgeBase.createForm.feishuAppIdPlaceholder')} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={['parser_config', 'app_secret']}
|
||||
label={t('knowledgeBase.createForm.feishuAppSecret')}
|
||||
rules={[{ required: true, message: t('knowledgeBase.createForm.feishuAppSecretRequired') }]}
|
||||
>
|
||||
<Input.Password placeholder={t('knowledgeBase.createForm.feishuAppSecretPlaceholder')} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={['parser_config', 'folder_token']}
|
||||
label={t('knowledgeBase.createForm.feishuFolderToken')}
|
||||
rules={[{ required: true, message: t('knowledgeBase.createForm.feishuFolderTokenRequired') }]}
|
||||
>
|
||||
<Input placeholder={t('knowledgeBase.createForm.feishuFolderTokenPlaceholder')} />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{currentType !== 'Folder' && dynamicTypeList.map((tp) => {
|
||||
const fieldKey = typeToFieldKey(tp);
|
||||
// When tp is 'llm', merge llm and chat options
|
||||
|
||||
@@ -223,8 +223,6 @@ const KnowledgeBaseManagement: FC = () => {
|
||||
const fetchKnowledgeBaseTypes = async () => {
|
||||
try {
|
||||
let types = await getKnowledgeBaseTypeList();
|
||||
types = types.filter(type => (type === 'General' || type === 'Folder' )); //
|
||||
//暂时未实现 ,过滤掉未实现
|
||||
setKnowledgeBaseTypes(types);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch knowledge base types:', error);
|
||||
|
||||
@@ -95,8 +95,24 @@ export interface ParserConfig {
|
||||
auto_keywords?: number; // 自动关键词
|
||||
auto_questions?: number; // 自动问题
|
||||
html4excel?: boolean; // 是否为Excel文件
|
||||
graphrag:GraphragConfig; // 知识图谱生成
|
||||
|
||||
graphrag: GraphragConfig; // 知识图谱生成
|
||||
|
||||
// Web 类型特有字段
|
||||
entry_url?: string; // 入口网址
|
||||
max_pages?: number; // 最大页面数 (10-200)
|
||||
delay_seconds?: number; // 延迟秒数 (1-3)
|
||||
timeout_seconds?: number; // 超时秒数 (5-15)
|
||||
user_agent?: string; // 用户代理
|
||||
|
||||
// Third-party 类型特有字段
|
||||
third_party_platform?: 'yuque' | 'feishu'; // 第三方平台类型
|
||||
// 语雀字段
|
||||
yuque_user_id?: string; // 语雀用户ID
|
||||
yuque_token?: string; // 语雀Token
|
||||
// 飞书字段
|
||||
app_id?: string; // 飞书应用ID
|
||||
app_secret?: string; // 飞书应用密钥
|
||||
folder_token?: string; // 飞书文件夹Token
|
||||
}
|
||||
// 文件数据
|
||||
export interface KnowledgeBaseDocumentData { // 知识库文档数据
|
||||
|
||||
Reference in New Issue
Block a user