mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-24 17:36:47 +08:00
284 lines
8.5 KiB
TypeScript
284 lines
8.5 KiB
TypeScript
import {
|
|
DynamicForm,
|
|
DynamicFormRef,
|
|
FormFieldConfig,
|
|
FormFieldType,
|
|
} from '@/components/dynamic-form';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Separator } from '@/components/ui/separator';
|
|
import { RunningStatus, RunningStatusOld } from '@/constants/knowledge';
|
|
import { isEqual } from 'lodash';
|
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
import { FieldValues } from 'react-hook-form';
|
|
import { useTranslation } from 'react-i18next';
|
|
import {
|
|
DataSourceFormDefaultValues,
|
|
DataSourceKey,
|
|
getCommonExtraDefaultValues,
|
|
getDataSourceFormBaseFields,
|
|
getDataSourceFieldsWithExtras,
|
|
mergeDataSourceFormValues,
|
|
useDataSourceInfo,
|
|
} from '../constant';
|
|
import {
|
|
useAddDataSource,
|
|
useFetchDataSourceDetail,
|
|
useTestDataSource,
|
|
useUpdateDataSourceStatus,
|
|
} from '../hooks';
|
|
import { DataSourceLogsTable } from './log-table';
|
|
|
|
const SourceDetailPage = () => {
|
|
const { t } = useTranslation();
|
|
const formRef = useRef<DynamicFormRef>(null);
|
|
|
|
const { data: detail } = useFetchDataSourceDetail();
|
|
const { updateStatus, loading: statusUpdateLoading } =
|
|
useUpdateDataSourceStatus();
|
|
const { dataSourceInfo } = useDataSourceInfo();
|
|
const detailInfo = useMemo(() => {
|
|
if (detail) {
|
|
return dataSourceInfo[detail.source];
|
|
}
|
|
}, [detail, dataSourceInfo]);
|
|
|
|
const [fields, setFields] = useState<FormFieldConfig[]>([]);
|
|
const [isDirty, setIsDirty] = useState(false);
|
|
const [defaultValues, setDefaultValues] = useState<FieldValues>(
|
|
DataSourceFormDefaultValues[
|
|
detail?.source as keyof typeof DataSourceFormDefaultValues
|
|
] as FieldValues,
|
|
);
|
|
|
|
const customFields = useMemo(() => {
|
|
return [
|
|
{
|
|
label: t('setting.dataSourcePruneFreq'),
|
|
name: 'prune_freq',
|
|
type: FormFieldType.Number,
|
|
required: false,
|
|
shouldRender: (values: any) => !!values?.config?.sync_deleted_files,
|
|
render: (fieldProps: FormFieldConfig) => {
|
|
return (
|
|
<Input
|
|
{...fieldProps}
|
|
type={FormFieldType.Number}
|
|
suffix={
|
|
<span className="px-2 text-text-secondary italic">
|
|
{t('setting.minutes')}
|
|
</span>
|
|
}
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
label: t('setting.dataSourceRefreshFreq'),
|
|
name: 'refresh_freq',
|
|
type: FormFieldType.Number,
|
|
required: false,
|
|
render: (fieldProps: FormFieldConfig) => (
|
|
<Input
|
|
{...fieldProps}
|
|
type={FormFieldType.Number}
|
|
suffix={
|
|
<span className="px-2 text-text-secondary italic">
|
|
{t('setting.minutes')}
|
|
</span>
|
|
}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
label: t('setting.dataSourceTimeoutSecs'),
|
|
name: 'timeout_secs',
|
|
type: FormFieldType.Number,
|
|
required: false,
|
|
render: (fieldProps: FormFieldConfig) => (
|
|
<div className="flex items-center gap-1 w-full relative">
|
|
<div className="flex-1">
|
|
<Input
|
|
{...fieldProps}
|
|
type={FormFieldType.Number}
|
|
suffix={
|
|
<span className="px-2 text-text-secondary italic">
|
|
{t('setting.seconds')}
|
|
</span>
|
|
}
|
|
/>
|
|
</div>
|
|
</div>
|
|
),
|
|
},
|
|
];
|
|
}, [t]);
|
|
|
|
const { addLoading, handleAddOk } = useAddDataSource({ isEdit: true });
|
|
const { loading: testLoading, handleTest } = useTestDataSource();
|
|
|
|
const onSubmit = useCallback(() => {
|
|
formRef?.current?.submit();
|
|
}, []);
|
|
|
|
const isUnstarted = useMemo(
|
|
() =>
|
|
detail?.status === RunningStatus.UNSTART ||
|
|
detail?.status === RunningStatusOld.UNSTART,
|
|
[detail?.status],
|
|
);
|
|
|
|
const isConnectorActive = useMemo(
|
|
() =>
|
|
detail?.status === RunningStatus.RUNNING ||
|
|
detail?.status === RunningStatus.SCHEDULE ||
|
|
detail?.status === RunningStatusOld.RUNNING ||
|
|
detail?.status === RunningStatusOld.SCHEDULE,
|
|
[detail?.status],
|
|
);
|
|
|
|
const actionMode = useMemo(() => {
|
|
if (isDirty) {
|
|
return 'save' as const;
|
|
}
|
|
|
|
if (isUnstarted) {
|
|
return 'save' as const;
|
|
}
|
|
|
|
if (isConnectorActive) {
|
|
return 'stop' as const;
|
|
}
|
|
|
|
return 'resume' as const;
|
|
}, [isConnectorActive, isDirty, isUnstarted]);
|
|
|
|
const handlePrimaryAction = useCallback(() => {
|
|
if (actionMode === 'save') {
|
|
onSubmit();
|
|
return;
|
|
}
|
|
updateStatus(
|
|
actionMode === 'resume' ? RunningStatus.SCHEDULE : RunningStatus.CANCEL,
|
|
);
|
|
}, [actionMode, onSubmit, updateStatus]);
|
|
|
|
const primaryActionLabel = useMemo(() => {
|
|
if (actionMode === 'stop') return t('common.stop');
|
|
if (actionMode === 'resume') return t('common.resume');
|
|
return t('common.save');
|
|
}, [actionMode, t]);
|
|
|
|
useEffect(() => {
|
|
const baseFields = getDataSourceFormBaseFields(t).map((field) => {
|
|
if (field.name === 'name') {
|
|
return {
|
|
...field,
|
|
disabled: true,
|
|
};
|
|
} else {
|
|
return {
|
|
...field,
|
|
};
|
|
}
|
|
});
|
|
if (detail) {
|
|
const fields = [
|
|
...baseFields,
|
|
...getDataSourceFieldsWithExtras(t, detail.source as any),
|
|
...customFields,
|
|
] as FormFieldConfig[];
|
|
|
|
const newFields = fields.map((field) => {
|
|
return {
|
|
...field,
|
|
horizontal: true,
|
|
onChange: undefined,
|
|
};
|
|
});
|
|
setFields(newFields);
|
|
|
|
const defaultValueTemp = {
|
|
...mergeDataSourceFormValues(
|
|
DataSourceFormDefaultValues[
|
|
detail?.source as keyof typeof DataSourceFormDefaultValues
|
|
] as FieldValues,
|
|
getCommonExtraDefaultValues(),
|
|
detail as FieldValues,
|
|
),
|
|
};
|
|
setDefaultValues(defaultValueTemp);
|
|
setIsDirty(false);
|
|
}
|
|
}, [detail, customFields, onSubmit, t]);
|
|
|
|
useEffect(() => {
|
|
const instance = formRef.current;
|
|
if (!instance) return;
|
|
|
|
setIsDirty(!isEqual(instance.getValues(), defaultValues));
|
|
return instance.watchDirty((_nextIsDirty, values) => {
|
|
setIsDirty(!isEqual(values, defaultValues));
|
|
});
|
|
}, [defaultValues, fields]);
|
|
|
|
return (
|
|
<div className="px-10 py-5">
|
|
{/* <BackButton /> */}
|
|
<Card className="bg-transparent border border-border-button px-5 pt-[10px] pb-5 rounded-md mt-5">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 p-0 pb-3">
|
|
{/* <Users className="mr-2 h-5 w-5 text-[#1677ff]" /> */}
|
|
<CardTitle className="text-2xl text-text-primary flex gap-1 items-center font-normal pb-3">
|
|
{detailInfo?.icon}
|
|
{detail?.name}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<Separator className="border-border-button bg-border-button w-[calc(100%+2rem)] -translate-x-4 -translate-y-4" />
|
|
<CardContent className="p-2 flex flex-col gap-10 max-h-[calc(100vh-230px)] overflow-y-auto scrollbar-auto">
|
|
<div className="max-w-[1200px]">
|
|
<DynamicForm.Root
|
|
ref={formRef}
|
|
fields={fields}
|
|
onSubmit={(data) => handleAddOk(data)}
|
|
defaultValues={defaultValues}
|
|
/>
|
|
</div>
|
|
<div className="max-w-[1200px] flex justify-end gap-2">
|
|
{(detail?.source === DataSourceKey.REST_API ||
|
|
detail?.source === DataSourceKey.BIGQUERY) && (
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={handleTest}
|
|
disabled={testLoading}
|
|
loading={testLoading}
|
|
>
|
|
{t('setting.restApiTestConnection')}
|
|
</Button>
|
|
)}
|
|
<Button
|
|
type="button"
|
|
onClick={handlePrimaryAction}
|
|
disabled={addLoading || statusUpdateLoading}
|
|
loading={
|
|
(addLoading && actionMode === 'save') ||
|
|
(statusUpdateLoading && actionMode !== 'save')
|
|
}
|
|
>
|
|
{primaryActionLabel}
|
|
</Button>
|
|
</div>
|
|
<section className="flex flex-col gap-2">
|
|
<div className="text-2xl text-text-primary mb-2">
|
|
{t('setting.log')}
|
|
</div>
|
|
<DataSourceLogsTable autoRefresh={isConnectorActive} />
|
|
</section>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
};
|
|
export default SourceDetailPage;
|