fix: fix dataset-page's bugs (#8786)

### What problem does this PR solve?

fix dataset-page's bugs,Input component supports icon, added Radio
component, and removed antd from chunk-result-bar page [#3221
](https://github.com/infiniflow/ragflow/issues/3221)

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
chanx
2025-07-11 11:34:36 +08:00
committed by GitHub
parent 07208e519b
commit 52dce4329d
23 changed files with 399 additions and 153 deletions

View File

@ -12,7 +12,13 @@ import {
type EntityTypesFormFieldProps = {
name?: string;
};
const initialEntityTypes = [
'organization',
'person',
'geo',
'event',
'category',
];
export function EntityTypesFormField({
name = 'parser_config.entity_types',
}: EntityTypesFormFieldProps) {
@ -23,24 +29,27 @@ export function EntityTypesFormField({
<FormField
control={form.control}
name={name}
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">
<span className="text-red-600">*</span> {t('entityTypes')}
</FormLabel>
<div className="w-3/4">
<FormControl>
<EditTag {...field}></EditTag>
</FormControl>
defaultValue={initialEntityTypes}
render={({ field }) => {
return (
<FormItem className=" items-center space-y-0 ">
<div className="flex items-center">
<FormLabel className="text-sm text-muted-foreground whitespace-nowrap w-1/4">
<span className="text-red-600">*</span> {t('entityTypes')}
</FormLabel>
<div className="w-3/4">
<FormControl>
<EditTag {...field}></EditTag>
</FormControl>
</div>
</div>
</div>
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
)}
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
);
}}
/>
);
}

View File

@ -25,7 +25,7 @@ const EntityTypesItem = ({
rules={[{ required: true }]}
initialValue={initialEntityTypes}
>
<EditTag></EditTag>
<EditTag value={field}></EditTag>
</Form.Item>
);
};

View File

@ -1,12 +1,17 @@
import { useGetPaginationWithRouter } from '@/hooks/logic-hooks';
import { useCallback, useState } from 'react';
import { FilterChange, FilterValue } from './interface';
export function useHandleFilterSubmit() {
const [filterValue, setFilterValue] = useState<FilterValue>({});
const handleFilterSubmit: FilterChange = useCallback((value) => {
setFilterValue(value);
}, []);
const { setPagination } = useGetPaginationWithRouter();
const handleFilterSubmit: FilterChange = useCallback(
(value) => {
setFilterValue(value);
setPagination({ page: 1 });
},
[setPagination],
);
return { filterValue, setFilterValue, handleFilterSubmit };
}

View File

@ -7,7 +7,7 @@ interface IProps {
max?: number;
}
export function MaxTokenNumberFormField({ max = 2048 }: IProps) {
export function MaxTokenNumberFormField({ max = 2048, initialValue }: IProps) {
const { t } = useTranslate('knowledgeConfiguration');
return (
@ -15,6 +15,7 @@ export function MaxTokenNumberFormField({ max = 2048 }: IProps) {
name={'parser_config.chunk_token_num'}
label={t('chunkTokenNumber')}
max={max}
defaultValue={initialValue ?? 0}
layout={FormLayout.Horizontal}
></SliderInputFormField>
);

View File

@ -1,24 +1,49 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
type InputProps = React.ComponentProps<'input'> & {
icon?: React.ReactNode;
iconPosition?: 'left' | 'right';
};
function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
function Input({
className,
type,
icon,
iconPosition = 'left',
...props
}: InputProps) {
return (
<input
type={type}
data-slot="input"
className={cn(
'border-input file:text-foreground placeholder:text-muted-foreground/70 flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
type === 'search' &&
'[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none [&::-webkit-search-results-button]:appearance-none [&::-webkit-search-results-decoration]:appearance-none',
type === 'file' &&
'text-muted-foreground/70 file:border-input file:text-foreground p-0 pr-3 italic file:me-3 file:h-full file:border-0 file:border-r file:border-solid file:bg-transparent file:px-3 file:text-sm file:font-medium file:not-italic',
className,
<div className="relative">
{icon && (
<div
className={cn(
'absolute w-1 top-0 flex h-full items-center justify-center pointer-events-none',
iconPosition === 'left' ? 'left-5' : 'right-5',
iconPosition === 'left' ? 'pr-2' : 'pl-2',
)}
>
{icon}
</div>
)}
{...props}
/>
<input
type={type}
data-slot="input"
className={cn(
'border-input file:text-foreground placeholder:text-muted-foreground/70 flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
type === 'search' &&
'[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none [&::-webkit-search-results-button]:appearance-none [&::-webkit-search-results-decoration]:appearance-none',
type === 'file' &&
'text-muted-foreground/70 file:border-input file:text-foreground p-0 pr-3 italic file:me-3 file:h-full file:border-0 file:border-r file:border-solid file:bg-transparent file:px-3 file:text-sm file:font-medium file:not-italic',
icon && iconPosition === 'left' && 'pl-7',
icon && iconPosition === 'right' && 'pr-7',
className,
)}
{...props}
/>
</div>
);
}

View File

@ -64,10 +64,10 @@ const RaptorFormFields = () => {
control={form.control}
name={UseRaptorField}
render={({ field }) => {
if (typeof field.value === 'undefined') {
// default value set
form.setValue('parser_config.raptor.use_raptor', false);
}
// if (typeof field.value === 'undefined') {
// // default value set
// form.setValue('parser_config.raptor.use_raptor', false);
// }
return (
<FormItem
defaultChecked={false}
@ -102,27 +102,33 @@ const RaptorFormFields = () => {
<FormField
control={form.control}
name={'parser_config.raptor.prompt'}
render={({ field }) => (
<FormItem className=" items-center space-y-0 ">
<div className="flex items-start">
<FormLabel
tooltip={t('promptTip')}
className="text-sm text-muted-foreground whitespace-nowrap w-1/4"
>
{t('prompt')}
</FormLabel>
<div className="w-3/4">
<FormControl>
<Textarea {...field} rows={8} />
</FormControl>
render={({ field }) => {
return (
<FormItem className=" items-center space-y-0 ">
<div className="flex items-start">
<FormLabel
tooltip={t('promptTip')}
className="text-sm text-muted-foreground whitespace-nowrap w-1/4"
>
{t('prompt')}
</FormLabel>
<div className="w-3/4">
<FormControl>
<Textarea
{...field}
rows={8}
defaultValue={t('promptText')}
/>
</FormControl>
</div>
</div>
</div>
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
)}
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
);
}}
/>
<SliderInputFormField
name={'parser_config.raptor.max_token'}
@ -164,7 +170,7 @@ const RaptorFormFields = () => {
<div className="w-3/4">
<FormControl defaultValue={0}>
<div className="flex gap-4">
<Input {...field} />
<Input {...field} defaultValue={0} />
<Button
size={'sm'}
onClick={handleGenerate}

View File

@ -4,10 +4,12 @@ import { cn } from '@/lib/utils';
import { Search } from 'lucide-react';
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
extends React.InputHTMLAttributes<HTMLInputElement> {
value?: string | number | readonly string[] | undefined;
}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
({ className, type, value, ...props }, ref) => {
return (
<input
type={type}
@ -16,6 +18,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
className,
)}
ref={ref}
value={value ?? ''}
{...props}
/>
);

View File

@ -5,7 +5,27 @@ import * as React from 'react';
import { cn } from '@/lib/utils';
const Popover = PopoverPrimitive.Root;
const Popover = (props: PopoverPrimitive.PopoverProps) => {
const { children, open: openState, onOpenChange } = props;
const [open, setOpen] = React.useState(true);
React.useEffect(() => {
setOpen(!!openState);
}, [openState]);
const handleOnOpenChange = React.useCallback(
(e: boolean) => {
if (onOpenChange) {
onOpenChange?.(e);
}
setOpen(e);
},
[onOpenChange],
);
return (
<PopoverPrimitive.Root open={open} onOpenChange={handleOnOpenChange}>
{children}
</PopoverPrimitive.Root>
);
};
const PopoverTrigger = PopoverPrimitive.Trigger;

View File

@ -0,0 +1,133 @@
import { cn } from '@/lib/utils';
import { Radio as LucideRadio } from 'lucide-react';
import React, { useContext, useState } from 'react';
const RadioGroupContext = React.createContext<{
value: string | number;
onChange: (value: string | number) => void;
disabled?: boolean;
} | null>(null);
type RadioProps = {
value: string | number;
checked?: boolean;
disabled?: boolean;
onChange?: (checked: boolean) => void;
children?: React.ReactNode;
};
function Radio({ value, checked, disabled, onChange, children }: RadioProps) {
const groupContext = useContext(RadioGroupContext);
const isControlled = checked !== undefined;
// const [internalChecked, setInternalChecked] = useState(false);
const isChecked = isControlled ? checked : groupContext?.value === value;
const mergedDisabled = disabled || groupContext?.disabled;
const handleClick = () => {
if (mergedDisabled) return;
// if (!isControlled) {
// setInternalChecked(!isChecked);
// }
if (onChange) {
onChange(!isChecked);
}
if (groupContext && !groupContext.disabled) {
groupContext.onChange(value);
}
};
return (
<label
className={cn(
'flex items-center cursor-pointer gap-2 text-sm',
mergedDisabled && 'cursor-not-allowed opacity-50',
)}
>
<span
className={cn(
'flex h-4 w-4 items-center justify-center rounded-full border border-input transition-colors',
'peer ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
isChecked && 'border-primary bg-primary/10',
mergedDisabled && 'border-muted',
)}
onClick={handleClick}
>
{isChecked && (
<LucideRadio className="h-3 w-3 fill-primary text-primary" />
)}
</span>
{children && <span className="text-foreground">{children}</span>}
</label>
);
}
type RadioGroupProps = {
value?: string | number;
defaultValue?: string | number;
onChange?: (value: string | number) => void;
disabled?: boolean;
children: React.ReactNode;
className?: string;
direction?: 'horizontal' | 'vertical';
};
function Group({
value,
defaultValue,
onChange,
disabled,
children,
className,
direction = 'horizontal',
}: RadioGroupProps) {
const [internalValue, setInternalValue] = useState(defaultValue || '');
const isControlled = value !== undefined;
const mergedValue = isControlled ? value : internalValue;
const handleChange = (val: string | number) => {
if (disabled) return;
if (!isControlled) {
setInternalValue(val);
}
if (onChange) {
onChange(val);
}
};
return (
<RadioGroupContext.Provider
value={{
value: mergedValue,
onChange: handleChange,
disabled,
}}
>
<div
className={cn(
'flex gap-4',
direction === 'vertical' ? 'flex-col' : 'flex-row',
className,
)}
>
{React.Children.map(children, (child) =>
React.cloneElement(child as React.ReactElement, {
disabled: disabled || child?.props?.disabled,
}),
)}
</div>
</RadioGroupContext.Provider>
);
}
const RadioComponent = Object.assign(Radio, {
Group,
});
export { RadioComponent as Radio };

View File

@ -31,6 +31,15 @@ export function Segmented({
onChange,
className,
}: SegmentedProps) {
const [selectedValue, setSelectedValue] = React.useState<
SegmentedValue | undefined
>(value);
const handleOnChange = (e: SegmentedValue) => {
if (onChange) {
onChange(e);
}
setSelectedValue(e);
};
return (
<div
className={cn(
@ -48,11 +57,11 @@ export function Segmented({
className={cn(
'inline-flex items-center px-6 py-2 text-base font-normal rounded-3xl cursor-pointer text-text-badge',
{
'bg-text-title': value === actualValue,
'text-text-title-invert': value === actualValue,
'bg-text-title': selectedValue === actualValue,
'text-text-title-invert': selectedValue === actualValue,
},
)}
onClick={() => onChange?.(actualValue)}
onClick={() => handleOnChange(actualValue)}
>
{isObject ? option.label : option}
</div>