diff --git a/web/src/components/ui/async-tree-select.tsx b/web/src/components/ui/async-tree-select.tsx index 6ef95bfd11..4bb40091ec 100644 --- a/web/src/components/ui/async-tree-select.tsx +++ b/web/src/components/ui/async-tree-select.tsx @@ -25,6 +25,7 @@ type AsyncTreeSelectProps = { value?: TreeId; onChange?(value: TreeId): void; loadData?(node: TreeNodeType): Promise; + canSelect?(node: TreeNodeType): boolean; }; function getNodeText(node: ReactNode): string { @@ -39,6 +40,7 @@ export function AsyncTreeSelect({ value, loadData, onChange, + canSelect, }: AsyncTreeSelectProps) { const [open, setOpen] = useState(false); const [searchText, setSearchText] = useState(''); @@ -106,19 +108,8 @@ export function AsyncTreeSelect({ [expandedKeys, searchText, treeData, visibleNodeIds], ); - const handleNodeClick = useCallback( - (id: TreeId) => (e: React.MouseEvent) => { - e.stopPropagation(); - onChange?.(id); - setOpen(false); - setSearchText(''); - }, - [onChange], - ); - - const handleArrowClick = useCallback( - (node: TreeNodeType) => async (e: React.MouseEvent) => { - e.stopPropagation(); + const toggleNode = useCallback( + async (node: TreeNodeType) => { const { id } = node; if (isExpanded(id)) { setExpandedKeys((keys) => { @@ -140,6 +131,28 @@ export function AsyncTreeSelect({ [isExpanded, loadData, treeData], ); + const handleNodeClick = useCallback( + (node: TreeNodeType) => async (e: React.MouseEvent) => { + e.stopPropagation(); + if (canSelect && !canSelect(node)) { + await toggleNode(node); + return; + } + onChange?.(node.id); + setOpen(false); + setSearchText(''); + }, + [canSelect, onChange, toggleNode], + ); + + const handleArrowClick = useCallback( + (node: TreeNodeType) => async (e: React.MouseEvent) => { + e.stopPropagation(); + await toggleNode(node); + }, + [toggleNode], + ); + const handleOpenChange = useCallback((open: boolean) => { setOpen(open); if (!open) { @@ -166,7 +179,7 @@ export function AsyncTreeSelect({ {currentLevelList.map((x) => (
  • ', value: [] }, }, diff --git a/web/src/pages/agent/form/extractor-form/index.tsx b/web/src/pages/agent/form/extractor-form/index.tsx index 22748fb384..c51a313f9f 100644 --- a/web/src/pages/agent/form/extractor-form/index.tsx +++ b/web/src/pages/agent/form/extractor-form/index.tsx @@ -7,8 +7,11 @@ import { LargeModelFormField } from '@/components/large-model-form-field'; import { LlmSettingSchema } from '@/components/llm-setting-items/next'; import { SelectWithSearch } from '@/components/originui/select-with-search'; import { RAGFlowFormItem } from '@/components/ragflow-form'; +import { SliderInputFormField } from '@/components/slider-input-form-field'; +import { AsyncTreeSelect } from '@/components/ui/async-tree-select'; import { Form } from '@/components/ui/form'; import { PromptEditor } from '@/pages/agent/form/components/prompt-editor'; +import { isGoBackend } from '@/utils/backend-runtime'; import { buildOptions } from '@/utils/form'; import { zodResolver } from '@hookform/resolvers/zod'; import { memo } from 'react'; @@ -29,6 +32,8 @@ import { buildOutputList } from '../../utils/build-output-list'; import { FormWrapper } from '../components/form-wrapper'; import { Output } from '../components/output'; import { useSwitchPrompt } from './use-switch-prompt'; +import { canSelectTagFile, useTagFileTree } from './use-tag-file-tree'; +import { FormLayout } from '@/constants/form'; export const FormSchema = z.object({ field_name: z.string(), @@ -36,6 +41,8 @@ export const FormSchema = z.object({ prompts: z.string().optional(), auto_keywords: z.number().optional(), auto_questions: z.number().optional(), + auto_tags: z.number().optional(), + tag_file_id: z.string().optional(), ...LlmSettingSchema, }); @@ -75,6 +82,8 @@ const ExtractorForm = ({ const ownerTenantId = useOwnerTenantId(); const isToc = form.getValues('field_name') === 'toc'; + const { treeData, loadData } = useTagFileTree(); + return (
    @@ -83,6 +92,31 @@ const ExtractorForm = ({ > + {isGoBackend() && ( + <> + + + + {(field) => ( + + )} + + + )} + {(field) => ( ; +type SwitchPromptForm = { + getValues(name: 'field_name'): string; + setValue( + name: SwitchPromptField, + value: string, + options?: { shouldDirty?: boolean; shouldValidate?: boolean }, + ): void; +}; -export function useSwitchPrompt(form: UseFormReturn) { +export function useSwitchPrompt(form: SwitchPromptForm) { const { visible, showModal, hideModal } = useSetModalState(); const { t } = useTranslation(); const previousFieldNames = useRef([form.getValues('field_name')]); const setPromptValue = useCallback( - (field: keyof ExtractorFormSchemaType, key: string, value: string) => { + (field: SwitchPromptField, key: string, value: string) => { form.setValue(field, t(`flow.prompts.${key}.${value}`), { shouldDirty: true, shouldValidate: true, diff --git a/web/src/pages/agent/form/extractor-form/use-tag-file-tree.ts b/web/src/pages/agent/form/extractor-form/use-tag-file-tree.ts new file mode 100644 index 0000000000..7ddcfa3b74 --- /dev/null +++ b/web/src/pages/agent/form/extractor-form/use-tag-file-tree.ts @@ -0,0 +1,52 @@ +import { TreeNodeType } from '@/components/ui/async-tree-select'; +import { useFetchPureFileList } from '@/hooks/use-file-request'; +import { IFile } from '@/interfaces/database/file-manager'; +import { isFolderType } from '@/pages/files/util'; +import { getExtension } from '@/utils/document-util'; +import { uniqBy } from 'lodash'; +import { useCallback, useState } from 'react'; + +const AllowedExtensions = ['xlsx', 'xls', 'csv', 'txt']; + +export function canSelectTagFile(node: TreeNodeType) { + return Boolean(node.isLeaf); +} + +export function useTagFileTree() { + const { fetchList } = useFetchPureFileList(); + + const [treeData, setTreeData] = useState([]); + + const loadData = useCallback( + async ({ id }: TreeNodeType) => { + const ret = await fetchList(id as string); + if (ret.code === 0) { + setTreeData((tree) => + uniqBy( + tree.concat( + ret.data.files + .filter( + (x: IFile) => + (isFolderType(x.type) && + x.name.toLowerCase() !== 'skills') || + AllowedExtensions.includes(getExtension(x.name)), + ) + .map((x: IFile) => ({ + id: x.id, + parentId: x.parent_id, + title: x.name, + // has_child_folder only counts child folders, so folders + // must always be expandable to reach files inside them + isLeaf: !isFolderType(x.type), + })), + ), + 'id', + ), + ); + } + }, + [fetchList], + ); + + return { treeData, loadData }; +} diff --git a/web/src/pages/agent/form/parser-form/dynamic-page-range.tsx b/web/src/pages/agent/form/parser-form/dynamic-page-range.tsx new file mode 100644 index 0000000000..a2fdae74ed --- /dev/null +++ b/web/src/pages/agent/form/parser-form/dynamic-page-range.tsx @@ -0,0 +1,107 @@ +import { Button } from '@/components/ui/button'; +import { + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { Separator } from '@/components/ui/separator'; +import { LucidePlus, LucideTrash2 } from 'lucide-react'; +import { useCallback } from 'react'; +import { useFieldArray, useFormContext } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; +import { CommonProps } from './interface'; +import { buildFieldNameWithPrefix } from './utils'; + +export function DynamicPageRange({ prefix }: CommonProps) { + const { t } = useTranslation(); + const form = useFormContext(); + + const pagesName = buildFieldNameWithPrefix('pages', prefix); + + const { fields, remove, append } = useFieldArray({ + name: pagesName, + control: form.control, + }); + + const handleAppend = useCallback(() => { + append({ from: 1, to: 100000 }); + }, [append]); + + return ( +
    + + {t('knowledgeDetails.pageRanges')} + + {fields.map((field, index) => { + return ( +
    + ( + + + + + + + + )} + /> + + + + ( + + + + + + + + )} + /> + + +
    + ); + })} + + +
    + ); +} diff --git a/web/src/pages/agent/form/parser-form/index.tsx b/web/src/pages/agent/form/parser-form/index.tsx index 409ac19afa..c94170a89b 100644 --- a/web/src/pages/agent/form/parser-form/index.tsx +++ b/web/src/pages/agent/form/parser-form/index.tsx @@ -47,83 +47,6 @@ import { WordFormFields } from './word-form-fields'; const outputList = buildOutputList(initialParserValues.outputs); -// type PreprocessOptionConfig = { -// value: PreprocessValue; -// required?: boolean; -// }; - -// const DefaultPreprocessOptionConfigs: PreprocessOptionConfig[] = [ -// { value: MAIN_CONTENT_PREPROCESS_VALUE, required: true }, -// ]; - -// const PreprocessOptionConfigsMap: Partial< -// Record -// > = { -// [FileType.PDF]: [ -// { value: MAIN_CONTENT_PREPROCESS_VALUE, required: true }, -// { value: PreprocessValue.abstract }, -// { value: PreprocessValue.author }, -// { value: PreprocessValue.section_title }, -// ], -// [FileType.PowerPoint]: [ -// { value: MAIN_CONTENT_PREPROCESS_VALUE, required: true }, -// ], -// [FileType.Spreadsheet]: [ -// { value: MAIN_CONTENT_PREPROCESS_VALUE, required: true }, -// ], -// [FileType.TextMarkdown]: [ -// { value: MAIN_CONTENT_PREPROCESS_VALUE, required: true }, -// { value: PreprocessValue.section_title }, -// ], -// [FileType.Code]: [{ value: MAIN_CONTENT_PREPROCESS_VALUE, required: true }], -// [FileType.Html]: [ -// { value: MAIN_CONTENT_PREPROCESS_VALUE, required: true }, -// { value: PreprocessValue.section_title }, -// ], -// [FileType.Doc]: [ -// { value: MAIN_CONTENT_PREPROCESS_VALUE, required: true }, -// { value: PreprocessValue.section_title }, -// ], -// [FileType.Docx]: [ -// { value: MAIN_CONTENT_PREPROCESS_VALUE, required: true }, -// { value: PreprocessValue.section_title }, -// ], -// }; - -// function getPreprocessOptionConfigs(fileType?: FileType) { -// if (!fileType) { -// return DefaultPreprocessOptionConfigs; -// } - -// return PreprocessOptionConfigsMap[fileType] ?? DefaultPreprocessOptionConfigs; -// } - -// function normalizePreprocessValuesByFileType( -// fileType: FileType | undefined, -// values: string[] | undefined, -// ) { -// const optionConfigs = getPreprocessOptionConfigs(fileType); -// const allowedValueSet = new Set(optionConfigs.map((x) => x.value)); -// const requiredValues = optionConfigs -// .filter((x) => x.required) -// .map((x) => x.value); -// const normalizedOptionalValues = (Array.isArray(values) ? values : []).filter( -// (value) => allowedValueSet.has(value as PreprocessValue), -// ) as PreprocessValue[]; - -// return Array.from( -// new Set([...requiredValues, ...normalizedOptionalValues]), -// ); -// } - -// function isSameStringArray(a: string[] | undefined, b: string[]) { -// if (!a || a.length !== b.length) { -// return false; -// } - -// return a.every((item, idx) => item === b[idx]); -// } - const FileFormatWidgetMap = { [FileType.PDF]: PdfFormFields, [FileType.Spreadsheet]: SpreadsheetFormFields, @@ -163,6 +86,9 @@ export const FormSchema = z.object({ enable_multi_column: z.boolean().optional(), remove_toc: z.boolean().optional(), remove_header_footer: z.boolean().optional(), + pages: z + .array(z.object({ from: z.coerce.number(), to: z.coerce.number() })) + .optional(), }), ), }); @@ -213,57 +139,6 @@ function ParserItem({ [form, index], ); - // const handlePreprocessChange = useCallback( - // (value: PreprocessValue[]) => { - // form.setValue(`setups.${index}.preprocess`, value, { - // shouldDirty: true, - // shouldValidate: true, - // shouldTouch: true, - // }); - // }, - // [form, index], - // ); - - // const preprocessOptions = useMemo(() => { - // const optionConfigs = getPreprocessOptionConfigs(fileFormat as FileType); - - // return optionConfigs.map((optionConfig) => { - // const labelMap: Record = { - // [MAIN_CONTENT_PREPROCESS_VALUE]: t('flow.preprocess.mainContent'), - // [PreprocessValue.section_title]: t('flow.preprocess.sectionTitle'), - // [PreprocessValue.abstract]: t('flow.preprocess.abstract'), - // [PreprocessValue.author]: t('flow.preprocess.author'), - // }; - - // const label = labelMap[optionConfig.value] || optionConfig.value; - - // return { - // value: optionConfig.value, - // disabled: optionConfig.required, - // label: label, - // }; - // }); - // }, [fileFormat, t]); - - // useEffect(() => { - // const currentPreprocessValues = form.getValues( - // `setups.${index}.preprocess`, - // ) as string[] | undefined; - // const normalizedPreprocessValues = normalizePreprocessValuesByFileType( - // fileFormat as FileType, - // currentPreprocessValues, - // ); - - // if ( - // !isSameStringArray(currentPreprocessValues, normalizedPreprocessValues) - // ) { - // form.setValue(`setups.${index}.preprocess`, normalizedPreprocessValues, { - // shouldDirty: false, - // shouldValidate: true, - // }); - // } - // }, [fileFormat, form, index]); - return (
    - {/* - {(field) => ( - { - const nextValues = normalizePreprocessValuesByFileType( - fileFormat as FileType, - val, - ); - field.onChange(nextValues); - handlePreprocessChange(nextValues); - }} - showSelectAll={false} - options={preprocessOptions} - > - )} - */} + {index < fieldLength - 1 && } ); diff --git a/web/src/pages/agent/form/parser-form/pdf-form-fields.tsx b/web/src/pages/agent/form/parser-form/pdf-form-fields.tsx index c1ac251568..2436e86df3 100644 --- a/web/src/pages/agent/form/parser-form/pdf-form-fields.tsx +++ b/web/src/pages/agent/form/parser-form/pdf-form-fields.tsx @@ -8,6 +8,7 @@ import { SelectWithSearchFlagOptionType, } from '@/components/originui/select-with-search'; import { RAGFlowFormItem } from '@/components/ragflow-form'; +import { isGoBackend } from '@/utils/backend-runtime'; import { isEmpty } from 'lodash'; import { useEffect, useMemo } from 'react'; import { useFormContext, useWatch } from 'react-hook-form'; @@ -22,6 +23,7 @@ import { TwoColumnCheckFormField, } from './common-form-fields'; import { CommonProps } from './interface'; +import { DynamicPageRange } from './dynamic-page-range'; import { useSetInitialLanguage } from './use-set-initial-language'; import { buildFieldNameWithPrefix } from './utils'; @@ -107,6 +109,7 @@ export function PdfFormFields({ prefix }: CommonProps) { + {isGoBackend() && } {!flattenMediaToText && ( = { output_format: cur.output_format, @@ -230,6 +231,11 @@ export function transformParserParams(params: ParserFormSchemaType) { enable_multi_column: cur.enable_multi_column, remove_toc: cur.remove_toc, remove_header_footer: cur.remove_header_footer || false, + ...(isGoBackend() + ? { + pages: cur.pages?.map((x) => [x.from, x.to]) ?? [], + } + : {}), }; // Only include TCADP parameters if TCADP Parser is selected if (cur.parse_method?.toLowerCase() === 'tcadp parser') { diff --git a/web/src/pages/dataset/setting/form-schema.ts b/web/src/pages/dataset/setting/form-schema.ts index 3f39737dc8..367998677f 100644 --- a/web/src/pages/dataset/setting/form-schema.ts +++ b/web/src/pages/dataset/setting/form-schema.ts @@ -15,6 +15,7 @@ export const formSchema = z parser_id: z.string().optional(), avatar: z.any().nullish(), permission: z.string().optional(), + language: z.string().optional(), embedding_model: z.string(), pagerank: z.number(), parser_config: z.record(z.string(), z.any()).optional(), diff --git a/web/src/pages/dataset/setting/general-form.tsx b/web/src/pages/dataset/setting/general-form.tsx index 8972bd27f7..b868f2e6b4 100644 --- a/web/src/pages/dataset/setting/general-form.tsx +++ b/web/src/pages/dataset/setting/general-form.tsx @@ -1,5 +1,7 @@ import { AvatarUpload } from '@/components/avatar-upload'; +import { SelectWithSearch } from '@/components/originui/select-with-search'; import PageRankFormField from '@/components/page-rank-form-field'; +import { RAGFlowFormItem } from '@/components/ragflow-form'; import { FormControl, FormField, @@ -8,6 +10,9 @@ import { FormMessage, } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; +import { LanguageTranslationMap } from '@/constants/common'; +import { isGoBackend } from '@/utils/backend-runtime'; +import { useMemo } from 'react'; import { useFormContext } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { useKnowledgeBaseContext } from '../contexts/knowledge-base-context'; @@ -18,6 +23,13 @@ export function GeneralForm() { const form = useFormContext(); const { t } = useTranslation(); + const languageOptions = useMemo(() => { + return Object.keys(LanguageTranslationMap).map((x) => ({ + label: x, + value: x, + })); + }, []); + return ( <> )} /> + {isGoBackend() && ( +
    + + + +
    + )}