feat: Added UI functions related to data-flow knowledge base #3221 (#10038)

### What problem does this PR solve?

feat: Added UI functions related to data-flow knowledge base #3221

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
chanx
2025-09-11 09:51:18 +08:00
committed by GitHub
parent df8d31451b
commit 8a09f07186
64 changed files with 5079 additions and 81 deletions

View File

@ -0,0 +1,9 @@
export enum LogTabs {
FILE_LOGS = 'fileLogs',
DATASET_LOGS = 'datasetLogs',
}
export enum processingType {
knowledgeGraph = 'knowledgeGraph',
raptor = 'raptor',
}

View File

@ -0,0 +1,91 @@
import { FilterButton } from '@/components/list-filter-bar';
import {
CheckboxFormMultipleProps,
FilterPopover,
} from '@/components/list-filter-bar/filter-popover';
import { Button } from '@/components/ui/button';
import { SearchInput } from '@/components/ui/input';
import { cn } from '@/lib/utils';
import { ChangeEventHandler, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { LogTabs } from './dataset-common';
interface IProps {
searchString?: string;
onSearchChange?: ChangeEventHandler<HTMLInputElement>;
active?: (typeof LogTabs)[keyof typeof LogTabs];
setActive?: (active: (typeof LogTabs)[keyof typeof LogTabs]) => void;
}
const DatasetFilter = (
props: IProps & Omit<CheckboxFormMultipleProps, 'setOpen'>,
) => {
const {
searchString,
onSearchChange,
value,
onChange,
filters,
onOpenChange,
active = LogTabs.FILE_LOGS,
setActive,
...rest
} = props;
const { t } = useTranslation();
const filterCount = useMemo(() => {
return typeof value === 'object' && value !== null
? Object.values(value).reduce((pre, cur) => {
return pre + cur.length;
}, 0)
: 0;
}, [value]);
return (
<div className="flex items-center justify-between mb-4">
<div className="flex space-x-2 bg-bg-card p-1 rounded-md">
<Button
className={cn(
'px-4 py-2 rounded-md hover:text-text-primary hover:bg-bg-base',
{
'bg-bg-base text-text-primary': active === LogTabs.FILE_LOGS,
'bg-transparent text-text-secondary ':
active !== LogTabs.FILE_LOGS,
},
)}
onClick={() => setActive?.(LogTabs.FILE_LOGS)}
>
{t('knowledgeDetails.fileLogs')}
</Button>
<Button
className={cn(
'px-4 py-2 rounded-md hover:text-text-primary hover:bg-bg-base',
{
'bg-bg-base text-text-primary': active === LogTabs.DATASET_LOGS,
'bg-transparent text-text-secondary ':
active !== LogTabs.DATASET_LOGS,
},
)}
onClick={() => setActive?.(LogTabs.DATASET_LOGS)}
>
{t('knowledgeDetails.datasetLogs')}
</Button>
</div>
<div className="flex items-center space-x-2">
<FilterPopover
value={value}
onChange={onChange}
filters={filters}
onOpenChange={onOpenChange}
>
<FilterButton count={filterCount}></FilterButton>
</FilterPopover>
<SearchInput
value={searchString}
onChange={onSearchChange}
className="w-32"
></SearchInput>
</div>
</div>
);
};
export { DatasetFilter };

View File

@ -0,0 +1,156 @@
import {
CircleQuestionMark,
Cpu,
FileChartLine,
HardDriveDownload,
} from 'lucide-react';
import { FC, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { LogTabs } from './dataset-common';
import { DatasetFilter } from './dataset-filter';
import FileLogsTable from './overview-table';
interface StatCardProps {
title: string;
value: number;
icon: JSX.Element;
children?: JSX.Element;
}
const StatCard: FC<StatCardProps> = ({ title, value, children, icon }) => {
return (
<div className="bg-bg-card p-4 rounded-lg border border-border flex flex-col gap-2">
<div className="flex items-center justify-between">
<h3 className="flex items-center gap-1 text-sm font-medium text-text-secondary">
{title}
<CircleQuestionMark size={12} />
</h3>
{icon}
</div>
<div className="text-2xl font-bold text-text-primary">{value}</div>
<div className="h-12 w-full flex items-center">
<div className="flex-1">{children}</div>
</div>
</div>
);
};
interface CardFooterProcessProps {
total: number;
completed: number;
success: number;
failed: number;
}
const CardFooterProcess: FC<CardFooterProcessProps> = ({
total,
completed,
success,
failed,
}) => {
const { t } = useTranslation();
const successPrecentage = (success / total) * 100;
const failedPrecentage = (failed / total) * 100;
return (
<div className="flex items-center flex-col gap-2">
<div className="flex justify-between w-full text-sm text-text-secondary">
<div className="flex items-center gap-2">
<div className="flex items-center gap-1">
{success}
<span>{t('knowledgeDetails.success')}</span>
</div>
<div className="flex items-center gap-1">
{failed}
<span>{t('knowledgeDetails.failed')}</span>
</div>
</div>
<div className="flex items-center gap-1">
{completed}
<span>{t('knowledgeDetails.completed')}</span>
</div>
</div>
<div className="w-full flex rounded-full h-3 bg-bg-card text-sm font-bold text-text-primary">
<div
className=" rounded-full h-3 bg-accent-primary"
style={{ width: successPrecentage + '%' }}
></div>
<div
className=" rounded-full h-3 bg-state-error"
style={{ width: failedPrecentage + '%' }}
></div>
</div>
</div>
);
};
const FileLogsPage: FC = () => {
const { t } = useTranslation();
const [active, setActive] = useState<(typeof LogTabs)[keyof typeof LogTabs]>(
LogTabs.FILE_LOGS,
);
const mockData = Array(30)
.fill(0)
.map((_, i) => ({
id: i === 0 ? '#952734' : `14`,
fileName: 'PRD for DealBees 1.2 (1).txt',
source: 'GitHub',
pipeline: i === 0 ? 'data demo for...' : i === 1 ? 'test' : 'kikis demo',
startDate: '14/03/2025 14:53:39',
task: i === 0 ? 'Parse' : 'Parser',
status:
i === 0
? 'Success'
: i === 1
? 'Failed'
: i === 2
? 'Running'
: 'Pending',
}));
const pagination = {
current: 1,
pageSize: 30,
total: 100,
};
const changeActiveLogs = (active: (typeof LogTabs)[keyof typeof LogTabs]) => {
setActive(active);
};
const handlePaginationChange = (page: number, pageSize: number) => {
console.log('Pagination changed:', { page, pageSize });
};
return (
<div className="p-5 min-w-[880px] border-border border rounded-lg mr-5">
{/* Stats Cards */}
<div className="grid grid-cols-3 md:grid-cols-3 gap-4 mb-6">
<StatCard title="Total Files" value={2827} icon={<FileChartLine />}>
<div>+7% from last week</div>
</StatCard>
<StatCard title="Downloading" value={28} icon={<HardDriveDownload />}>
<CardFooterProcess
total={100}
success={8}
failed={2}
completed={15}
/>
</StatCard>
<StatCard title="Processing" value={156} icon={<Cpu />}>
<CardFooterProcess total={20} success={8} failed={2} completed={15} />
</StatCard>
</div>
{/* Tabs & Search */}
<DatasetFilter active={active} setActive={changeActiveLogs} />
{/* Table */}
<FileLogsTable
data={mockData}
pagination={pagination}
setPagination={handlePaginationChange}
pageCount={10}
active={active}
/>
</div>
);
};
export default FileLogsPage;

View File

@ -0,0 +1,384 @@
import FileStatusBadge from '@/components/file-status-badge';
import { FileIcon } from '@/components/icon-font';
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
import SvgIcon from '@/components/svg-icon';
import { Button } from '@/components/ui/button';
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { useTranslate } from '@/hooks/common-hooks';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import ProcessLogModal from '@/pages/datasets/process-log-modal';
import {
ColumnDef,
ColumnFiltersState,
SortingState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
import { TFunction } from 'i18next';
import { ClipboardList, Eye } from 'lucide-react';
import { Dispatch, FC, SetStateAction, useMemo, useState } from 'react';
import { LogTabs, processingType } from './dataset-common';
interface DocumentLog {
id: string;
fileName: string;
source: string;
pipeline: string;
startDate: string;
task: string;
status: 'Success' | 'Failed' | 'Running' | 'Pending';
}
interface FileLogsTableProps {
data: DocumentLog[];
pageCount: number;
pagination: {
current: number;
pageSize: number;
total: number;
};
setPagination: (pagination: { page: number; pageSize: number }) => void;
loading?: boolean;
active: (typeof LogTabs)[keyof typeof LogTabs];
}
export const getFileLogsTableColumns = (
t: TFunction<'translation', string>,
setIsModalVisible: Dispatch<SetStateAction<boolean>>,
navigateToDataflowResult: (
id: string,
knowledgeId?: string | undefined,
) => () => void,
) => {
// const { t } = useTranslate('knowledgeDetails');
const columns: ColumnDef<DocumentLog>[] = [
{
id: 'select',
header: ({ table }) => (
<input
type="checkbox"
checked={table.getIsAllRowsSelected()}
onChange={table.getToggleAllRowsSelectedHandler()}
className="rounded bg-gray-900 text-blue-500 focus:ring-blue-500"
/>
),
cell: ({ row }) => (
<input
type="checkbox"
checked={row.getIsSelected()}
onChange={row.getToggleSelectedHandler()}
className="rounded border-gray-600 bg-gray-900 text-blue-500 focus:ring-blue-500"
/>
),
},
{
accessorKey: 'id',
header: 'ID',
cell: ({ row }) => (
<div className="text-text-primary">{row.original.id}</div>
),
},
{
accessorKey: 'fileName',
header: t('fileName'),
cell: ({ row }) => (
<div
className="flex items-center gap-2 text-text-primary"
onClick={navigateToDataflowResult(
row.original.id,
row.original.kb_id,
)}
>
<FileIcon name={row.original.fileName}></FileIcon>
{row.original.fileName}
</div>
),
},
{
accessorKey: 'source',
header: t('source'),
cell: ({ row }) => (
<div className="text-text-primary">{row.original.source}</div>
),
},
{
accessorKey: 'pipeline',
header: t('dataPipeline'),
cell: ({ row }) => (
<div className="flex items-center gap-2 text-text-primary">
<RAGFlowAvatar
avatar={null}
name={row.original.pipeline}
className="size-4"
/>
{row.original.pipeline}
</div>
),
},
{
accessorKey: 'startDate',
header: t('startDate'),
cell: ({ row }) => (
<div className="text-text-primary">{row.original.startDate}</div>
),
},
{
accessorKey: 'task',
header: t('task'),
cell: ({ row }) => (
<div className="text-text-primary">{row.original.task}</div>
),
},
{
accessorKey: 'status',
header: t('status'),
cell: ({ row }) => <FileStatusBadge status={row.original.status} />,
},
{
id: 'operations',
header: t('operations'),
cell: ({ row }) => (
<div className="flex justify-start space-x-2">
<Button
variant="ghost"
size="sm"
className="p-1"
onClick={() => {
setIsModalVisible(true);
}}
>
<Eye />
</Button>
<Button
variant="ghost"
size="sm"
className="p-1"
onClick={navigateToDataflowResult(row.original.id)}
>
<ClipboardList />
</Button>
</div>
),
},
];
return columns;
};
export const getDatasetLogsTableColumns = (
t: TFunction<'translation', string>,
setIsModalVisible: Dispatch<SetStateAction<boolean>>,
) => {
// const { t } = useTranslate('knowledgeDetails');
const columns: ColumnDef<DocumentLog>[] = [
{
id: 'select',
header: ({ table }) => (
<input
type="checkbox"
checked={table.getIsAllRowsSelected()}
onChange={table.getToggleAllRowsSelectedHandler()}
className="rounded bg-gray-900 text-blue-500 focus:ring-blue-500"
/>
),
cell: ({ row }) => (
<input
type="checkbox"
checked={row.getIsSelected()}
onChange={row.getToggleSelectedHandler()}
className="rounded border-gray-600 bg-gray-900 text-blue-500 focus:ring-blue-500"
/>
),
},
{
accessorKey: 'id',
header: 'ID',
cell: ({ row }) => (
<div className="text-text-primary">{row.original.id}</div>
),
},
{
accessorKey: 'startDate',
header: t('startDate'),
cell: ({ row }) => (
<div className="text-text-primary">{row.original.startDate}</div>
),
},
{
accessorKey: 'processingType',
header: t('processingType'),
cell: ({ row }) => (
<div className="flex items-center gap-2 text-text-primary">
{processingType.knowledgeGraph === row.original.processingType && (
<SvgIcon name={`data-flow/knowledgegraph`} width={24}></SvgIcon>
)}
{processingType.raptor === row.original.processingType && (
<SvgIcon name={`data-flow/raptor`} width={24}></SvgIcon>
)}
{row.original.processingType}
</div>
),
},
{
accessorKey: 'status',
header: t('status'),
cell: ({ row }) => <FileStatusBadge status={row.original.status} />,
},
{
id: 'operations',
header: t('operations'),
cell: ({ row }) => (
<div className="flex justify-start space-x-2">
<Button
variant="ghost"
size="sm"
className="p-1"
onClick={() => {
setIsModalVisible(true);
}}
>
<Eye />
</Button>
</div>
),
},
];
return columns;
};
const FileLogsTable: FC<FileLogsTableProps> = ({
data,
pagination,
setPagination,
loading,
active = LogTabs.FILE_LOGS,
}) => {
const [sorting, setSorting] = useState<SortingState>([]);
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const [rowSelection, setRowSelection] = useState({});
const { t } = useTranslate('knowledgeDetails');
const [isModalVisible, setIsModalVisible] = useState(false);
const { navigateToDataflowResult } = useNavigatePage();
const columns = useMemo(() => {
console.log('columns', active);
return active === LogTabs.FILE_LOGS
? getFileLogsTableColumns(t, setIsModalVisible, navigateToDataflowResult)
: getDatasetLogsTableColumns(t, setIsModalVisible);
}, [active, t]);
const currentPagination = useMemo(
() => ({
pageIndex: (pagination.current || 1) - 1,
pageSize: pagination.pageSize || 10,
}),
[pagination],
);
const table = useReactTable({
data,
columns,
manualPagination: true,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onRowSelectionChange: setRowSelection,
state: {
sorting,
columnFilters,
rowSelection,
pagination: currentPagination,
},
pageCount: pagination.total
? Math.ceil(pagination.total / pagination.pageSize)
: 0,
});
const taskInfo = {
taskId: '#9527',
fileName: 'PRD for DealBees 1.2 (1).text',
fileSize: '2.4G',
source: 'Github',
task: 'Parse',
state: 'Running',
startTime: '14/03/2025 14:53:39',
duration: '800',
details: 'PRD for DealBees 1.2 (1).text',
};
return (
<div className="w-full h-[calc(100vh-350px)]">
<Table rootClassName="max-h-[calc(100vh-380px)]">
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody className="relative">
{table.getRowModel().rows.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && 'selected'}
className="group"
>
{row.getVisibleCells().map((cell) => (
<TableCell
key={cell.id}
className={cell.column.columnDef.meta?.cellClassName}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
<div className="flex items-center justify-end py-4 absolute bottom-3 right-12">
<div className="space-x-2">
<RAGFlowPagination
{...{ current: pagination.current, pageSize: pagination.pageSize }}
total={pagination.total}
onChange={(page, pageSize) => setPagination({ page, pageSize })}
/>
</div>
</div>
<ProcessLogModal
visible={isModalVisible}
onCancel={() => setIsModalVisible(false)}
taskInfo={taskInfo}
/>
</div>
);
};
export default FileLogsTable;

View File

@ -0,0 +1,31 @@
import { Button } from '@/components/ui/button';
import { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { NaiveConfiguration } from './naive';
import { SavingButton } from './saving-button';
export function ChunkMethodForm() {
const form = useFormContext();
const { t } = useTranslation();
return (
<section className="h-full flex flex-col">
<div className="overflow-auto flex-1 min-h-0">
<NaiveConfiguration></NaiveConfiguration>
</div>
<div className="text-right pt-4 flex justify-end gap-3">
<Button
type="reset"
className="bg-transparent text-color-white hover:bg-transparent border-gray-500 border-[1px]"
onClick={() => {
form.reset();
}}
>
{t('knowledgeConfiguration.cancel')}
</Button>
<SavingButton></SavingButton>
</div>
</section>
);
}

View File

@ -0,0 +1,155 @@
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
import { SliderInputFormField } from '@/components/slider-input-form-field';
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { MultiSelect } from '@/components/ui/multi-select';
import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks';
import { Flex, Form, InputNumber, Select, Slider, Space } from 'antd';
import DOMPurify from 'dompurify';
import { useFormContext, useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
export const TagSetItem = () => {
const { t } = useTranslation();
const form = useFormContext();
const { list: knowledgeList } = useFetchKnowledgeList(true);
const knowledgeOptions = knowledgeList
.filter((x) => x.parser_id === 'tag')
.map((x) => ({
label: x.name,
value: x.id,
icon: () => (
<Space>
<RAGFlowAvatar
name={x.name}
avatar={x.avatar}
className="size-4"
></RAGFlowAvatar>
</Space>
),
}));
return (
<FormField
control={form.control}
name="parser_config.tag_kb_ids"
render={({ field }) => (
<FormItem className=" items-center space-y-0 ">
<div className="flex items-center">
<FormLabel
className="text-sm text-muted-foreground whitespace-nowrap w-1/4"
tooltip={
<div
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(
t('knowledgeConfiguration.tagSetTip'),
),
}}
></div>
}
>
{t('knowledgeConfiguration.tagSet')}
</FormLabel>
<div className="w-3/4">
<FormControl>
<MultiSelect
options={knowledgeOptions}
onValueChange={field.onChange}
placeholder={t('chat.knowledgeBasesMessage')}
variant="inverted"
maxCount={10}
{...field}
/>
</FormControl>
</div>
</div>
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
)}
/>
);
return (
<Form.Item
label={t('knowledgeConfiguration.tagSet')}
name={['parser_config', 'tag_kb_ids']}
tooltip={
<div
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(t('knowledgeConfiguration.tagSetTip')),
}}
></div>
}
rules={[
{
message: t('chat.knowledgeBasesMessage'),
type: 'array',
},
]}
>
<Select
mode="multiple"
options={knowledgeOptions}
placeholder={t('chat.knowledgeBasesMessage')}
></Select>
</Form.Item>
);
};
export const TopNTagsItem = () => {
const { t } = useTranslation();
return (
<SliderInputFormField
name={'parser_config.topn_tags'}
label={t('knowledgeConfiguration.topnTags')}
max={10}
min={1}
defaultValue={3}
></SliderInputFormField>
);
return (
<Form.Item label={t('knowledgeConfiguration.topnTags')}>
<Flex gap={20} align="center">
<Flex flex={1}>
<Form.Item
name={['parser_config', 'topn_tags']}
noStyle
initialValue={3}
>
<Slider max={10} min={1} style={{ width: '100%' }} />
</Form.Item>
</Flex>
<Form.Item name={['parser_config', 'topn_tags']} noStyle>
<InputNumber max={10} min={1} />
</Form.Item>
</Flex>
</Form.Item>
);
};
export function TagItems() {
const form = useFormContext();
const ids: string[] = useWatch({
control: form.control,
name: 'parser_config.tag_kb_ids',
});
return (
<>
<TagSetItem></TagSetItem>
{Array.isArray(ids) && ids.length > 0 && <TopNTagsItem></TopNTagsItem>}
</>
);
}

View File

@ -0,0 +1,14 @@
import { FormContainerProps } from '@/components/form-container';
import { cn } from '@/lib/utils';
import { PropsWithChildren } from 'react';
export function ConfigurationFormContainer({
children,
className,
}: FormContainerProps) {
return <section className={cn('space-y-4', className)}>{children}</section>;
}
export function MainContainer({ children }: PropsWithChildren) {
return <section className="space-y-5">{children}</section>;
}

View File

@ -0,0 +1,328 @@
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Radio } from '@/components/ui/radio';
import { RAGFlowSelect } from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
import { useTranslate } from '@/hooks/common-hooks';
import { cn } from '@/lib/utils';
import { ArrowUpRight } from 'lucide-react';
import { useFormContext } from 'react-hook-form';
import {
useHasParsedDocument,
useSelectChunkMethodList,
useSelectEmbeddingModelOptions,
} from '../hooks';
export function ChunkMethodItem() {
const { t } = useTranslate('knowledgeConfiguration');
const form = useFormContext();
// const handleChunkMethodSelectChange = useHandleChunkMethodSelectChange(form);
const parserList = useSelectChunkMethodList();
return (
<FormField
control={form.control}
name={'parser_id'}
render={({ field }) => (
<FormItem className=" items-center space-y-0 ">
<div className="flex items-center">
<FormLabel
required
tooltip={t('chunkMethodTip')}
className="text-sm text-muted-foreground whitespace-wrap w-1/4"
>
{t('chunkMethod')}
</FormLabel>
<div className="w-3/4 ">
<FormControl>
<RAGFlowSelect
{...field}
options={parserList}
placeholder={t('chunkMethodPlaceholder')}
// onChange={handleChunkMethodSelectChange}
/>
</FormControl>
</div>
</div>
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
)}
/>
);
}
export function EmbeddingModelItem({ line = 1 }: { line?: 1 | 2 }) {
const { t } = useTranslate('knowledgeConfiguration');
const form = useFormContext();
const embeddingModelOptions = useSelectEmbeddingModelOptions();
const disabled = useHasParsedDocument();
return (
<FormField
control={form.control}
name={'embd_id'}
render={({ field }) => (
<FormItem className=" items-center space-y-0 ">
<div className={cn({ 'flex items-center': line === 1 })}>
<FormLabel
required
tooltip={t('embeddingModelTip')}
className={cn('text-sm whitespace-wrap ', {
'w-1/4': line === 1,
})}
>
{t('embeddingModel')}
</FormLabel>
<div
className={cn('text-muted-foreground', { 'w-3/4': line === 1 })}
>
<FormControl>
<RAGFlowSelect
{...field}
options={embeddingModelOptions}
disabled={disabled}
placeholder={t('embeddingModelPlaceholder')}
/>
</FormControl>
</div>
</div>
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
)}
/>
);
}
export function ParseTypeItem() {
const { t } = useTranslate('knowledgeConfiguration');
const form = useFormContext();
return (
<FormField
control={form.control}
name={'parseType'}
render={({ field }) => (
<FormItem className=" items-center space-y-0 ">
<div className="">
<FormLabel
tooltip={t('parseTypeTip')}
className="text-sm whitespace-wrap "
>
{t('parseType')}
</FormLabel>
<div className="text-muted-foreground">
<FormControl>
<Radio.Group {...field}>
<div className="w-3/4 flex gap-2 justify-between text-muted-foreground">
<Radio value={1}>{t('builtIn')}</Radio>
<Radio value={2}>{t('manualSetup')}</Radio>
</div>
</Radio.Group>
</FormControl>
</div>
</div>
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
)}
/>
);
}
export function DataFlowItem() {
const { t } = useTranslate('knowledgeConfiguration');
const form = useFormContext();
return (
<FormField
control={form.control}
name={'data_flow'}
render={({ field }) => (
<FormItem className=" items-center space-y-0 ">
<div className="">
<div className="flex gap-2 justify-between ">
<FormLabel
tooltip={t('dataFlowTip')}
className="text-sm text-text-primary whitespace-wrap "
>
{t('dataFlow')}
</FormLabel>
<div className="text-sm flex text-text-primary">
{t('buildItFromScratch')}
<ArrowUpRight size={14} />
</div>
</div>
<div className="text-muted-foreground">
<FormControl>
<RAGFlowSelect
{...field}
placeholder={t('dataFlowPlaceholder')}
options={[{ value: '0', label: t('dataFlowDefault') }]}
/>
</FormControl>
</div>
</div>
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
)}
/>
);
}
export function DataExtractKnowledgeItem() {
const { t } = useTranslate('knowledgeConfiguration');
const form = useFormContext();
return (
<>
{' '}
<FormField
control={form.control}
name={'extractKnowledgeGraph'}
render={({ field }) => (
<FormItem className=" items-center space-y-0 ">
<div className="">
<FormLabel
tooltip={t('extractKnowledgeGraphTip')}
className="text-sm whitespace-wrap "
>
{t('extractKnowledgeGraph')}
</FormLabel>
<div className="text-muted-foreground">
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</div>
</div>
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
)}
/>{' '}
<FormField
control={form.control}
name={'useRAPTORToEnhanceRetrieval'}
render={({ field }) => (
<FormItem className=" items-center space-y-0 ">
<div className="">
<FormLabel
tooltip={t('useRAPTORToEnhanceRetrievalTip')}
className="text-sm whitespace-wrap "
>
{t('useRAPTORToEnhanceRetrieval')}
</FormLabel>
<div className="text-muted-foreground">
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</div>
</div>
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
)}
/>
</>
);
}
export function TeamItem() {
const { t } = useTranslate('knowledgeConfiguration');
const form = useFormContext();
return (
<FormField
control={form.control}
name={'team'}
render={({ field }) => (
<FormItem className=" items-center space-y-0 ">
<div className="">
<FormLabel
tooltip={t('teamTip')}
className="text-sm whitespace-wrap "
>
<span className="text-destructive mr-1"> *</span>
{t('team')}
</FormLabel>
<div className="text-muted-foreground">
<FormControl>
<RAGFlowSelect
{...field}
placeholder={t('teamPlaceholder')}
options={[{ value: '0', label: t('teamDefault') }]}
/>
</FormControl>
</div>
</div>
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
)}
/>
);
}
export function EnableAutoGenerateItem() {
const { t } = useTranslate('knowledgeConfiguration');
const form = useFormContext();
return (
<FormField
control={form.control}
name={'enableAutoGenerate'}
render={({ field }) => (
<FormItem className=" items-center space-y-0 ">
<div className="flex items-center">
<FormLabel
tooltip={t('enableAutoGenerateTip')}
className="text-sm whitespace-wrap w-1/4"
>
{t('enableAutoGenerate')}
</FormLabel>
<div className="text-muted-foreground w-3/4">
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</div>
</div>
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
)}
/>
);
}

View File

@ -0,0 +1,73 @@
import { z } from 'zod';
export const formSchema = z.object({
name: z.string().min(1, {
message: 'Username must be at least 2 characters.',
}),
description: z.string().min(2, {
message: 'Username must be at least 2 characters.',
}),
// avatar: z.instanceof(File),
avatar: z.any().nullish(),
permission: z.string().optional(),
parser_id: z.string(),
embd_id: z.string(),
parser_config: z
.object({
layout_recognize: z.string(),
chunk_token_num: z.number(),
delimiter: z.string(),
auto_keywords: z.number().optional(),
auto_questions: z.number().optional(),
html4excel: z.boolean(),
tag_kb_ids: z.array(z.string()).nullish(),
topn_tags: z.number().optional(),
raptor: z
.object({
use_raptor: z.boolean().optional(),
prompt: z.string().optional(),
max_token: z.number().optional(),
threshold: z.number().optional(),
max_cluster: z.number().optional(),
random_seed: z.number().optional(),
})
.refine(
(data) => {
if (data.use_raptor && !data.prompt) {
return false;
}
return true;
},
{
message: 'Prompt is required',
path: ['prompt'],
},
),
graphrag: z
.object({
use_graphrag: z.boolean().optional(),
entity_types: z.array(z.string()).optional(),
method: z.string().optional(),
resolution: z.boolean().optional(),
community: z.boolean().optional(),
})
.refine(
(data) => {
if (
data.use_graphrag &&
(!data.entity_types || data.entity_types.length === 0)
) {
return false;
}
return true;
},
{
message: 'Please enter Entity types',
path: ['entity_types'],
},
),
})
.optional(),
pagerank: z.number(),
// icon: z.array(z.instanceof(File)),
});

View File

@ -0,0 +1,92 @@
import { AvatarUpload } from '@/components/avatar-upload';
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { EmbeddingModelItem } from './configuration/common-item';
import { PermissionFormField } from './permission-form-field';
export function GeneralForm() {
const form = useFormContext();
const { t } = useTranslation();
return (
<section className="space-y-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem className="items-center space-y-0">
<div className="flex">
<FormLabel className="text-sm text-muted-foreground whitespace-nowrap w-1/4">
<span className="text-red-600">*</span>
{t('common.name')}
</FormLabel>
<FormControl className="w-3/4">
<Input {...field}></Input>
</FormControl>
</div>
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
)}
/>
<FormField
control={form.control}
name="avatar"
render={({ field }) => (
<FormItem className="items-center space-y-0">
<div className="flex">
<FormLabel className="text-sm text-muted-foreground whitespace-nowrap w-1/4">
{t('setting.avatar')}
</FormLabel>
<FormControl className="w-3/4">
<AvatarUpload {...field}></AvatarUpload>
</FormControl>
</div>
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => {
// null initialize empty string
if (typeof field.value === 'object' && !field.value) {
form.setValue('description', ' ');
}
return (
<FormItem className="items-center space-y-0">
<div className="flex">
<FormLabel className="text-sm text-muted-foreground whitespace-nowrap w-1/4">
{t('flow.description')}
</FormLabel>
<FormControl className="w-3/4">
<Input {...field}></Input>
</FormControl>
</div>
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
);
}}
/>
<PermissionFormField></PermissionFormField>
<EmbeddingModelItem></EmbeddingModelItem>
</section>
);
}

View File

@ -0,0 +1,87 @@
import { LlmModelType } from '@/constants/knowledge';
import { useSetModalState } from '@/hooks/common-hooks';
import { useSelectLlmOptionsByModelType } from '@/hooks/llm-hooks';
import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-request';
import { useSelectParserList } from '@/hooks/user-setting-hooks';
import { useIsFetching } from '@tanstack/react-query';
import { pick } from 'lodash';
import { useCallback, useEffect, useState } from 'react';
import { UseFormReturn } from 'react-hook-form';
import { z } from 'zod';
import { formSchema } from './form-schema';
// The value that does not need to be displayed in the analysis method Select
const HiddenFields = ['email', 'picture', 'audio'];
export function useSelectChunkMethodList() {
const parserList = useSelectParserList();
return parserList.filter((x) => !HiddenFields.some((y) => y === x.value));
}
export function useSelectEmbeddingModelOptions() {
const allOptions = useSelectLlmOptionsByModelType();
return allOptions[LlmModelType.Embedding];
}
export function useHasParsedDocument() {
const { data: knowledgeDetails } = useFetchKnowledgeBaseConfiguration();
return knowledgeDetails.chunk_num > 0;
}
export const useFetchKnowledgeConfigurationOnMount = (
form: UseFormReturn<z.infer<typeof formSchema>, any, undefined>,
) => {
const { data: knowledgeDetails } = useFetchKnowledgeBaseConfiguration();
useEffect(() => {
const parser_config = {
...form.formState?.defaultValues?.parser_config,
...knowledgeDetails.parser_config,
};
const formValues = {
...pick({ ...knowledgeDetails, parser_config: parser_config }, [
'description',
'name',
'permission',
'embd_id',
'parser_id',
'language',
'parser_config',
'pagerank',
'avatar',
]),
};
form.reset(formValues);
}, [form, knowledgeDetails]);
return knowledgeDetails;
};
export const useSelectKnowledgeDetailsLoading = () =>
useIsFetching({ queryKey: ['fetchKnowledgeDetail'] }) > 0;
export const useRenameKnowledgeTag = () => {
const [tag, setTag] = useState<string>('');
const {
visible: tagRenameVisible,
hideModal: hideTagRenameModal,
showModal: showFileRenameModal,
} = useSetModalState();
const handleShowTagRenameModal = useCallback(
(record: string) => {
setTag(record);
showFileRenameModal();
},
[showFileRenameModal],
);
return {
initialName: tag,
tagRenameVisible,
hideTagRenameModal,
showTagRenameModal: handleShowTagRenameModal,
};
};

View File

@ -0,0 +1,88 @@
import { Form } from '@/components/ui/form';
import { DocumentParserType } from '@/constants/knowledge';
import { PermissionRole } from '@/constants/permission';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import { TopTitle } from '../dataset-title';
import { ChunkMethodForm } from './chunk-method-form';
import { formSchema } from './form-schema';
import { GeneralForm } from './general-form';
import { useFetchKnowledgeConfigurationOnMount } from './hooks';
const enum DocumentType {
DeepDOC = 'DeepDOC',
PlainText = 'Plain Text',
}
const initialEntityTypes = [
'organization',
'person',
'geo',
'event',
'category',
];
const enum MethodValue {
General = 'general',
Light = 'light',
}
export default function DatasetSettings() {
const { t } = useTranslation();
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
name: '',
parser_id: DocumentParserType.Naive,
permission: PermissionRole.Me,
parser_config: {
layout_recognize: DocumentType.DeepDOC,
chunk_token_num: 512,
delimiter: `\n`,
auto_keywords: 0,
auto_questions: 0,
html4excel: false,
topn_tags: 3,
raptor: {
use_raptor: false,
},
graphrag: {
use_graphrag: false,
entity_types: initialEntityTypes,
method: MethodValue.Light,
},
},
pagerank: 0,
},
});
useFetchKnowledgeConfigurationOnMount(form);
async function onSubmit(data: z.infer<typeof formSchema>) {
console.log('🚀 ~ DatasetSettings ~ data:', data);
}
return (
<section className="p-5 h-full flex flex-col">
<TopTitle
title={t('knowledgeDetails.configuration')}
description={t('knowledgeConfiguration.titleDescription')}
></TopTitle>
<div className="flex gap-14 flex-1 min-h-0">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6 flex-1"
>
<div className="w-[768px]">
<GeneralForm></GeneralForm>
<ChunkMethodForm></ChunkMethodForm>
</div>
</form>
</Form>
</div>
</section>
);
}

View File

@ -0,0 +1,33 @@
import GraphRagItems from '@/components/parse-configuration/graph-rag-form-fields';
import RaptorFormFields from '@/components/parse-configuration/raptor-form-fields';
import {
ConfigurationFormContainer,
MainContainer,
} from './configuration-form-container';
import { EnableAutoGenerateItem } from './configuration/common-item';
export function NaiveConfiguration() {
return (
<MainContainer>
<GraphRagItems className="border-none p-0"></GraphRagItems>
<ConfigurationFormContainer>
<RaptorFormFields></RaptorFormFields>
</ConfigurationFormContainer>
<EnableAutoGenerateItem />
{/* <ConfigurationFormContainer>
<ChunkMethodItem></ChunkMethodItem>
<LayoutRecognizeFormField></LayoutRecognizeFormField>
<MaxTokenNumberFormField initialValue={512}></MaxTokenNumberFormField>
<DelimiterFormField></DelimiterFormField>
</ConfigurationFormContainer>
<ConfigurationFormContainer>
<PageRankFormField></PageRankFormField>
<AutoKeywordsFormField></AutoKeywordsFormField>
<AutoQuestionsFormField></AutoQuestionsFormField>
<ExcelToHtmlFormField></ExcelToHtmlFormField>
<TagItems></TagItems>
</ConfigurationFormContainer> */}
</MainContainer>
);
}

View File

@ -0,0 +1,29 @@
import { SelectWithSearch } from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { PermissionRole } from '@/constants/permission';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
export function PermissionFormField() {
const { t } = useTranslation();
const teamOptions = useMemo(() => {
return Object.values(PermissionRole).map((x) => ({
label: t('knowledgeConfiguration.' + x),
value: x,
}));
}, [t]);
return (
<RAGFlowFormItem
name="permission"
label={t('knowledgeConfiguration.permissions')}
tooltip={t('knowledgeConfiguration.permissionsTip')}
horizontal
>
<SelectWithSearch
options={teamOptions}
triggerClassName="w-3/4"
></SelectWithSearch>
</RAGFlowFormItem>
);
}

View File

@ -0,0 +1,82 @@
import { ButtonLoading } from '@/components/ui/button';
import { useUpdateKnowledge } from '@/hooks/use-knowledge-request';
import { useMemo } from 'react';
import { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useParams } from 'umi';
export function GeneralSavingButton() {
const form = useFormContext();
const { saveKnowledgeConfiguration, loading: submitLoading } =
useUpdateKnowledge();
const { id: kb_id } = useParams();
const { t } = useTranslation();
const defaultValues = useMemo(
() => form.formState.defaultValues ?? {},
[form.formState.defaultValues],
);
const parser_id = defaultValues['parser_id'];
return (
<ButtonLoading
type="button"
loading={submitLoading}
onClick={() => {
(async () => {
let isValidate = await form.trigger('name');
const { name, description, permission, avatar } = form.getValues();
if (isValidate) {
saveKnowledgeConfiguration({
kb_id,
parser_id,
name,
description,
avatar,
permission,
});
}
})();
}}
>
{t('knowledgeConfiguration.save')}
</ButtonLoading>
);
}
export function SavingButton() {
const { saveKnowledgeConfiguration, loading: submitLoading } =
useUpdateKnowledge();
const form = useFormContext();
const { id: kb_id } = useParams();
const { t } = useTranslation();
return (
<ButtonLoading
loading={submitLoading}
onClick={() => {
(async () => {
try {
let beValid = await form.formControl.trigger();
if (beValid) {
form.handleSubmit(async (values) => {
console.log('saveKnowledgeConfiguration: ', values);
delete values['avatar'];
await saveKnowledgeConfiguration({
kb_id,
...values,
});
})();
}
} catch (e) {
console.log(e);
} finally {
}
})();
}}
>
{t('knowledgeConfiguration.save')}
</ButtonLoading>
);
}

View File

@ -0,0 +1,68 @@
import SvgIcon from '@/components/svg-icon';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { t } from 'i18next';
import { lowerFirst, toLower } from 'lodash';
import { WandSparkles } from 'lucide-react';
const MenuItem: React.FC<{ name: 'KnowledgeGraph' | 'Raptor' }> = ({
name,
}) => {
console.log(name, 'pppp');
return (
<div className="flex items-start gap-2 flex-col">
<div className="flex justify-start text-text-primary items-center gap-2">
<SvgIcon name={`data-flow/${toLower(name)}`} width={24}></SvgIcon>
{t(`knowledgeDetails.${lowerFirst(name)}`)}
</div>
<div className="text-text-secondary text-sm">
{t(`knowledgeDetails.generate${name}`)}
</div>
</div>
);
};
const Generate: React.FC = () => {
return (
<div className="generate">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant={'transparent'}>
<WandSparkles className="mr-2" />
{t('knowledgeDetails.generate')}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-[380px] p-2 ">
<DropdownMenuItem className="border cursor-pointer p-2 rounded-md hover:border-accent-primary hover:bg-[rgba(59,160,92,0.1)]">
<MenuItem name="KnowledgeGraph" />
</DropdownMenuItem>
<DropdownMenuItem
className="border cursor-pointer p-2 rounded-md mt-3 hover:border-accent-primary hover:bg-[rgba(59,160,92,0.1)]"
onSelect={(e) => {
e.preventDefault();
}}
onClick={(e) => {
e.stopPropagation();
}}
>
<MenuItem name="Raptor" />
{/* <div className="flex items-start gap-2 flex-col">
<div className="flex items-center gap-2">
<SvgIcon name={`data-flow/raptor`} width={24}></SvgIcon>
{t('knowledgeDetails.raptor')}
</div>
<div>{t('knowledgeDetails.generateRaptor')}</div>
</div> */}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
};
export default Generate;

View File

@ -15,6 +15,7 @@ import { useFetchDocumentList } from '@/hooks/use-document-request';
import { Upload } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { DatasetTable } from './dataset-table';
import Generate from './generate';
import { useBulkOperateDataset } from './use-bulk-operate-dataset';
import { useCreateEmptyDocument } from './use-create-empty-document';
import { useSelectDatasetFilters } from './use-select-filters';
@ -59,69 +60,74 @@ export default function Dataset() {
setRowSelection,
});
return (
<section className="p-5 min-w-[880px]">
<ListFilterBar
title="Dataset"
onSearchChange={handleInputChange}
searchString={searchString}
value={filterValue}
onChange={handleFilterSubmit}
onOpenChange={onOpenChange}
filters={filters}
leftPanel={
<div className="items-start">
<div className="pb-1">{t('knowledgeDetails.dataset')}</div>
<div className="text-text-sub-title-invert text-sm">
{t('knowledgeDetails.datasetDescription')}
<>
<div className="absolute top-4 right-5">
<Generate />
</div>
<section className="p-5 min-w-[880px]">
<ListFilterBar
title="Dataset"
onSearchChange={handleInputChange}
searchString={searchString}
value={filterValue}
onChange={handleFilterSubmit}
onOpenChange={onOpenChange}
filters={filters}
leftPanel={
<div className="items-start">
<div className="pb-1">{t('knowledgeDetails.dataset')}</div>
<div className="text-text-sub-title-invert text-sm">
{t('knowledgeDetails.datasetDescription')}
</div>
</div>
</div>
}
>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size={'sm'}>
<Upload />
{t('knowledgeDetails.addFile')}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuItem onClick={showDocumentUploadModal}>
{t('fileManager.uploadFile')}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={showCreateModal}>
{t('knowledgeDetails.emptyFiles')}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</ListFilterBar>
{rowSelectionIsEmpty || (
<BulkOperateBar list={list} count={selectedCount}></BulkOperateBar>
)}
<DatasetTable
documents={documents}
pagination={pagination}
setPagination={setPagination}
rowSelection={rowSelection}
setRowSelection={setRowSelection}
loading={loading}
></DatasetTable>
{documentUploadVisible && (
<FileUploadDialog
hideModal={hideDocumentUploadModal}
onOk={onDocumentUploadOk}
loading={documentUploadLoading}
showParseOnCreation
></FileUploadDialog>
)}
{createVisible && (
<RenameDialog
hideModal={hideCreateModal}
onOk={onCreateOk}
loading={createLoading}
title={'File Name'}
></RenameDialog>
)}
</section>
}
>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size={'sm'}>
<Upload />
{t('knowledgeDetails.addFile')}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuItem onClick={showDocumentUploadModal}>
{t('fileManager.uploadFile')}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={showCreateModal}>
{t('knowledgeDetails.emptyFiles')}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</ListFilterBar>
{rowSelectionIsEmpty || (
<BulkOperateBar list={list} count={selectedCount}></BulkOperateBar>
)}
<DatasetTable
documents={documents}
pagination={pagination}
setPagination={setPagination}
rowSelection={rowSelection}
setRowSelection={setRowSelection}
loading={loading}
></DatasetTable>
{documentUploadVisible && (
<FileUploadDialog
hideModal={hideDocumentUploadModal}
onOk={onDocumentUploadOk}
loading={documentUploadLoading}
showParseOnCreation
></FileUploadDialog>
)}
{createVisible && (
<RenameDialog
hideModal={hideCreateModal}
onOk={onCreateOk}
loading={createLoading}
title={'File Name'}
></RenameDialog>
)}
</section>
</>
);
}

View File

@ -5,8 +5,11 @@ import {
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Radio } from '@/components/ui/radio';
import { RAGFlowSelect } from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
import { useTranslate } from '@/hooks/common-hooks';
import { ArrowUpRight } from 'lucide-react';
import { useFormContext } from 'react-hook-form';
import {
useHasParsedDocument,
@ -67,15 +70,16 @@ export function EmbeddingModelItem() {
name={'embd_id'}
render={({ field }) => (
<FormItem className=" items-center space-y-0 ">
<div className="flex items-center">
<div className="">
<FormLabel
required
tooltip={t('embeddingModelTip')}
className="text-sm text-muted-foreground whitespace-wrap w-1/4"
className="text-sm whitespace-wrap "
>
<span className="text-destructive mr-1"> *</span>
{t('embeddingModel')}
</FormLabel>
<div className="w-3/4">
<div className="text-muted-foreground">
<FormControl>
<RAGFlowSelect
{...field}
@ -95,3 +99,190 @@ export function EmbeddingModelItem() {
/>
);
}
export function ParseTypeItem() {
const { t } = useTranslate('knowledgeConfiguration');
const form = useFormContext();
return (
<FormField
control={form.control}
name={'parseType'}
render={({ field }) => (
<FormItem className=" items-center space-y-0 ">
<div className="">
<FormLabel
tooltip={t('parseTypeTip')}
className="text-sm whitespace-wrap "
>
{t('parseType')}
</FormLabel>
<div className="text-muted-foreground">
<FormControl>
<Radio.Group {...field}>
<div className="w-3/4 flex gap-2 justify-between text-muted-foreground">
<Radio value={1}>{t('builtIn')}</Radio>
<Radio value={2}>{t('manualSetup')}</Radio>
</div>
</Radio.Group>
</FormControl>
</div>
</div>
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
)}
/>
);
}
export function DataFlowItem() {
const { t } = useTranslate('knowledgeConfiguration');
const form = useFormContext();
return (
<FormField
control={form.control}
name={'data_flow'}
render={({ field }) => (
<FormItem className=" items-center space-y-0 ">
<div className="">
<div className="flex gap-2 justify-between ">
<FormLabel
tooltip={t('dataFlowTip')}
className="text-sm text-text-primary whitespace-wrap "
>
{t('dataFlow')}
</FormLabel>
<div className="text-sm flex text-text-primary">
{t('buildItFromScratch')}
<ArrowUpRight size={14} />
</div>
</div>
<div className="text-muted-foreground">
<FormControl>
<RAGFlowSelect
{...field}
placeholder={t('dataFlowPlaceholder')}
options={[{ value: '0', label: t('dataFlowDefault') }]}
/>
</FormControl>
</div>
</div>
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
)}
/>
);
}
export function DataExtractKnowledgeItem() {
const { t } = useTranslate('knowledgeConfiguration');
const form = useFormContext();
return (
<>
{' '}
<FormField
control={form.control}
name={'extractKnowledgeGraph'}
render={({ field }) => (
<FormItem className=" items-center space-y-0 ">
<div className="">
<FormLabel
tooltip={t('extractKnowledgeGraphTip')}
className="text-sm whitespace-wrap "
>
{t('extractKnowledgeGraph')}
</FormLabel>
<div className="text-muted-foreground">
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</div>
</div>
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
)}
/>{' '}
<FormField
control={form.control}
name={'useRAPTORToEnhanceRetrieval'}
render={({ field }) => (
<FormItem className=" items-center space-y-0 ">
<div className="">
<FormLabel
tooltip={t('useRAPTORToEnhanceRetrievalTip')}
className="text-sm whitespace-wrap "
>
{t('useRAPTORToEnhanceRetrieval')}
</FormLabel>
<div className="text-muted-foreground">
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</div>
</div>
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
)}
/>
</>
);
}
export function TeamItem() {
const { t } = useTranslate('knowledgeConfiguration');
const form = useFormContext();
return (
<FormField
control={form.control}
name={'team'}
render={({ field }) => (
<FormItem className=" items-center space-y-0 ">
<div className="">
<FormLabel
tooltip={t('teamTip')}
className="text-sm whitespace-wrap "
>
<span className="text-destructive mr-1"> *</span>
{t('team')}
</FormLabel>
<div className="text-muted-foreground">
<FormControl>
<RAGFlowSelect
{...field}
placeholder={t('teamPlaceholder')}
options={[{ value: '0', label: t('teamDefault') }]}
/>
</FormControl>
</div>
</div>
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
)}
/>
);
}

View File

@ -9,7 +9,13 @@ import { cn, formatBytes } from '@/lib/utils';
import { Routes } from '@/routes';
import { formatPureDate } from '@/utils/date';
import { isEmpty } from 'lodash';
import { Banknote, Database, FileSearch2, GitGraph } from 'lucide-react';
import {
Banknote,
Database,
DatabaseZap,
FileSearch2,
GitGraph,
} from 'lucide-react';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useHandleMenuClick } from './hooks';
@ -28,6 +34,11 @@ export function SideBar({ refreshCount }: PropType) {
const items = useMemo(() => {
const list = [
{
icon: DatabaseZap,
label: t(`knowledgeDetails.overview`),
key: Routes.DataSetOverview,
},
{
icon: Database,
label: t(`knowledgeDetails.dataset`),