Compare commits

..

2 Commits

Author SHA1 Message Date
d8ef22db68 Fix(dataset): Optimized the dataset configuration page UI #9869 (#10066)
### What problem does this PR solve?
fix(dataset): Optimized the dataset configuration page UI

- Added the DataPipelineSelect component for selecting data pipelines
- Restructured the layout and style of the dataset settings page
- Removed unnecessary components and code
- Optimized data pipeline configuration
- Adjusted the Create Dataset dialog box
- Updated the processing log modal style

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-09-12 16:01:37 +08:00
592f3b1555 Feat: Bind options to the parser operator form. #9869 (#10069)
### What problem does this PR solve?

Feat: Bind options to the parser operator form. #9869

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2025-09-12 16:01:24 +08:00
25 changed files with 470 additions and 326 deletions

View File

@ -30,11 +30,13 @@ const options = Languages.map((x) => ({
type CrossLanguageItemProps = { type CrossLanguageItemProps = {
name?: string; name?: string;
vertical?: boolean; vertical?: boolean;
label?: string;
}; };
export const CrossLanguageFormField = ({ export const CrossLanguageFormField = ({
name = 'prompt_config.cross_languages', name = 'prompt_config.cross_languages',
vertical = true, vertical = true,
label,
}: CrossLanguageItemProps) => { }: CrossLanguageItemProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const form = useFormContext(); const form = useFormContext();
@ -53,7 +55,7 @@ export const CrossLanguageFormField = ({
})} })}
> >
<FormLabel tooltip={t('chat.crossLanguageTip')}> <FormLabel tooltip={t('chat.crossLanguageTip')}>
{t('chat.crossLanguage')} {label || t('chat.crossLanguage')}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<MultiSelect <MultiSelect

View File

@ -0,0 +1,76 @@
import { useTranslate } from '@/hooks/common-hooks';
import { buildSelectOptions } from '@/utils/component-util';
import { ArrowUpRight } from 'lucide-react';
import { useFormContext } from 'react-hook-form';
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '../ui/form';
import { RAGFlowSelect } from '../ui/select';
interface IProps {
toDataPipeline?: () => void;
formFieldName: string;
}
const data = [
{ id: '1', name: 'data-pipeline-1' },
{ id: '2', name: 'data-pipeline-2' },
{ id: '3', name: 'data-pipeline-3' },
{ id: '4', name: 'data-pipeline-4' },
];
export function DataFlowItem(props: IProps) {
const { toDataPipeline, formFieldName } = props;
const { t } = useTranslate('knowledgeConfiguration');
const form = useFormContext();
const toDataPipLine = () => {
// window.open('/data-pipeline');
toDataPipeline?.();
};
const options = buildSelectOptions(data, 'id', 'name');
return (
<FormField
control={form.control}
name={formFieldName}
render={({ field }) => (
<FormItem className=" items-center space-y-0 ">
<div className="flex flex-col gap-1">
<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 cursor-pointer"
onClick={toDataPipLine}
>
{t('buildItFromScratch')}
<ArrowUpRight size={14} />
</div>
</div>
<div className="text-muted-foreground">
<FormControl>
<RAGFlowSelect
{...field}
placeholder={t('dataFlowPlaceholder')}
options={options}
/>
</FormControl>
</div>
</div>
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
)}
/>
);
}

View File

@ -1,6 +1,7 @@
import { LlmModelType } from '@/constants/knowledge'; import { LlmModelType } from '@/constants/knowledge';
import { useTranslate } from '@/hooks/common-hooks'; import { useTranslate } from '@/hooks/common-hooks';
import { useSelectLlmOptionsByModelType } from '@/hooks/llm-hooks'; import { useSelectLlmOptionsByModelType } from '@/hooks/llm-hooks';
import { cn } from '@/lib/utils';
import { camelCase } from 'lodash'; import { camelCase } from 'lodash';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useFormContext } from 'react-hook-form'; import { useFormContext } from 'react-hook-form';
@ -18,7 +19,13 @@ export const enum DocumentType {
PlainText = 'Plain Text', PlainText = 'Plain Text',
} }
export function LayoutRecognizeFormField() { export function LayoutRecognizeFormField({
name = 'parser_config.layout_recognize',
horizontal = true,
}: {
name?: string;
horizontal?: boolean;
}) {
const form = useFormContext(); const form = useFormContext();
const { t } = useTranslate('knowledgeDetails'); const { t } = useTranslate('knowledgeDetails');
@ -53,33 +60,32 @@ export function LayoutRecognizeFormField() {
return ( return (
<FormField <FormField
control={form.control} control={form.control}
name="parser_config.layout_recognize" name={name}
render={({ field }) => { render={({ field }) => {
if (typeof field.value === 'undefined') {
// default value set
form.setValue(
'parser_config.layout_recognize',
form.formState.defaultValues?.parser_config?.layout_recognize ??
'DeepDOC',
);
}
return ( return (
<FormItem className=" items-center space-y-0 "> <FormItem className={'items-center space-y-0 '}>
<div className="flex items-center"> <div
className={cn('flex', {
'flex-col ': !horizontal,
'items-center': horizontal,
})}
>
<FormLabel <FormLabel
tooltip={t('layoutRecognizeTip')} tooltip={t('layoutRecognizeTip')}
className="text-sm text-muted-foreground whitespace-wrap w-1/4" className={cn('text-sm text-muted-foreground whitespace-wrap', {
['w-1/4']: horizontal,
})}
> >
{t('layoutRecognize')} {t('layoutRecognize')}
</FormLabel> </FormLabel>
<div className="w-3/4"> <div className={horizontal ? 'w-3/4' : 'w-full'}>
<FormControl> <FormControl>
<RAGFlowSelect {...field} options={options}></RAGFlowSelect> <RAGFlowSelect {...field} options={options}></RAGFlowSelect>
</FormControl> </FormControl>
</div> </div>
</div> </div>
<div className="flex pt-1"> <div className="flex pt-1">
<div className="w-1/4"></div> <div className={horizontal ? 'w-1/4' : 'w-full'}></div>
<FormMessage /> <FormMessage />
</div> </div>
</FormItem> </FormItem>

View File

@ -16,6 +16,7 @@ type RAGFlowFormItemProps = {
children: ReactNode | ((field: ControllerRenderProps) => ReactNode); children: ReactNode | ((field: ControllerRenderProps) => ReactNode);
horizontal?: boolean; horizontal?: boolean;
required?: boolean; required?: boolean;
labelClassName?: string;
}; };
export function RAGFlowFormItem({ export function RAGFlowFormItem({
@ -25,6 +26,7 @@ export function RAGFlowFormItem({
children, children,
horizontal = false, horizontal = false,
required = false, required = false,
labelClassName,
}: RAGFlowFormItemProps) { }: RAGFlowFormItemProps) {
const form = useFormContext(); const form = useFormContext();
return ( return (
@ -40,7 +42,7 @@ export function RAGFlowFormItem({
<FormLabel <FormLabel
required={required} required={required}
tooltip={tooltip} tooltip={tooltip}
className={cn({ 'w-1/4': horizontal })} className={cn({ 'w-1/4': horizontal }, labelClassName)}
> >
{label} {label}
</FormLabel> </FormLabel>

View File

@ -261,12 +261,13 @@ export default {
reRankModelWaring: 'Re-rank model is very time consuming.', reRankModelWaring: 'Re-rank model is very time consuming.',
}, },
knowledgeConfiguration: { knowledgeConfiguration: {
default: 'Default',
dataPipeline: 'Data Pipeline',
linkDataPipeline: 'Link Data Pipeline',
enableAutoGenerate: 'Enable Auto Generate', enableAutoGenerate: 'Enable Auto Generate',
teamPlaceholder: 'Please select a team.', teamPlaceholder: 'Please select a team.',
dataFlowPlaceholder: 'Please select a data flow.', dataFlowPlaceholder: 'Please select a data flow.',
buildItFromScratch: 'Build it from scratch', buildItFromScratch: 'Build it from scratch',
useRAPTORToEnhanceRetrieval: 'Use RAPTOR to Enhance Retrieval',
extractKnowledgeGraph: 'Extract Knowledge Graph',
dataFlow: 'Data Flow', dataFlow: 'Data Flow',
parseType: 'Parse Type', parseType: 'Parse Type',
manualSetup: 'Manual Setup', manualSetup: 'Manual Setup',

View File

@ -246,12 +246,13 @@ export default {
theDocumentBeingParsedCannotBeDeleted: '正在解析的文档不能被删除', theDocumentBeingParsedCannotBeDeleted: '正在解析的文档不能被删除',
}, },
knowledgeConfiguration: { knowledgeConfiguration: {
default: '默认',
dataPipeline: '数据流',
linkDataPipeline: '关联数据流',
enableAutoGenerate: '是否启用自动生成', enableAutoGenerate: '是否启用自动生成',
teamPlaceholder: '请选择团队', teamPlaceholder: '请选择团队',
dataFlowPlaceholder: '请选择数据流', dataFlowPlaceholder: '请选择数据流',
buildItFromScratch: '去Scratch构建', buildItFromScratch: '去Scratch构建',
useRAPTORToEnhanceRetrieval: '使用 RAPTOR 提升检索效果',
extractKnowledgeGraph: '知识图谱提取',
dataFlow: '数据流', dataFlow: '数据流',
parseType: '切片方法', parseType: '切片方法',
manualSetup: '手动设置', manualSetup: '手动设置',

View File

@ -1,32 +1,53 @@
import { LayoutRecognizeFormField } from '@/components/layout-recognize-form-field';
import { LLMFormField } from '@/components/llm-setting-items/llm-form-field'; import { LLMFormField } from '@/components/llm-setting-items/llm-form-field';
import { SelectWithSearch } from '@/components/originui/select-with-search'; import {
SelectWithSearch,
SelectWithSearchFlagOptionType,
} from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form'; import { RAGFlowFormItem } from '@/components/ragflow-form';
import { buildOptions } from '@/utils/form';
import { FileType } from '../../constant';
import { OutputFormatMap } from './constant';
import { CommonProps } from './interface'; import { CommonProps } from './interface';
import { buildFieldNameWithPrefix } from './utils'; import { buildFieldNameWithPrefix } from './utils';
export function LanguageFormField({ prefix }: CommonProps) { function buildOutputOptionsFormatMap() {
return ( return Object.entries(OutputFormatMap).reduce<
<RAGFlowFormItem Record<string, SelectWithSearchFlagOptionType[]>
name={buildFieldNameWithPrefix(`lang`, prefix)} >((pre, [key, value]) => {
label="lang" pre[key] = buildOptions(value);
> return pre;
<SelectWithSearch options={[]}></SelectWithSearch> }, {});
</RAGFlowFormItem>
);
} }
export function OutputFormatFormField({ prefix }: CommonProps) { export type OutputFormatFormFieldProps = CommonProps & {
fileType: FileType;
};
export function OutputFormatFormField({
prefix,
fileType,
}: OutputFormatFormFieldProps) {
return ( return (
<RAGFlowFormItem <RAGFlowFormItem
name={buildFieldNameWithPrefix(`output_format`, prefix)} name={buildFieldNameWithPrefix(`output_format`, prefix)}
label="output_format" label="output_format"
> >
<SelectWithSearch options={[]}></SelectWithSearch> <SelectWithSearch
options={buildOutputOptionsFormatMap()[fileType]}
></SelectWithSearch>
</RAGFlowFormItem> </RAGFlowFormItem>
); );
} }
export function ParserMethodFormField({ prefix }: CommonProps) { export function ParserMethodFormField({ prefix }: CommonProps) {
return (
<LayoutRecognizeFormField
name={buildFieldNameWithPrefix(`parse_method`, prefix)}
horizontal={false}
></LayoutRecognizeFormField>
);
return ( return (
<RAGFlowFormItem <RAGFlowFormItem
name={buildFieldNameWithPrefix(`parse_method`, prefix)} name={buildFieldNameWithPrefix(`parse_method`, prefix)}

View File

@ -0,0 +1,52 @@
import { FileType } from '../../constant';
export enum PdfOutputFormat {
Json = 'json',
Markdown = 'markdown',
}
export enum SpreadsheetOutputFormat {
Json = 'json',
Html = 'html',
}
export enum ImageOutputFormat {
Text = 'text',
}
export enum EmailOutputFormat {
Json = 'json',
Text = 'text',
}
export enum TextMarkdownOutputFormat {
Text = 'text',
}
export enum DocxOutputFormat {
Markdown = 'markdown',
}
export enum PptOutputFormat {
Json = 'json',
}
export enum VideoOutputFormat {
Json = 'json',
}
export enum AudioOutputFormat {
Text = 'text',
}
export const OutputFormatMap = {
[FileType.PDF]: PdfOutputFormat,
[FileType.Spreadsheet]: SpreadsheetOutputFormat,
[FileType.Image]: ImageOutputFormat,
[FileType.Email]: EmailOutputFormat,
[FileType.TextMarkdown]: TextMarkdownOutputFormat,
[FileType.Docx]: DocxOutputFormat,
[FileType.PowerPoint]: PptOutputFormat,
[FileType.Video]: VideoOutputFormat,
[FileType.Audio]: AudioOutputFormat,
};

View File

@ -1,6 +1,7 @@
import { SelectWithSearch } from '@/components/originui/select-with-search'; import { SelectWithSearch } from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form'; import { RAGFlowFormItem } from '@/components/ragflow-form';
import { buildOptions } from '@/utils/form'; import { buildOptions } from '@/utils/form';
import { FileType } from '../../constant';
import { OutputFormatFormField } from './common-form-fields'; import { OutputFormatFormField } from './common-form-fields';
import { CommonProps } from './interface'; import { CommonProps } from './interface';
import { buildFieldNameWithPrefix } from './utils'; import { buildFieldNameWithPrefix } from './utils';
@ -25,7 +26,10 @@ export function EmailFormFields({ prefix }: CommonProps) {
> >
<SelectWithSearch options={options}></SelectWithSearch> <SelectWithSearch options={options}></SelectWithSearch>
</RAGFlowFormItem> </RAGFlowFormItem>
<OutputFormatFormField prefix={prefix}></OutputFormatFormField> <OutputFormatFormField
prefix={prefix}
fileType={FileType.Email}
></OutputFormatFormField>
</> </>
); );
} }

View File

@ -1,3 +1,4 @@
import { FileType } from '../../constant';
import { import {
LargeModelFormField, LargeModelFormField,
OutputFormatFormField, OutputFormatFormField,
@ -11,7 +12,10 @@ export function ImageFormFields({ prefix }: CommonProps) {
<ParserMethodFormField prefix={prefix}></ParserMethodFormField> <ParserMethodFormField prefix={prefix}></ParserMethodFormField>
{/* Multimodal Model */} {/* Multimodal Model */}
<LargeModelFormField prefix={prefix}></LargeModelFormField> <LargeModelFormField prefix={prefix}></LargeModelFormField>
<OutputFormatFormField prefix={prefix}></OutputFormatFormField> <OutputFormatFormField
prefix={prefix}
fileType={FileType.Image}
></OutputFormatFormField>
</> </>
); );
} }

View File

@ -1,13 +1,20 @@
import { FormContainer } from '@/components/form-container';
import { SelectWithSearch } from '@/components/originui/select-with-search'; import { SelectWithSearch } from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form'; import { RAGFlowFormItem } from '@/components/ragflow-form';
import { BlockButton, Button } from '@/components/ui/button'; import { BlockButton, Button } from '@/components/ui/button';
import { Form } from '@/components/ui/form'; import { Form } from '@/components/ui/form';
import { Separator } from '@/components/ui/separator';
import { cn } from '@/lib/utils';
import { buildOptions } from '@/utils/form'; import { buildOptions } from '@/utils/form';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useHover } from 'ahooks';
import { Trash2 } from 'lucide-react'; import { Trash2 } from 'lucide-react';
import { memo, useCallback } from 'react'; import { memo, useCallback, useRef } from 'react';
import { useFieldArray, useForm } from 'react-hook-form'; import {
UseFieldArrayRemove,
useFieldArray,
useForm,
useFormContext,
} from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
import { FileType, initialParserValues } from '../../constant'; import { FileType, initialParserValues } from '../../constant';
import { useFormValues } from '../../hooks/use-form-values'; import { useFormValues } from '../../hooks/use-form-values';
@ -34,6 +41,51 @@ const FileFormatWidgetMap = {
[FileType.Image]: ImageFormFields, [FileType.Image]: ImageFormFields,
}; };
type ParserItemProps = {
name: string;
index: number;
fieldLength: number;
remove: UseFieldArrayRemove;
};
function ParserItem({ name, index, fieldLength, remove }: ParserItemProps) {
const form = useFormContext();
const ref = useRef(null);
const isHovering = useHover(ref);
const prefix = `${name}.${index}`;
const fileFormat = form.getValues(`${name}.${index}.fileFormat`);
const Widget =
fileFormat && fileFormat in FileFormatWidgetMap
? FileFormatWidgetMap[fileFormat as keyof typeof FileFormatWidgetMap]
: OutputFormatFormField;
return (
<section
className={cn('space-y-5 p-5 rounded-md', {
'bg-state-error-5': isHovering,
})}
>
<div className="flex justify-between items-center">
<span className="text-text-primary text-sm">Parser {index}</span>
{index > 0 && (
<Button variant={'ghost'} onClick={() => remove(index)} ref={ref}>
<Trash2 />
</Button>
)}
</div>
<RAGFlowFormItem
name={buildFieldNameWithPrefix(`fileFormat`, prefix)}
label="File Formats"
>
<SelectWithSearch options={FileFormatOptions}></SelectWithSearch>
</RAGFlowFormItem>
<Widget prefix={prefix} fileType={fileFormat as FileType}></Widget>
{index < fieldLength - 1 && <Separator />}
</section>
);
}
export const FormSchema = z.object({ export const FormSchema = z.object({
parser: z.array( parser: z.array(
z.object({ z.object({
@ -67,35 +119,22 @@ const ParserForm = ({ node }: INextOperatorForm) => {
return ( return (
<Form {...form}> <Form {...form}>
{fields.map((field, index) => { <form className="px-5">
const prefix = `${name}.${index}`; {fields.map((field, index) => {
const fileFormat = form.getValues(`${name}.${index}.fileFormat`); return (
<ParserItem
const Widget = key={field.id}
fileFormat && fileFormat in FileFormatWidgetMap name={name}
? FileFormatWidgetMap[ index={index}
fileFormat as keyof typeof FileFormatWidgetMap fieldLength={fields.length}
] remove={remove}
: OutputFormatFormField; ></ParserItem>
return ( );
<FormContainer key={field.id}> })}
<div className="flex justify-between items-center"> <BlockButton onClick={add} type="button">
<span className="text-text-primary text-sm">Parser {index}</span> Add Parser
<Button variant={'ghost'} onClick={() => remove(index)}> </BlockButton>
<Trash2 /> </form>
</Button>
</div>
<RAGFlowFormItem
name={buildFieldNameWithPrefix(`fileFormat`, prefix)}
label="File Formats"
>
<SelectWithSearch options={FileFormatOptions}></SelectWithSearch>
</RAGFlowFormItem>
<Widget prefix={prefix}></Widget>
</FormContainer>
);
})}
<BlockButton onClick={add}>Add Parser</BlockButton>
<div className="p-5"> <div className="p-5">
<Output list={outputList}></Output> <Output list={outputList}></Output>
</div> </div>

View File

@ -1,10 +1,12 @@
import { CrossLanguageFormField } from '@/components/cross-language-form-field';
import { FileType } from '../../constant';
import { import {
LanguageFormField,
LargeModelFormField, LargeModelFormField,
OutputFormatFormField, OutputFormatFormField,
ParserMethodFormField, ParserMethodFormField,
} from './common-form-fields'; } from './common-form-fields';
import { CommonProps } from './interface'; import { CommonProps } from './interface';
import { buildFieldNameWithPrefix } from './utils';
export function PdfFormFields({ prefix }: CommonProps) { export function PdfFormFields({ prefix }: CommonProps) {
return ( return (
@ -12,8 +14,14 @@ export function PdfFormFields({ prefix }: CommonProps) {
<ParserMethodFormField prefix={prefix}></ParserMethodFormField> <ParserMethodFormField prefix={prefix}></ParserMethodFormField>
{/* Multimodal Model */} {/* Multimodal Model */}
<LargeModelFormField prefix={prefix}></LargeModelFormField> <LargeModelFormField prefix={prefix}></LargeModelFormField>
<LanguageFormField prefix={prefix}></LanguageFormField> <CrossLanguageFormField
<OutputFormatFormField prefix={prefix}></OutputFormatFormField> name={buildFieldNameWithPrefix(`lang`, prefix)}
label="lang"
></CrossLanguageFormField>
<OutputFormatFormField
prefix={prefix}
fileType={FileType.Image}
></OutputFormatFormField>
</> </>
); );
} }

View File

@ -1,15 +1,21 @@
import { import {
LargeModelFormField, LargeModelFormField,
OutputFormatFormField, OutputFormatFormField,
OutputFormatFormFieldProps,
} from './common-form-fields'; } from './common-form-fields';
import { CommonProps } from './interface';
export function VideoFormFields({ prefix }: CommonProps) { export function VideoFormFields({
prefix,
fileType,
}: OutputFormatFormFieldProps) {
return ( return (
<> <>
{/* Multimodal Model */} {/* Multimodal Model */}
<LargeModelFormField prefix={prefix}></LargeModelFormField> <LargeModelFormField prefix={prefix}></LargeModelFormField>
<OutputFormatFormField prefix={prefix}></OutputFormatFormField> <OutputFormatFormField
prefix={prefix}
fileType={fileType}
></OutputFormatFormField>
</> </>
); );
} }

View File

@ -317,7 +317,8 @@ const FileLogsTable: FC<FileLogsTableProps> = ({
state: 'Running', state: 'Running',
startTime: '14/03/2025 14:53:39', startTime: '14/03/2025 14:53:39',
duration: '800', duration: '800',
details: 'PRD for DealBees 1.2 (1).text', details:
'\n17:43:21 Task has been received.\n17:43:25 Page(1~100000001): Start to parse.\n17:43:25 Page(1~100000001): Start to tag for every chunk ...\n17:43:45 Page(1~100000001): Tagging 2 chunks completed in 18.99s\n17:43:45 Page(1~100000001): Generate 2 chunks\n17:43:55 Page(1~100000001): Embedding chunks (10.60s)\n17:43:55 Page(1~100000001): Indexing done (0.07s). Task done (33.97s)\n17:43:58 created task raptor\n17:43:58 Task has been received.\n17:44:36 Cluster one layer: 2 -> 1\n17:44:36 Indexing done (0.05s). Task done (37.88s)\n17:44:40 created task graphrag\n17:44:41 Task has been received.\n17:50:57 Entities extraction of chunk 0 1/3 done, 25 nodes, 26 edges, 14893 tokens.\n17:56:01 [ERROR][Exception]: Operation timed out after 7200 seconds and 1 attempts.',
}; };
return ( return (
<div className="w-full h-[calc(100vh-350px)]"> <div className="w-full h-[calc(100vh-350px)]">

View File

@ -1,31 +0,0 @@
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,79 @@
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
import { Button } from '@/components/ui/button';
import { Link, Route, Settings2, Unlink } from 'lucide-react';
import { useTranslation } from 'react-i18next';
interface DataPipelineItemProps {
name: string;
avatar?: string;
isDefault?: boolean;
linked?: boolean;
}
const DataPipelineItem = (props: DataPipelineItemProps) => {
const { t } = useTranslation();
const { name, avatar, isDefault, linked } = props;
return (
<div className="flex items-center justify-between gap-1 px-2 rounded-lg border">
<div className="flex items-center gap-1">
<RAGFlowAvatar avatar={avatar} name={name} className="size-4" />
<div>{name}</div>
{isDefault && (
<div className="text-xs bg-text-secondary text-bg-base px-2 py-1 rounded-md">
{t('knowledgeConfiguration.default')}
</div>
)}
</div>
<div className="flex gap-1 items-center">
<Button variant={'transparent'} className="border-none">
<Settings2 />
</Button>
<Button variant={'transparent'} className="border-none">
{linked ? <Link /> : <Unlink />}
</Button>
</div>
</div>
);
};
const LinkDataPipeline = () => {
const { t } = useTranslation();
const testNode = [
{
name: 'Data Pipeline 1',
avatar: 'https://avatars.githubusercontent.com/u/10656201?v=4',
isDefault: true,
linked: true,
},
{
name: 'Data Pipeline 2',
avatar: 'https://avatars.githubusercontent.com/u/10656201?v=4',
linked: false,
},
];
return (
<div className="flex flex-col gap-2">
<section className="flex flex-col">
<div className="flex items-center gap-1 text-text-primary text-sm">
<Route className="size-4" />
{t('knowledgeConfiguration.dataPipeline')}
</div>
<div className="flex justify-between items-center">
<div className="text-center text-xs text-text-secondary">
Manage data pipeline linkage with this dataset
</div>
<Button variant={'transparent'}>
<Link />
<span className="text-xs text-text-primary">
{t('knowledgeConfiguration.linkDataPipeline')}
</span>
</Button>
</div>
</section>
<section className="flex flex-col gap-2">
{testNode.map((item) => (
<DataPipelineItem key={item.name} {...item} />
))}
</section>
</div>
);
};
export default LinkDataPipeline;

View File

@ -10,7 +10,6 @@ import { RAGFlowSelect } from '@/components/ui/select';
import { Switch } from '@/components/ui/switch'; import { Switch } from '@/components/ui/switch';
import { useTranslate } from '@/hooks/common-hooks'; import { useTranslate } from '@/hooks/common-hooks';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { ArrowUpRight } from 'lucide-react';
import { useFormContext } from 'react-hook-form'; import { useFormContext } from 'react-hook-form';
import { import {
useHasParsedDocument, useHasParsedDocument,
@ -70,8 +69,13 @@ export function EmbeddingModelItem({ line = 1 }: { line?: 1 | 2 }) {
control={form.control} control={form.control}
name={'embd_id'} name={'embd_id'}
render={({ field }) => ( render={({ field }) => (
<FormItem className=" items-center space-y-0 "> <FormItem className={cn(' items-center space-y-0 ')}>
<div className={cn({ 'flex items-center': line === 1 })}> <div
className={cn('flex', {
' items-center': line === 1,
'flex-col gap-1': line === 2,
})}
>
<FormLabel <FormLabel
required required
tooltip={t('embeddingModelTip')} tooltip={t('embeddingModelTip')}
@ -142,155 +146,6 @@ export function ParseTypeItem() {
); );
} }
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() { export function EnableAutoGenerateItem() {
const { t } = useTranslate('knowledgeConfiguration'); const { t } = useTranslate('knowledgeConfiguration');
const form = useFormContext(); const form = useFormContext();

View File

@ -17,7 +17,7 @@ export function GeneralForm() {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<section className="space-y-4"> <>
<FormField <FormField
control={form.control} control={form.control}
name="name" name="name"
@ -87,6 +87,6 @@ export function GeneralForm() {
/> />
<PermissionFormField></PermissionFormField> <PermissionFormField></PermissionFormField>
<EmbeddingModelItem></EmbeddingModelItem> <EmbeddingModelItem></EmbeddingModelItem>
</section> </>
); );
} }

View File

@ -1,3 +1,6 @@
import GraphRagItems from '@/components/parse-configuration/graph-rag-form-fields';
import RaptorFormFields from '@/components/parse-configuration/raptor-form-fields';
import { Button } from '@/components/ui/button';
import Divider from '@/components/ui/divider'; import Divider from '@/components/ui/divider';
import { Form } from '@/components/ui/form'; import { Form } from '@/components/ui/form';
import { DocumentParserType } from '@/constants/knowledge'; import { DocumentParserType } from '@/constants/knowledge';
@ -7,11 +10,12 @@ import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { z } from 'zod'; import { z } from 'zod';
import { TopTitle } from '../dataset-title'; import { TopTitle } from '../dataset-title';
import { ChunkMethodForm } from './chunk-method-form'; import LinkDataPipeline from './components/link-data-pipeline';
import { MainContainer } from './configuration-form-container';
import { formSchema } from './form-schema'; import { formSchema } from './form-schema';
import { GeneralForm } from './general-form'; import { GeneralForm } from './general-form';
import { useFetchKnowledgeConfigurationOnMount } from './hooks'; import { useFetchKnowledgeConfigurationOnMount } from './hooks';
import { SavingButton } from './saving-button';
const enum DocumentType { const enum DocumentType {
DeepDOC = 'DeepDOC', DeepDOC = 'DeepDOC',
PlainText = 'Plain Text', PlainText = 'Plain Text',
@ -77,10 +81,29 @@ export default function DatasetSettings() {
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6 flex-1" className="space-y-6 flex-1"
> >
<div className="w-[768px] h-[calc(100vh-220px)] overflow-y-auto scrollbar-thin"> <div className="w-[768px] h-[calc(100vh-240px)] pr-1 overflow-y-auto scrollbar-auto">
<GeneralForm></GeneralForm> <MainContainer>
<Divider /> <GeneralForm></GeneralForm>
<ChunkMethodForm></ChunkMethodForm> <Divider />
<GraphRagItems className="border-none p-0"></GraphRagItems>
<Divider />
<RaptorFormFields></RaptorFormFields>
<Divider />
<LinkDataPipeline />
</MainContainer>
</div>
<div className="text-right items-center flex justify-end gap-3 w-[768px]">
<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> </div>
</form> </form>
</Form> </Form>

View File

@ -1,20 +0,0 @@
import GraphRagItems from '@/components/parse-configuration/graph-rag-form-fields';
import RaptorFormFields from '@/components/parse-configuration/raptor-form-fields';
import Divider from '@/components/ui/divider';
import {
ConfigurationFormContainer,
MainContainer,
} from './configuration-form-container';
export function NaiveConfiguration() {
return (
<MainContainer>
<GraphRagItems className="border-none p-0"></GraphRagItems>
<Divider />
<ConfigurationFormContainer>
<RaptorFormFields></RaptorFormFields>
</ConfigurationFormContainer>
<Divider />
</MainContainer>
);
}

View File

@ -1,3 +1,4 @@
import { DataFlowItem } from '@/components/data-pipeline-select';
import { ButtonLoading } from '@/components/ui/button'; import { ButtonLoading } from '@/components/ui/button';
import { import {
Dialog, Dialog,
@ -15,17 +16,15 @@ import {
FormMessage, FormMessage,
} from '@/components/ui/form'; } from '@/components/ui/form';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { IModalProps } from '@/interfaces/common'; import { IModalProps } from '@/interfaces/common';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useForm, useWatch } from 'react-hook-form'; import { useForm, useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { z } from 'zod'; import { z } from 'zod';
import { import {
DataExtractKnowledgeItem,
DataFlowItem,
EmbeddingModelItem, EmbeddingModelItem,
ParseTypeItem, ParseTypeItem,
TeamItem,
} from '../dataset/dataset-setting/configuration/common-item'; } from '../dataset/dataset-setting/configuration/common-item';
const FormId = 'dataset-creating-form'; const FormId = 'dataset-creating-form';
@ -58,6 +57,7 @@ export function InputForm({ onOk }: IModalProps<any>) {
control: form.control, control: form.control,
name: 'parseType', name: 'parseType',
}); });
const { navigateToAgents } = useNavigatePage();
return ( return (
<Form {...form}> <Form {...form}>
<form <form
@ -88,9 +88,10 @@ export function InputForm({ onOk }: IModalProps<any>) {
<ParseTypeItem /> <ParseTypeItem />
{parseType === 2 && ( {parseType === 2 && (
<> <>
<DataFlowItem /> <DataFlowItem
<DataExtractKnowledgeItem /> toDataPipeline={navigateToAgents}
<TeamItem /> formFieldName="data_flow"
/>
</> </>
)} )}
</form> </form>

View File

@ -1,7 +1,9 @@
import FileStatusBadge from '@/components/file-status-badge';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Modal } from '@/components/ui/modal/modal'; import { Modal } from '@/components/ui/modal/modal';
import { useTranslate } from '@/hooks/common-hooks'; import { useTranslate } from '@/hooks/common-hooks';
import React from 'react'; import React from 'react';
import reactStringReplace from 'react-string-replace';
interface ProcessLogModalProps { interface ProcessLogModalProps {
visible: boolean; visible: boolean;
@ -12,7 +14,7 @@ interface ProcessLogModalProps {
fileSize: string; fileSize: string;
source: string; source: string;
task: string; task: string;
state: 'Running' | 'Completed' | 'Failed' | 'Pending'; state: 'Running' | 'Success' | 'Failed' | 'Pending';
startTime: string; startTime: string;
endTime?: string; endTime?: string;
duration?: string; duration?: string;
@ -20,32 +22,6 @@ interface ProcessLogModalProps {
}; };
} }
const StatusTag: React.FC<{ state: string }> = ({ state }) => {
const getTagStyle = () => {
switch (state) {
case 'Running':
return 'bg-green-500 text-green-100';
case 'Completed':
return 'bg-blue-500 text-blue-100';
case 'Failed':
return 'bg-red-500 text-red-100';
case 'Pending':
return 'bg-yellow-500 text-yellow-100';
default:
return 'bg-gray-500 text-gray-100';
}
};
return (
<span
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getTagStyle()}`}
>
<span className="w-1.5 h-1.5 rounded-full mr-1 bg-current"></span>
{state}
</span>
);
};
const InfoItem: React.FC<{ const InfoItem: React.FC<{
label: string; label: string;
value: string | React.ReactNode; value: string | React.ReactNode;
@ -65,6 +41,24 @@ const ProcessLogModal: React.FC<ProcessLogModalProps> = ({
taskInfo, taskInfo,
}) => { }) => {
const { t } = useTranslate('knowledgeDetails'); const { t } = useTranslate('knowledgeDetails');
const replaceText = (text: string) => {
// Remove duplicate \n
const nextText = text.replace(/(\n)\1+/g, '$1');
const replacedText = reactStringReplace(
nextText,
/(\[ERROR\].+\s)/g,
(match, i) => {
return (
<span key={i} className={'text-red-600'}>
{match}
</span>
);
},
);
return replacedText;
};
return ( return (
<Modal <Modal
title={t('processLog')} title={t('processLog')}
@ -86,15 +80,14 @@ const ProcessLogModal: React.FC<ProcessLogModalProps> = ({
<InfoItem label="File Size" value={taskInfo.fileSize} /> <InfoItem label="File Size" value={taskInfo.fileSize} />
<InfoItem label="Source" value={taskInfo.source} /> <InfoItem label="Source" value={taskInfo.source} />
<InfoItem label="Task" value={taskInfo.task} /> <InfoItem label="Task" value={taskInfo.task} />
<InfoItem label="Details" value={taskInfo.details} />
</div> </div>
{/* Right Column */} {/* Right Column */}
<div className="space-y-4"> <div className="space-y-4">
<div className="flex flex-col"> <div className="flex flex-col">
<span className="text-text-secondary text-sm">States</span> <span className="text-text-secondary text-sm">Status</span>
<div className="mt-1"> <div className="mt-1">
<StatusTag state={taskInfo.state} /> <FileStatusBadge status={taskInfo.state} />
</div> </div>
</div> </div>
@ -108,6 +101,17 @@ const ProcessLogModal: React.FC<ProcessLogModalProps> = ({
/> />
</div> </div>
</div> </div>
{/* <InfoItem label="Details" value={taskInfo.details} /> */}
<div>
<div>Details</div>
<div>
<ul className="space-y-2">
<div className={'w-full whitespace-pre-line text-wrap '}>
{replaceText(taskInfo.details)}
</div>
</ul>
</div>
</div>
</div> </div>
</Modal> </Modal>
); );

View File

@ -1,3 +1,10 @@
export function buildSelectOptions(list: Array<string>) { export function buildSelectOptions(
list: Array<any>,
keyName?: string,
valueName?: string,
) {
if (keyName && valueName) {
return list.map((x) => ({ label: x[valueName], value: x[keyName] }));
}
return list.map((x) => ({ label: x, value: x })); return list.map((x) => ({ label: x, value: x }));
} }

View File

@ -80,7 +80,10 @@ module.exports = {
'bg-accent': 'var(--bg-accent)', 'bg-accent': 'var(--bg-accent)',
'state-success': 'var(--state-success)', 'state-success': 'var(--state-success)',
'state-warning': 'var(--state-warning)', 'state-warning': 'var(--state-warning)',
'state-error': 'var(--state-error)', 'state-error': {
DEFAULT: 'rgb(var(--state-error) / <alpha-value>)',
5: 'rgba(var(--state-error) / 0.05)', // 5%
},
'team-group': 'var(--team-group)', 'team-group': 'var(--team-group)',
'team-member': 'var(--team-member)', 'team-member': 'var(--team-member)',
'team-department': 'var(--team-department)', 'team-department': 'var(--team-department)',

View File

@ -119,7 +119,7 @@
--state-success: #3ba05c; --state-success: #3ba05c;
--state-warning: #faad14; --state-warning: #faad14;
--state-error: #d8494b; --state-error: 216 73 75;
--team-group: #5ab77e; --team-group: #5ab77e;
--team-member: #5c96c8; --team-member: #5c96c8;
@ -291,8 +291,8 @@
@layer utilities { @layer utilities {
.scrollbar-auto { .scrollbar-auto {
/* hide scrollbar */ /* hide scrollbar */
scrollbar-width: none; scrollbar-width: thin;
scrollbar-color: transparent transparent; scrollbar-color: var(--border-default) var(--bg-card);
} }
.scrollbar-auto::-webkit-scrollbar { .scrollbar-auto::-webkit-scrollbar {