mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
Fix: Remove antd from dataset-page (#8830)
### What problem does this PR solve? remove antd from dataset-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:
@ -1,22 +0,0 @@
|
|||||||
.tweenGroup {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
// width: 100%;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag {
|
|
||||||
max-width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 2px 20px 0px 4px;
|
|
||||||
height: 26px;
|
|
||||||
font-size: 14px;
|
|
||||||
.textEllipsis();
|
|
||||||
position: relative;
|
|
||||||
:global(.ant-tag-close-icon) {
|
|
||||||
position: absolute;
|
|
||||||
top: 7px;
|
|
||||||
right: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,21 +1,24 @@
|
|||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import type { InputRef } from 'antd';
|
|
||||||
import { Input, Tag, theme, Tooltip } from 'antd';
|
|
||||||
import { TweenOneGroup } from 'rc-tween-one';
|
import { TweenOneGroup } from 'rc-tween-one';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import styles from './index.less';
|
import { X } from 'lucide-react';
|
||||||
|
import { Button } from '../ui/button';
|
||||||
|
import {
|
||||||
|
HoverCard,
|
||||||
|
HoverCardContent,
|
||||||
|
HoverCardTrigger,
|
||||||
|
} from '../ui/hover-card';
|
||||||
|
import { Input } from '../ui/input';
|
||||||
interface EditTagsProps {
|
interface EditTagsProps {
|
||||||
value?: string[];
|
value?: string[];
|
||||||
onChange?: (tags: string[]) => void;
|
onChange?: (tags: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EditTag = ({ value = [], onChange }: EditTagsProps) => {
|
const EditTag = ({ value = [], onChange }: EditTagsProps) => {
|
||||||
const { token } = theme.useToken();
|
|
||||||
const [inputVisible, setInputVisible] = useState(false);
|
const [inputVisible, setInputVisible] = useState(false);
|
||||||
const [inputValue, setInputValue] = useState('');
|
const [inputValue, setInputValue] = useState('');
|
||||||
const inputRef = useRef<InputRef>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (inputVisible) {
|
if (inputVisible) {
|
||||||
@ -50,34 +53,66 @@ const EditTag = ({ value = [], onChange }: EditTagsProps) => {
|
|||||||
|
|
||||||
const forMap = (tag: string) => {
|
const forMap = (tag: string) => {
|
||||||
return (
|
return (
|
||||||
<Tooltip title={tag}>
|
<HoverCard>
|
||||||
<Tag
|
<HoverCardContent side="top">{tag}</HoverCardContent>
|
||||||
key={tag}
|
<HoverCardTrigger>
|
||||||
className={styles.tag}
|
<div
|
||||||
closable
|
key={tag}
|
||||||
onClose={(e) => {
|
className="w-fit flex items-center justify-center gap-2 border-dashed border px-1 rounded-sm bg-background-card"
|
||||||
e.preventDefault();
|
>
|
||||||
handleClose(tag);
|
<div className="flex gap-2 items-center">
|
||||||
}}
|
<div className="max-w-80 overflow-hidden text-ellipsis">
|
||||||
>
|
{tag}
|
||||||
{tag}
|
</div>
|
||||||
</Tag>
|
<X
|
||||||
</Tooltip>
|
className="w-4 h-4 text-muted-foreground hover:text-primary"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleClose(tag);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</HoverCardTrigger>
|
||||||
|
</HoverCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const tagChild = value?.map(forMap);
|
const tagChild = value?.map(forMap);
|
||||||
|
|
||||||
const tagPlusStyle: React.CSSProperties = {
|
const tagPlusStyle: React.CSSProperties = {
|
||||||
background: token.colorBgContainer,
|
|
||||||
borderStyle: 'dashed',
|
borderStyle: 'dashed',
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
{inputVisible ? (
|
||||||
|
<Input
|
||||||
|
ref={inputRef}
|
||||||
|
type="text"
|
||||||
|
className="h-8 bg-background-card"
|
||||||
|
value={inputValue}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
onBlur={handleInputConfirm}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e?.key === 'Enter') {
|
||||||
|
handleInputConfirm();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
variant="dashed"
|
||||||
|
className="w-fit flex items-center justify-center gap-2 bg-background-card"
|
||||||
|
onClick={showInput}
|
||||||
|
style={tagPlusStyle}
|
||||||
|
>
|
||||||
|
<PlusOutlined />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
{Array.isArray(tagChild) && tagChild.length > 0 && (
|
{Array.isArray(tagChild) && tagChild.length > 0 && (
|
||||||
<TweenOneGroup
|
<TweenOneGroup
|
||||||
className={styles.tweenGroup}
|
className="flex gap-2 flex-wrap mt-2"
|
||||||
enter={{
|
enter={{
|
||||||
scale: 0.8,
|
scale: 0.8,
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
@ -95,21 +130,6 @@ const EditTag = ({ value = [], onChange }: EditTagsProps) => {
|
|||||||
{tagChild}
|
{tagChild}
|
||||||
</TweenOneGroup>
|
</TweenOneGroup>
|
||||||
)}
|
)}
|
||||||
{inputVisible ? (
|
|
||||||
<Input
|
|
||||||
ref={inputRef}
|
|
||||||
type="text"
|
|
||||||
size="small"
|
|
||||||
value={inputValue}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
onBlur={handleInputConfirm}
|
|
||||||
onPressEnter={handleInputConfirm}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Tag onClick={showInput} style={tagPlusStyle}>
|
|
||||||
<PlusOutlined />
|
|
||||||
</Tag>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
.primitiveImg {
|
|
||||||
display: inline-block;
|
|
||||||
max-height: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image {
|
|
||||||
max-width: 100px;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
.imagePreview {
|
|
||||||
display: block;
|
|
||||||
max-width: 45vw;
|
|
||||||
max-height: 40vh;
|
|
||||||
}
|
|
||||||
@ -1,8 +1,6 @@
|
|||||||
import { api_host } from '@/utils/api';
|
import { api_host } from '@/utils/api';
|
||||||
import { Popover } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
interface IImage {
|
interface IImage {
|
||||||
id: string;
|
id: string;
|
||||||
@ -16,7 +14,7 @@ const Image = ({ id, className, ...props }: IImage) => {
|
|||||||
{...props}
|
{...props}
|
||||||
src={`${api_host}/document/image/${id}`}
|
src={`${api_host}/document/image/${id}`}
|
||||||
alt=""
|
alt=""
|
||||||
className={classNames(styles.primitiveImg, className)}
|
className={classNames('max-w-[45vw] max-h-[40wh] block', className)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -25,11 +23,13 @@ export default Image;
|
|||||||
|
|
||||||
export const ImageWithPopover = ({ id }: { id: string }) => {
|
export const ImageWithPopover = ({ id }: { id: string }) => {
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover>
|
||||||
placement="left"
|
<PopoverTrigger>
|
||||||
content={<Image id={id} className={styles.imagePreview}></Image>}
|
<Image id={id} className="max-h-[100px] inline-block"></Image>
|
||||||
>
|
</PopoverTrigger>
|
||||||
<Image id={id} className={styles.image}></Image>
|
<PopoverContent>
|
||||||
|
<Image id={id} className="max-w-[100px] object-contain"></Image>
|
||||||
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,11 +6,12 @@ type InputProps = React.ComponentProps<'input'> & {
|
|||||||
iconPosition?: 'left' | 'right';
|
iconPosition?: 'left' | 'right';
|
||||||
};
|
};
|
||||||
|
|
||||||
function Input({
|
const Input = function ({
|
||||||
className,
|
className,
|
||||||
type,
|
type,
|
||||||
icon,
|
icon,
|
||||||
iconPosition = 'left',
|
iconPosition = 'left',
|
||||||
|
ref,
|
||||||
...props
|
...props
|
||||||
}: InputProps) {
|
}: InputProps) {
|
||||||
return (
|
return (
|
||||||
@ -27,6 +28,7 @@ function Input({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<input
|
<input
|
||||||
|
ref={ref}
|
||||||
type={type}
|
type={type}
|
||||||
data-slot="input"
|
data-slot="input"
|
||||||
className={cn(
|
className={cn(
|
||||||
@ -45,6 +47,6 @@ function Input({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export { Input };
|
export { Input };
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useId,
|
useId,
|
||||||
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
@ -27,50 +28,9 @@ import {
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { RAGFlowSelectOptionType } from '../ui/select';
|
import { RAGFlowSelectOptionType } from '../ui/select';
|
||||||
|
|
||||||
const countries = [
|
|
||||||
{
|
|
||||||
label: 'America',
|
|
||||||
options: [
|
|
||||||
{ value: 'United States', label: '🇺🇸' },
|
|
||||||
{ value: 'Canada', label: '🇨🇦' },
|
|
||||||
{ value: 'Mexico', label: '🇲🇽' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Africa',
|
|
||||||
options: [
|
|
||||||
{ value: 'South Africa', label: '🇿🇦' },
|
|
||||||
{ value: 'Nigeria', label: '🇳🇬' },
|
|
||||||
{ value: 'Morocco', label: '🇲🇦' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Asia',
|
|
||||||
options: [
|
|
||||||
{ value: 'China', label: '🇨🇳' },
|
|
||||||
{ value: 'Japan', label: '🇯🇵' },
|
|
||||||
{ value: 'India', label: '🇮🇳' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Europe',
|
|
||||||
options: [
|
|
||||||
{ value: 'United Kingdom', label: '🇬🇧' },
|
|
||||||
{ value: 'France', label: '🇫🇷' },
|
|
||||||
{ value: 'Germany', label: '🇩🇪' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Oceania',
|
|
||||||
options: [
|
|
||||||
{ value: 'Australia', label: '🇦🇺' },
|
|
||||||
{ value: 'New Zealand', label: '🇳🇿' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export type SelectWithSearchFlagOptionType = {
|
export type SelectWithSearchFlagOptionType = {
|
||||||
label: string;
|
label: string;
|
||||||
|
value?: string;
|
||||||
options: RAGFlowSelectOptionType[];
|
options: RAGFlowSelectOptionType[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -84,99 +44,113 @@ export type SelectWithSearchFlagProps = {
|
|||||||
export const SelectWithSearch = forwardRef<
|
export const SelectWithSearch = forwardRef<
|
||||||
React.ElementRef<typeof Button>,
|
React.ElementRef<typeof Button>,
|
||||||
SelectWithSearchFlagProps
|
SelectWithSearchFlagProps
|
||||||
>(
|
>(({ value: val = '', onChange, options = [], triggerClassName }, ref) => {
|
||||||
(
|
const id = useId();
|
||||||
{ value: val = '', onChange, options = countries, triggerClassName },
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
ref,
|
const [value, setValue] = useState<string>('');
|
||||||
) => {
|
|
||||||
const id = useId();
|
|
||||||
const [open, setOpen] = useState<boolean>(false);
|
|
||||||
const [value, setValue] = useState<string>('');
|
|
||||||
|
|
||||||
const handleSelect = useCallback(
|
const handleSelect = useCallback(
|
||||||
(val: string) => {
|
(val: string) => {
|
||||||
setValue(val);
|
|
||||||
setOpen(false);
|
|
||||||
onChange?.(val);
|
|
||||||
},
|
|
||||||
[onChange],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setValue(val);
|
setValue(val);
|
||||||
}, [val]);
|
setOpen(false);
|
||||||
|
onChange?.(val);
|
||||||
|
},
|
||||||
|
[onChange],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
<Popover open={open} onOpenChange={setOpen}>
|
setValue(val);
|
||||||
<PopoverTrigger asChild>
|
}, [val]);
|
||||||
<Button
|
const selectLabel = useMemo(() => {
|
||||||
id={id}
|
const optionTemp = options[0];
|
||||||
variant="outline"
|
if (optionTemp?.options) {
|
||||||
role="combobox"
|
return optionTemp.options.find((opt) => opt.value === value)?.label || '';
|
||||||
aria-expanded={open}
|
} else {
|
||||||
ref={ref}
|
return options.find((opt) => opt.value === value)?.label || '';
|
||||||
className={cn(
|
}
|
||||||
'bg-background hover:bg-background border-input w-full justify-between px-3 font-normal outline-offset-0 outline-none focus-visible:outline-[3px]',
|
}, [options, value]);
|
||||||
triggerClassName,
|
return (
|
||||||
)}
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
>
|
<PopoverTrigger asChild>
|
||||||
{value ? (
|
<Button
|
||||||
<span className="flex min-w-0 options-center gap-2">
|
id={id}
|
||||||
<span className="text-lg leading-none truncate">
|
variant="outline"
|
||||||
{
|
role="combobox"
|
||||||
options
|
aria-expanded={open}
|
||||||
.map((group) =>
|
ref={ref}
|
||||||
group.options.find((item) => item.value === value),
|
className={cn(
|
||||||
)
|
'bg-background hover:bg-background border-input w-full justify-between px-3 font-normal outline-offset-0 outline-none focus-visible:outline-[3px]',
|
||||||
.filter(Boolean)[0]?.label
|
triggerClassName,
|
||||||
}
|
)}
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span className="text-muted-foreground">Select value</span>
|
|
||||||
)}
|
|
||||||
<ChevronDownIcon
|
|
||||||
size={16}
|
|
||||||
className="text-muted-foreground/80 shrink-0"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent
|
|
||||||
className="border-input w-full min-w-[var(--radix-popper-anchor-width)] p-0"
|
|
||||||
align="start"
|
|
||||||
>
|
>
|
||||||
<Command>
|
{value ? (
|
||||||
<CommandInput placeholder="Search ..." />
|
<span className="flex min-w-0 options-center gap-2">
|
||||||
<CommandList>
|
<span className="text-lg leading-none truncate">
|
||||||
<CommandEmpty>No data found.</CommandEmpty>
|
{selectLabel}
|
||||||
{options.map((group) => (
|
</span>
|
||||||
<Fragment key={group.label}>
|
</span>
|
||||||
<CommandGroup heading={group.label}>
|
) : (
|
||||||
{group.options.map((option) => (
|
<span className="text-muted-foreground">Select value</span>
|
||||||
<CommandItem
|
)}
|
||||||
key={option.value}
|
<ChevronDownIcon
|
||||||
value={option.value}
|
size={16}
|
||||||
onSelect={handleSelect}
|
className="text-muted-foreground/80 shrink-0"
|
||||||
>
|
aria-hidden="true"
|
||||||
<span className="text-lg leading-none">
|
/>
|
||||||
{option.label}
|
</Button>
|
||||||
</span>
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
className="border-input w-full min-w-[var(--radix-popper-anchor-width)] p-0"
|
||||||
|
align="start"
|
||||||
|
>
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder="Search ..." />
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>No data found.</CommandEmpty>
|
||||||
|
{options.map((group) => {
|
||||||
|
if (group.options) {
|
||||||
|
return (
|
||||||
|
<Fragment key={group.label}>
|
||||||
|
<CommandGroup heading={group.label}>
|
||||||
|
{group.options.map((option) => (
|
||||||
|
<CommandItem
|
||||||
|
key={option.value}
|
||||||
|
value={option.value}
|
||||||
|
onSelect={handleSelect}
|
||||||
|
>
|
||||||
|
<span className="text-lg leading-none">
|
||||||
|
{option.label}
|
||||||
|
</span>
|
||||||
|
|
||||||
{value === option.value && (
|
{value === option.value && (
|
||||||
<CheckIcon size={16} className="ml-auto" />
|
<CheckIcon size={16} className="ml-auto" />
|
||||||
)}
|
)}
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
))}
|
))}
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
);
|
||||||
</CommandList>
|
} else {
|
||||||
</Command>
|
return (
|
||||||
</PopoverContent>
|
<CommandItem
|
||||||
</Popover>
|
key={group.value}
|
||||||
);
|
value={group.value}
|
||||||
},
|
onSelect={handleSelect}
|
||||||
);
|
>
|
||||||
|
<span className="text-lg leading-none">{group.label}</span>
|
||||||
|
|
||||||
|
{value === group.value && (
|
||||||
|
<CheckIcon size={16} className="ml-auto" />
|
||||||
|
)}
|
||||||
|
</CommandItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
SelectWithSearch.displayName = 'SelectWithSearch';
|
SelectWithSearch.displayName = 'SelectWithSearch';
|
||||||
|
|||||||
@ -22,6 +22,7 @@ const buttonVariants = cva(
|
|||||||
tertiary:
|
tertiary:
|
||||||
'bg-colors-background-sentiment-solid-primary text-colors-text-persist-light hover:bg-colors-background-sentiment-solid-primary/80',
|
'bg-colors-background-sentiment-solid-primary text-colors-text-persist-light hover:bg-colors-background-sentiment-solid-primary/80',
|
||||||
icon: 'bg-colors-background-inverse-standard text-foreground hover:bg-colors-background-inverse-standard/80',
|
icon: 'bg-colors-background-inverse-standard text-foreground hover:bg-colors-background-inverse-standard/80',
|
||||||
|
dashed: 'border border-dashed border-input hover:bg-accent',
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: 'h-8 px-2.5 py-1.5 ',
|
default: 'h-8 px-2.5 py-1.5 ',
|
||||||
@ -49,7 +50,10 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
const Comp = asChild ? Slot : 'button';
|
const Comp = asChild ? Slot : 'button';
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
className={cn(buttonVariants({ variant, size, className }))}
|
className={cn(
|
||||||
|
'bg-background-card',
|
||||||
|
buttonVariants({ variant, size, className }),
|
||||||
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|||||||
64
web/src/components/ui/divider.tsx
Normal file
64
web/src/components/ui/divider.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// src/components/ui/divider.tsx
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type Direction = 'horizontal' | 'vertical';
|
||||||
|
type DividerType = 'horizontal' | 'vertical' | 'text';
|
||||||
|
|
||||||
|
interface DividerProps {
|
||||||
|
direction?: Direction;
|
||||||
|
type?: DividerType;
|
||||||
|
text?: React.ReactNode;
|
||||||
|
color?: string;
|
||||||
|
margin?: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Divider: React.FC<DividerProps> = ({
|
||||||
|
direction = 'horizontal',
|
||||||
|
type = 'horizontal',
|
||||||
|
text,
|
||||||
|
color = 'border-muted-foreground/50',
|
||||||
|
margin = 'my-4',
|
||||||
|
className = '',
|
||||||
|
}) => {
|
||||||
|
const baseClasses = 'flex items-center';
|
||||||
|
const directionClass = direction === 'horizontal' ? 'flex-row' : 'flex-col';
|
||||||
|
const colorClass = color.startsWith('border-') ? color : `border-${color}`;
|
||||||
|
const marginClass = margin || '';
|
||||||
|
const textClass = 'px-4 text-sm text-muted-foreground';
|
||||||
|
|
||||||
|
// Default vertical style
|
||||||
|
if (direction === 'vertical') {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`h-full ${colorClass} border-l ${marginClass} ${className}`}
|
||||||
|
>
|
||||||
|
{type === 'text' && (
|
||||||
|
<div className="transform -rotate-90 px-2 whitespace-nowrap">
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Horizontal with text
|
||||||
|
if (type === 'text') {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`${baseClasses} ${directionClass} ${marginClass} ${className}`}
|
||||||
|
>
|
||||||
|
<div className={`flex-1 ${colorClass} border-t`}></div>
|
||||||
|
<div className={textClass}>{text}</div>
|
||||||
|
<div className={`flex-1 ${colorClass} border-t`}></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default horizontal
|
||||||
|
return (
|
||||||
|
<div className={`${colorClass} border-t ${marginClass} ${className}`} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Divider;
|
||||||
@ -18,7 +18,7 @@ const HoverCardContent = React.forwardRef<
|
|||||||
align={align}
|
align={align}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
'z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-hover-card-content-transform-origin]',
|
'z-50 w-fit max-w-96 overflow-auto break-words whitespace-pre-wrap rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-hover-card-content-transform-origin]',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
199
web/src/components/ui/modal.tsx
Normal file
199
web/src/components/ui/modal.tsx
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
// src/components/ui/modal.tsx
|
||||||
|
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||||
|
import { Loader, X } from 'lucide-react';
|
||||||
|
import { FC, ReactNode, useCallback, useEffect, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
interface ModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange?: (open: boolean) => void;
|
||||||
|
title?: ReactNode;
|
||||||
|
children: ReactNode;
|
||||||
|
footer?: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
size?: 'small' | 'default' | 'large';
|
||||||
|
closable?: boolean;
|
||||||
|
closeIcon?: ReactNode;
|
||||||
|
maskClosable?: boolean;
|
||||||
|
destroyOnClose?: boolean;
|
||||||
|
full?: boolean;
|
||||||
|
confirmLoading?: boolean;
|
||||||
|
cancelText?: ReactNode | string;
|
||||||
|
okText?: ReactNode | string;
|
||||||
|
onOk?: () => void;
|
||||||
|
onCancel?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Modal: FC<ModalProps> = ({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
footer,
|
||||||
|
className = '',
|
||||||
|
size = 'default',
|
||||||
|
closable = true,
|
||||||
|
closeIcon = <X className="w-4 h-4" />,
|
||||||
|
maskClosable = true,
|
||||||
|
destroyOnClose = false,
|
||||||
|
full = false,
|
||||||
|
onOk,
|
||||||
|
onCancel,
|
||||||
|
confirmLoading,
|
||||||
|
cancelText,
|
||||||
|
okText,
|
||||||
|
}) => {
|
||||||
|
const sizeClasses = {
|
||||||
|
small: 'max-w-md',
|
||||||
|
default: 'max-w-2xl',
|
||||||
|
large: 'max-w-4xl',
|
||||||
|
};
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
// Handle ESC key close
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape' && maskClosable) {
|
||||||
|
onOpenChange?.(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
}, [maskClosable, onOpenChange]);
|
||||||
|
|
||||||
|
const handleCancel = useCallback(() => {
|
||||||
|
onOpenChange?.(false);
|
||||||
|
onCancel?.();
|
||||||
|
}, [onOpenChange, onCancel]);
|
||||||
|
|
||||||
|
const handleOk = useCallback(() => {
|
||||||
|
onOpenChange?.(true);
|
||||||
|
onOk?.();
|
||||||
|
}, [onOpenChange, onOk]);
|
||||||
|
const handleChange = (open: boolean) => {
|
||||||
|
onOpenChange?.(open);
|
||||||
|
if (open) {
|
||||||
|
handleOk();
|
||||||
|
}
|
||||||
|
if (!open) {
|
||||||
|
handleCancel();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const footEl = useMemo(() => {
|
||||||
|
let footerTemp;
|
||||||
|
if (footer) {
|
||||||
|
footerTemp = footer;
|
||||||
|
} else {
|
||||||
|
footerTemp = (
|
||||||
|
<div className="flex justify-end gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleCancel()}
|
||||||
|
className="px-2 py-1 border border-input rounded-md hover:bg-muted"
|
||||||
|
>
|
||||||
|
{cancelText ?? t('modal.cancelText')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={confirmLoading}
|
||||||
|
onClick={() => handleOk()}
|
||||||
|
className="px-2 py-1 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
|
||||||
|
>
|
||||||
|
{confirmLoading && (
|
||||||
|
<Loader className="inline-block mr-2 h-4 w-4 animate-spin" />
|
||||||
|
)}
|
||||||
|
{okText ?? t('modal.okText')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-end border-t border-border px-6 py-4">
|
||||||
|
{footerTemp}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [footer, cancelText, t, confirmLoading, okText, handleCancel, handleOk]);
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Root open={open} onOpenChange={handleChange}>
|
||||||
|
<DialogPrimitive.Portal>
|
||||||
|
<DialogPrimitive.Overlay
|
||||||
|
className="fixed inset-0 z-50 bg-colors-background-neutral-weak/50 backdrop-blur-sm flex items-center justify-center p-4"
|
||||||
|
onClick={() => maskClosable && onOpenChange?.(false)}
|
||||||
|
>
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
className={`relative w-[700px] ${full ? 'max-w-full' : sizeClasses[size]} ${className} bg-colors-background-neutral-standard rounded-lg shadow-lg transition-all`}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{/* title */}
|
||||||
|
{title && (
|
||||||
|
<div className="flex items-center justify-between border-b border-border px-6 py-4">
|
||||||
|
<DialogPrimitive.Title className="text-lg font-medium text-foreground">
|
||||||
|
{title}
|
||||||
|
</DialogPrimitive.Title>
|
||||||
|
{closable && (
|
||||||
|
<DialogPrimitive.Close asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex h-7 w-7 items-center justify-center rounded-full hover:bg-muted"
|
||||||
|
>
|
||||||
|
{closeIcon}
|
||||||
|
</button>
|
||||||
|
</DialogPrimitive.Close>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* content */}
|
||||||
|
<div className="p-6 overflow-y-auto max-h-[80vh]">
|
||||||
|
{destroyOnClose && !open ? null : children}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* footer */}
|
||||||
|
{footEl}
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</DialogPrimitive.Overlay>
|
||||||
|
</DialogPrimitive.Portal>
|
||||||
|
</DialogPrimitive.Root>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// example usage
|
||||||
|
/*
|
||||||
|
import { Modal } from '@/components/ui/modal';
|
||||||
|
|
||||||
|
function Demo() {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button onClick={() => setOpen(true)}>open modal</button>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
onOpenChange={setOpen}
|
||||||
|
title="title"
|
||||||
|
footer={
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button onClick={() => setOpen(false)} className="px-4 py-2 border rounded-md">
|
||||||
|
cancel
|
||||||
|
</button>
|
||||||
|
<button onClick={() => setOpen(false)} className="px-4 py-2 bg-primary text-white rounded-md">
|
||||||
|
ok
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="py-4">弹窗内容区域</div>
|
||||||
|
</Modal>
|
||||||
|
<Modal
|
||||||
|
title={'modal-title'}
|
||||||
|
onOk={handleOk}
|
||||||
|
confirmLoading={loading}
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
<div className="py-4">弹窗内容区域</div>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
*/
|
||||||
57
web/src/components/ui/space.tsx
Normal file
57
web/src/components/ui/space.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// src/components/ui/space.tsx
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type Direction = 'horizontal' | 'vertical';
|
||||||
|
type Size = 'small' | 'middle' | 'large';
|
||||||
|
|
||||||
|
interface SpaceProps {
|
||||||
|
direction?: Direction;
|
||||||
|
size?: Size;
|
||||||
|
align?: string;
|
||||||
|
justify?: string;
|
||||||
|
wrap?: boolean;
|
||||||
|
className?: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sizeClasses: Record<Size, string> = {
|
||||||
|
small: 'gap-2',
|
||||||
|
middle: 'gap-4',
|
||||||
|
large: 'gap-8',
|
||||||
|
};
|
||||||
|
|
||||||
|
const directionClasses: Record<Direction, string> = {
|
||||||
|
horizontal: 'flex-row',
|
||||||
|
vertical: 'flex-col',
|
||||||
|
};
|
||||||
|
|
||||||
|
const Space: React.FC<SpaceProps> = ({
|
||||||
|
direction = 'horizontal',
|
||||||
|
size = 'middle',
|
||||||
|
align,
|
||||||
|
justify,
|
||||||
|
wrap = false,
|
||||||
|
className = '',
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const baseClasses = 'flex';
|
||||||
|
const directionClass = directionClasses[direction];
|
||||||
|
const sizeClass = sizeClasses[size];
|
||||||
|
const alignClass = align ? `items-${align}` : '';
|
||||||
|
const justifyClass = justify ? `justify-${justify}` : '';
|
||||||
|
const wrapClass = wrap ? 'flex-wrap' : '';
|
||||||
|
|
||||||
|
const classes = [
|
||||||
|
baseClasses,
|
||||||
|
directionClass,
|
||||||
|
sizeClass,
|
||||||
|
alignClass,
|
||||||
|
justifyClass,
|
||||||
|
wrapClass,
|
||||||
|
className,
|
||||||
|
].join(' ');
|
||||||
|
|
||||||
|
return <div className={classes}>{children}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Space;
|
||||||
@ -1,51 +1,105 @@
|
|||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import {
|
||||||
|
ChangeEventHandler,
|
||||||
|
ComponentProps,
|
||||||
|
FocusEventHandler,
|
||||||
|
forwardRef,
|
||||||
|
TextareaHTMLAttributes,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
interface TextareaProps
|
||||||
|
extends Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'autoSize'> {
|
||||||
|
autoSize?: {
|
||||||
|
minRows?: number;
|
||||||
|
maxRows?: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||||
|
({ className, autoSize, ...props }, ref) => {
|
||||||
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
const getLineHeight = (element: HTMLElement): number => {
|
||||||
|
const style = window.getComputedStyle(element);
|
||||||
|
return parseInt(style.lineHeight, 10) || 20;
|
||||||
|
};
|
||||||
|
const adjustHeight = useCallback(() => {
|
||||||
|
if (!textareaRef.current) return;
|
||||||
|
const lineHeight = getLineHeight(textareaRef.current);
|
||||||
|
const maxHeight = (autoSize?.maxRows || 3) * lineHeight;
|
||||||
|
textareaRef.current.style.height = 'auto';
|
||||||
|
|
||||||
const Textarea = React.forwardRef<
|
requestAnimationFrame(() => {
|
||||||
HTMLTextAreaElement,
|
if (!textareaRef.current) return;
|
||||||
React.ComponentProps<'textarea'>
|
|
||||||
>(({ className, ...props }, ref) => {
|
const scrollHeight = textareaRef.current.scrollHeight;
|
||||||
return (
|
textareaRef.current.style.height = `${Math.min(scrollHeight, maxHeight)}px`;
|
||||||
<textarea
|
});
|
||||||
className={cn(
|
}, [autoSize]);
|
||||||
'flex min-h-[80px] w-full rounded-md border border-input bg-colors-background-inverse-weak px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm overflow-hidden',
|
|
||||||
className,
|
useEffect(() => {
|
||||||
)}
|
if (autoSize) {
|
||||||
ref={ref}
|
adjustHeight();
|
||||||
{...props}
|
}
|
||||||
/>
|
}, [textareaRef, autoSize, adjustHeight]);
|
||||||
);
|
|
||||||
});
|
useEffect(() => {
|
||||||
|
if (typeof ref === 'function') {
|
||||||
|
ref(textareaRef.current);
|
||||||
|
} else if (ref) {
|
||||||
|
ref.current = textareaRef.current;
|
||||||
|
}
|
||||||
|
}, [ref]);
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
className={cn(
|
||||||
|
'flex min-h-[80px] w-full rounded-md border border-input bg-colors-background-inverse-weak px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm overflow-hidden',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
rows={autoSize?.minRows ?? props.rows ?? undefined}
|
||||||
|
style={{
|
||||||
|
maxHeight: autoSize?.maxRows
|
||||||
|
? `${autoSize.maxRows * 20}px`
|
||||||
|
: undefined,
|
||||||
|
overflow: autoSize ? 'auto' : undefined,
|
||||||
|
}}
|
||||||
|
ref={textareaRef}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
Textarea.displayName = 'Textarea';
|
Textarea.displayName = 'Textarea';
|
||||||
|
|
||||||
export { Textarea };
|
export { Textarea };
|
||||||
|
|
||||||
type Value = string | readonly string[] | number | undefined;
|
type Value = string | readonly string[] | number | undefined;
|
||||||
|
|
||||||
export const BlurTextarea = React.forwardRef<
|
export const BlurTextarea = forwardRef<
|
||||||
HTMLTextAreaElement,
|
HTMLTextAreaElement,
|
||||||
React.ComponentProps<'textarea'> & {
|
ComponentProps<'textarea'> & {
|
||||||
value: Value;
|
value: Value;
|
||||||
onChange(value: Value): void;
|
onChange(value: Value): void;
|
||||||
}
|
}
|
||||||
>(({ value, onChange, ...props }, ref) => {
|
>(({ value, onChange, ...props }, ref) => {
|
||||||
const [val, setVal] = React.useState<Value>();
|
const [val, setVal] = useState<Value>();
|
||||||
|
|
||||||
const handleChange: React.ChangeEventHandler<HTMLTextAreaElement> =
|
const handleChange: ChangeEventHandler<HTMLTextAreaElement> = useCallback(
|
||||||
React.useCallback((e) => {
|
(e) => {
|
||||||
setVal(e.target.value);
|
setVal(e.target.value);
|
||||||
}, []);
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
const handleBlur: React.FocusEventHandler<HTMLTextAreaElement> =
|
const handleBlur: FocusEventHandler<HTMLTextAreaElement> = useCallback(
|
||||||
React.useCallback(
|
(e) => {
|
||||||
(e) => {
|
onChange?.(e.target.value);
|
||||||
onChange?.(e.target.value);
|
},
|
||||||
},
|
[onChange],
|
||||||
[onChange],
|
);
|
||||||
);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
setVal(value);
|
setVal(value);
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|||||||
import { useDebounce } from 'ahooks';
|
import { useDebounce } from 'ahooks';
|
||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useParams } from 'umi';
|
import { useParams, useSearchParams } from 'umi';
|
||||||
import {
|
import {
|
||||||
useGetPaginationWithRouter,
|
useGetPaginationWithRouter,
|
||||||
useHandleSearchChange,
|
useHandleSearchChange,
|
||||||
@ -230,6 +230,8 @@ export const useUpdateKnowledge = (shouldFetchList = false) => {
|
|||||||
|
|
||||||
export const useFetchKnowledgeBaseConfiguration = (refreshCount?: number) => {
|
export const useFetchKnowledgeBaseConfiguration = (refreshCount?: number) => {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const knowledgeBaseId = searchParams.get('id') || id;
|
||||||
|
|
||||||
let queryKey: (KnowledgeApiAction | number)[] = [
|
let queryKey: (KnowledgeApiAction | number)[] = [
|
||||||
KnowledgeApiAction.FetchKnowledgeDetail,
|
KnowledgeApiAction.FetchKnowledgeDetail,
|
||||||
@ -244,7 +246,7 @@ export const useFetchKnowledgeBaseConfiguration = (refreshCount?: number) => {
|
|||||||
gcTime: 0,
|
gcTime: 0,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await kbService.get_kb_detail({
|
const { data } = await kbService.get_kb_detail({
|
||||||
kb_id: id,
|
kb_id: knowledgeBaseId,
|
||||||
});
|
});
|
||||||
return data?.data ?? {};
|
return data?.data ?? {};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1303,6 +1303,10 @@ This delimiter is used to split the input text into several text pieces echo of
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
modal: {
|
||||||
|
okText: 'Confirm',
|
||||||
|
cancelText: 'Cancel',
|
||||||
|
},
|
||||||
mcp: {
|
mcp: {
|
||||||
export: 'Export',
|
export: 'Export',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
|
|||||||
@ -1185,4 +1185,8 @@ export default {
|
|||||||
chat: '聊天',
|
chat: '聊天',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
modal: {
|
||||||
|
okText: '確認',
|
||||||
|
cancelText: '取消',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1264,4 +1264,8 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
modal: {
|
||||||
|
okText: '确认',
|
||||||
|
cancelText: '取消',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,10 +1,28 @@
|
|||||||
import EditTag from '@/components/edit-tag';
|
import EditTag from '@/components/edit-tag';
|
||||||
|
import Divider from '@/components/ui/divider';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from '@/components/ui/form';
|
||||||
|
import {
|
||||||
|
HoverCard,
|
||||||
|
HoverCardContent,
|
||||||
|
HoverCardTrigger,
|
||||||
|
} from '@/components/ui/hover-card';
|
||||||
|
import { Modal } from '@/components/ui/modal';
|
||||||
|
import Space from '@/components/ui/space';
|
||||||
|
import { Switch } from '@/components/ui/switch';
|
||||||
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { useFetchChunk } from '@/hooks/chunk-hooks';
|
import { useFetchChunk } from '@/hooks/chunk-hooks';
|
||||||
import { IModalProps } from '@/interfaces/common';
|
import { IModalProps } from '@/interfaces/common';
|
||||||
import { IChunk } from '@/interfaces/database/knowledge';
|
import { IChunk } from '@/interfaces/database/knowledge';
|
||||||
import { DeleteOutlined } from '@ant-design/icons';
|
import { Trash2 } from 'lucide-react';
|
||||||
import { Divider, Form, Input, Modal, Space, Switch } from 'antd';
|
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { FieldValues, FormProvider, useForm } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useDeleteChunkByIds } from '../../hooks';
|
import { useDeleteChunkByIds } from '../../hooks';
|
||||||
import {
|
import {
|
||||||
@ -32,28 +50,35 @@ const ChunkCreatingModal: React.FC<IModalProps<any> & kFProps> = ({
|
|||||||
loading,
|
loading,
|
||||||
parserId,
|
parserId,
|
||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm();
|
// const [form] = Form.useForm();
|
||||||
|
// const form = useFormContext();
|
||||||
|
const form = useForm<FieldValues>({
|
||||||
|
defaultValues: {
|
||||||
|
content_with_weight: '',
|
||||||
|
tag_kwd: [],
|
||||||
|
question_kwd: [],
|
||||||
|
important_kwd: [],
|
||||||
|
tag_feas: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
const [checked, setChecked] = useState(false);
|
const [checked, setChecked] = useState(false);
|
||||||
const { removeChunk } = useDeleteChunkByIds();
|
const { removeChunk } = useDeleteChunkByIds();
|
||||||
const { data } = useFetchChunk(chunkId);
|
const { data } = useFetchChunk(chunkId);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const isTagParser = parserId === 'tag';
|
const isTagParser = parserId === 'tag';
|
||||||
|
const onSubmit = useCallback(
|
||||||
const handleOk = useCallback(async () => {
|
(values: FieldValues) => {
|
||||||
try {
|
|
||||||
const values = await form.validateFields();
|
|
||||||
console.log('🚀 ~ handleOk ~ values:', values);
|
|
||||||
|
|
||||||
onOk?.({
|
onOk?.({
|
||||||
...values,
|
...values,
|
||||||
tag_feas: transformTagFeaturesArrayToObject(values.tag_feas),
|
tag_feas: transformTagFeaturesArrayToObject(values.tag_feas),
|
||||||
available_int: checked ? 1 : 0, // available_int
|
available_int: checked ? 1 : 0,
|
||||||
});
|
});
|
||||||
} catch (errorInfo) {
|
},
|
||||||
console.log('Failed:', errorInfo);
|
[checked, onOk],
|
||||||
}
|
);
|
||||||
}, [checked, form, onOk]);
|
|
||||||
|
const handleOk = form.handleSubmit(onSubmit);
|
||||||
|
|
||||||
const handleRemove = useCallback(() => {
|
const handleRemove = useCallback(() => {
|
||||||
if (chunkId) {
|
if (chunkId) {
|
||||||
@ -68,8 +93,8 @@ const ChunkCreatingModal: React.FC<IModalProps<any> & kFProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data?.code === 0) {
|
if (data?.code === 0) {
|
||||||
const { available_int, tag_feas } = data.data;
|
const { available_int, tag_feas } = data.data;
|
||||||
form.setFieldsValue({
|
form.reset({
|
||||||
...(data.data || {}),
|
...data.data,
|
||||||
tag_feas: transformTagFeaturesObjectToArray(tag_feas),
|
tag_feas: transformTagFeaturesObjectToArray(tag_feas),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -83,54 +108,101 @@ const ChunkCreatingModal: React.FC<IModalProps<any> & kFProps> = ({
|
|||||||
open={true}
|
open={true}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
onCancel={hideModal}
|
onCancel={hideModal}
|
||||||
okButtonProps={{ loading }}
|
confirmLoading={loading}
|
||||||
destroyOnClose
|
destroyOnClose
|
||||||
>
|
>
|
||||||
<Form form={form} autoComplete="off" layout={'vertical'}>
|
<Form {...form}>
|
||||||
<Form.Item<FieldType>
|
<div className="flex flex-col gap-4">
|
||||||
label={t('chunk.chunk')}
|
<FormField
|
||||||
name="content_with_weight"
|
control={form.control}
|
||||||
rules={[{ required: true, message: t('chunk.chunkMessage') }]}
|
name="content_with_weight"
|
||||||
>
|
render={({ field }) => (
|
||||||
<Input.TextArea autoSize={{ minRows: 4, maxRows: 10 }} />
|
<FormItem>
|
||||||
</Form.Item>
|
<FormLabel>{t('chunk.chunk')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea {...field} autoSize={{ minRows: 4, maxRows: 10 }} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="important_kwd"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('chunk.keyword')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<EditTag {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<Form.Item<FieldType> label={t('chunk.keyword')} name="important_kwd">
|
<FormField
|
||||||
<EditTag></EditTag>
|
control={form.control}
|
||||||
</Form.Item>
|
name="question_kwd"
|
||||||
<Form.Item<FieldType>
|
render={({ field }) => (
|
||||||
label={t('chunk.question')}
|
<FormItem>
|
||||||
name="question_kwd"
|
<FormLabel className="flex justify-start items-start">
|
||||||
tooltip={t('chunk.questionTip')}
|
<div className="flex items-center gap-0">
|
||||||
>
|
<span>{t('chunk.question')}</span>
|
||||||
<EditTag></EditTag>
|
<HoverCard>
|
||||||
</Form.Item>
|
<HoverCardTrigger asChild>
|
||||||
{isTagParser && (
|
<span className="text-xs mt-[-3px] text-center scale-[90%] font-thin text-primary cursor-pointer rounded-full w-[16px] h-[16px] border-muted-foreground/50 border">
|
||||||
<Form.Item<FieldType>
|
?
|
||||||
label={t('knowledgeConfiguration.tagName')}
|
</span>
|
||||||
name="tag_kwd"
|
</HoverCardTrigger>
|
||||||
>
|
<HoverCardContent className="w-80" side="top">
|
||||||
<EditTag></EditTag>
|
{t('chunk.questionTip')}
|
||||||
</Form.Item>
|
</HoverCardContent>
|
||||||
)}
|
</HoverCard>
|
||||||
|
</div>
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<EditTag {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
{!isTagParser && <TagFeatureItem></TagFeatureItem>}
|
{isTagParser && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="tag_kwd"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('knowledgeConfiguration.tagName')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<EditTag {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isTagParser && (
|
||||||
|
<FormProvider {...form}>
|
||||||
|
<TagFeatureItem />
|
||||||
|
</FormProvider>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
{chunkId && (
|
{chunkId && (
|
||||||
<section>
|
<section>
|
||||||
<Divider></Divider>
|
<Divider />
|
||||||
<Space size={'large'}>
|
<Space size={'large'}>
|
||||||
<Switch
|
<div className="flex items-center gap-2">
|
||||||
checkedChildren={t('chunk.enabled')}
|
{t('chunk.enabled')}
|
||||||
unCheckedChildren={t('chunk.disabled')}
|
<Switch checked={checked} onCheckedChange={handleCheck} />
|
||||||
onChange={handleCheck}
|
</div>
|
||||||
checked={checked}
|
<div className="flex items-center gap-1" onClick={handleRemove}>
|
||||||
/>
|
<Trash2 size={16} /> {t('common.delete')}
|
||||||
|
</div>
|
||||||
<span onClick={handleRemove}>
|
|
||||||
<DeleteOutlined /> {t('common.delete')}
|
|
||||||
</span>
|
|
||||||
</Space>
|
</Space>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,22 +1,28 @@
|
|||||||
|
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
useFetchKnowledgeBaseConfiguration,
|
FormControl,
|
||||||
useFetchTagListByKnowledgeIds,
|
FormField,
|
||||||
} from '@/hooks/knowledge-hooks';
|
FormItem,
|
||||||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
FormLabel,
|
||||||
import { Button, Form, InputNumber, Select } from 'antd';
|
FormMessage,
|
||||||
|
} from '@/components/ui/form';
|
||||||
|
import { NumberInput } from '@/components/ui/input';
|
||||||
|
import { useFetchTagListByKnowledgeIds } from '@/hooks/knowledge-hooks';
|
||||||
|
import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-request';
|
||||||
|
import { CircleMinus, Plus } from 'lucide-react';
|
||||||
import { useCallback, useEffect, useMemo } from 'react';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
|
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FormListItem } from '../../utils';
|
import { FormListItem } from '../../utils';
|
||||||
|
|
||||||
const FieldKey = 'tag_feas';
|
const FieldKey = 'tag_feas';
|
||||||
|
|
||||||
export const TagFeatureItem = () => {
|
export const TagFeatureItem = () => {
|
||||||
const form = Form.useFormInstance();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { data: knowledgeConfiguration } = useFetchKnowledgeBaseConfiguration();
|
|
||||||
|
|
||||||
const { setKnowledgeIds, list } = useFetchTagListByKnowledgeIds();
|
const { setKnowledgeIds, list } = useFetchTagListByKnowledgeIds();
|
||||||
|
const { data: knowledgeConfiguration } = useFetchKnowledgeBaseConfiguration();
|
||||||
|
const form = useFormContext();
|
||||||
const tagKnowledgeIds = useMemo(() => {
|
const tagKnowledgeIds = useMemo(() => {
|
||||||
return knowledgeConfiguration?.parser_config?.tag_kb_ids ?? [];
|
return knowledgeConfiguration?.parser_config?.tag_kb_ids ?? [];
|
||||||
}, [knowledgeConfiguration?.parser_config?.tag_kb_ids]);
|
}, [knowledgeConfiguration?.parser_config?.tag_kb_ids]);
|
||||||
@ -30,15 +36,17 @@ export const TagFeatureItem = () => {
|
|||||||
|
|
||||||
const filterOptions = useCallback(
|
const filterOptions = useCallback(
|
||||||
(index: number) => {
|
(index: number) => {
|
||||||
const tags: FormListItem[] = form.getFieldValue(FieldKey) ?? [];
|
const tags: FormListItem[] = form.getValues(FieldKey) ?? [];
|
||||||
|
|
||||||
// Exclude it's own current data
|
// Exclude it's own current data
|
||||||
const list = tags
|
const list = tags
|
||||||
.filter((x, idx) => x && index !== idx)
|
.filter((x, idx) => x && index !== idx)
|
||||||
.map((x) => x.tag);
|
.map((x) => x.tag);
|
||||||
|
|
||||||
// Exclude the selected data from other options from one's own options.
|
// Exclude the selected data from other options from one's own options.
|
||||||
return options.filter((x) => !list.some((y) => x.value === y));
|
const resultList = options.filter(
|
||||||
|
(x) => !list.some((y) => x.value === y),
|
||||||
|
);
|
||||||
|
return resultList;
|
||||||
},
|
},
|
||||||
[form, options],
|
[form, options],
|
||||||
);
|
);
|
||||||
@ -47,61 +55,82 @@ export const TagFeatureItem = () => {
|
|||||||
setKnowledgeIds(tagKnowledgeIds);
|
setKnowledgeIds(tagKnowledgeIds);
|
||||||
}, [setKnowledgeIds, tagKnowledgeIds]);
|
}, [setKnowledgeIds, tagKnowledgeIds]);
|
||||||
|
|
||||||
|
const { fields, append, remove } = useFieldArray({
|
||||||
|
control: form.control,
|
||||||
|
name: FieldKey,
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<Form.Item label={t('knowledgeConfiguration.tags')}>
|
<FormField
|
||||||
<Form.List name={FieldKey} initialValue={[]}>
|
control={form.control}
|
||||||
{(fields, { add, remove }) => (
|
name={FieldKey as any}
|
||||||
<>
|
render={() => (
|
||||||
{fields.map(({ key, name, ...restField }) => (
|
<FormItem>
|
||||||
<div key={key} className="flex gap-3 items-center">
|
<FormLabel>{t('knowledgeConfiguration.tags')}</FormLabel>
|
||||||
<div className="flex flex-1 gap-8">
|
<div>
|
||||||
<Form.Item
|
{fields.map((item, name) => {
|
||||||
{...restField}
|
return (
|
||||||
name={[name, 'tag']}
|
<div key={item.id} className="flex gap-3 items-center mb-4">
|
||||||
rules={[
|
<div className="flex flex-1 gap-8">
|
||||||
{ required: true, message: t('common.pleaseSelect') },
|
<FormField
|
||||||
]}
|
control={form.control}
|
||||||
className="w-2/3"
|
name={`${FieldKey}.${name}.tag` as any}
|
||||||
>
|
render={({ field }) => (
|
||||||
<Select
|
<FormItem className="w-2/3">
|
||||||
showSearch
|
<FormControl className="w-full">
|
||||||
placeholder={t('knowledgeConfiguration.tagName')}
|
<div>
|
||||||
options={filterOptions(name)}
|
<SelectWithSearch
|
||||||
|
options={filterOptions(name)}
|
||||||
|
placeholder={t(
|
||||||
|
'knowledgeConfiguration.tagName',
|
||||||
|
)}
|
||||||
|
value={field.value}
|
||||||
|
onChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
<FormField
|
||||||
<Form.Item
|
control={form.control}
|
||||||
{...restField}
|
name={`${FieldKey}.${name}.frequency`}
|
||||||
name={[name, 'frequency']}
|
render={({ field }) => (
|
||||||
rules={[
|
<FormItem>
|
||||||
{ required: true, message: t('common.pleaseInput') },
|
<FormControl>
|
||||||
]}
|
<NumberInput
|
||||||
>
|
value={field.value}
|
||||||
<InputNumber
|
onChange={field.onChange}
|
||||||
placeholder={t('knowledgeConfiguration.frequency')}
|
placeholder={t(
|
||||||
max={10}
|
'knowledgeConfiguration.frequency',
|
||||||
min={0}
|
)}
|
||||||
|
max={10}
|
||||||
|
min={0}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</div>
|
||||||
|
<CircleMinus
|
||||||
|
onClick={() => remove(name)}
|
||||||
|
className="text-red-500"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<MinusCircleOutlined
|
);
|
||||||
onClick={() => remove(name)}
|
})}
|
||||||
className="mb-6"
|
<Button
|
||||||
/>
|
variant="dashed"
|
||||||
</div>
|
className="w-full flex items-center justify-center gap-2"
|
||||||
))}
|
onClick={() => append({ tag: '', frequency: 0 })}
|
||||||
<Form.Item>
|
>
|
||||||
<Button
|
<Plus size={16} />
|
||||||
type="dashed"
|
{t('knowledgeConfiguration.addTag')}
|
||||||
onClick={() => add()}
|
</Button>
|
||||||
block
|
</div>
|
||||||
icon={<PlusOutlined />}
|
</FormItem>
|
||||||
>
|
)}
|
||||||
{t('knowledgeConfiguration.addTag')}
|
/>
|
||||||
</Button>
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Form.List>
|
|
||||||
</Form.Item>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -61,12 +61,12 @@ export default ({
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className="flex pr-[25px]">
|
<div className="flex pr-[25px]">
|
||||||
<div className="flex items-center gap-4 bg-card text-muted-foreground w-fit h-[35px] rounded-md px-4 py-2 text-base">
|
<div className="flex items-center gap-4 bg-background-card text-muted w-fit h-[35px] rounded-md px-4 py-2 text-base">
|
||||||
{textSelectOptions.map((option) => (
|
{textSelectOptions.map((option) => (
|
||||||
<div
|
<div
|
||||||
key={option.value}
|
key={option.value}
|
||||||
className={cn('flex items-center cursor-pointer', {
|
className={cn('flex items-center cursor-pointer', {
|
||||||
'text-white': option.value === textSelectValue,
|
'text-primary': option.value === textSelectValue,
|
||||||
})}
|
})}
|
||||||
onClick={() => changeTextSelectValue(option.value)}
|
onClick={() => changeTextSelectValue(option.value)}
|
||||||
>
|
>
|
||||||
@ -76,7 +76,7 @@ export default ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="ml-auto"></div>
|
<div className="ml-auto"></div>
|
||||||
<Input
|
<Input
|
||||||
className="bg-card text-muted-foreground"
|
className="bg-background-card text-muted-foreground"
|
||||||
style={{ width: 200 }}
|
style={{ width: 200 }}
|
||||||
placeholder={t('search')}
|
placeholder={t('search')}
|
||||||
icon={<SearchOutlined />}
|
icon={<SearchOutlined />}
|
||||||
@ -86,7 +86,7 @@ export default ({
|
|||||||
<div className="w-[20px]"></div>
|
<div className="w-[20px]"></div>
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button className="bg-card text-muted-foreground hover:bg-card">
|
<Button className="bg-background-card text-muted-foreground hover:bg-card">
|
||||||
<ListFilter />
|
<ListFilter />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
@ -95,7 +95,10 @@ export default ({
|
|||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
<div className="w-[20px]"></div>
|
<div className="w-[20px]"></div>
|
||||||
<Button onClick={() => createChunk()}>
|
<Button
|
||||||
|
onClick={() => createChunk()}
|
||||||
|
className="bg-background-card text-primary"
|
||||||
|
>
|
||||||
<Plus size={44} />
|
<Plus size={44} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { Skeleton } from 'antd';
|
|
||||||
import { memo, useEffect, useRef } from 'react';
|
import { memo, useEffect, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
AreaHighlight,
|
AreaHighlight,
|
||||||
@ -11,6 +10,7 @@ import {
|
|||||||
import { useGetDocumentUrl } from './hooks';
|
import { useGetDocumentUrl } from './hooks';
|
||||||
|
|
||||||
import { useCatchDocumentError } from '@/components/pdf-previewer/hooks';
|
import { useCatchDocumentError } from '@/components/pdf-previewer/hooks';
|
||||||
|
import { Spin } from '@/components/ui/spin';
|
||||||
import FileError from '@/pages/document-viewer/file-error';
|
import FileError from '@/pages/document-viewer/file-error';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
@ -50,7 +50,11 @@ const PdfPreview = ({ highlights: state, setWidthAndHeight }: IProps) => {
|
|||||||
>
|
>
|
||||||
<PdfLoader
|
<PdfLoader
|
||||||
url={url}
|
url={url}
|
||||||
beforeLoad={<Skeleton active />}
|
beforeLoad={
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<Spin />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
workerSrc="/pdfjs-dist/pdf.worker.min.js"
|
workerSrc="/pdfjs-dist/pdf.worker.min.js"
|
||||||
errorMessage={<FileError>{error}</FileError>}
|
errorMessage={<FileError>{error}</FileError>}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import message from '@/components/ui/message';
|
import message from '@/components/ui/message';
|
||||||
|
import { Spin } from '@/components/ui/spin';
|
||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
import { Spin } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useGetDocumentUrl } from './hooks';
|
import { useGetDocumentUrl } from './hooks';
|
||||||
|
|||||||
@ -41,8 +41,7 @@
|
|||||||
|
|
||||||
.chunkContainer {
|
.chunkContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
// height: calc(100vh - 332px);
|
height: calc(100vh - 332px);
|
||||||
height: calc(100vh - 300px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chunkOtherContainer {
|
.chunkOtherContainer {
|
||||||
|
|||||||
@ -61,7 +61,7 @@ export function ParsingStatusCell({
|
|||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant={'ghost'} size={'sm'}>
|
<Button variant={'ghost'} size={'sm'}>
|
||||||
{parser_id}
|
{parser_id === 'naive' ? 'general' : parser_id}
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
|
|||||||
@ -221,79 +221,77 @@ export function TagTable() {
|
|||||||
</ConfirmDeleteDialog>
|
</ConfirmDeleteDialog>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-md border">
|
<Table rootClassName="rounded-none border max-h-80 overflow-y-auto">
|
||||||
<Table>
|
<TableHeader className="bg-[#39393b]">
|
||||||
<TableHeader>
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
<TableRow key={headerGroup.id}>
|
||||||
<TableRow key={headerGroup.id}>
|
{headerGroup.headers.map((header) => {
|
||||||
{headerGroup.headers.map((header) => {
|
return (
|
||||||
return (
|
<TableHead key={header.id}>
|
||||||
<TableHead key={header.id}>
|
{header.isPlaceholder
|
||||||
{header.isPlaceholder
|
? null
|
||||||
? null
|
: flexRender(
|
||||||
: flexRender(
|
header.column.columnDef.header,
|
||||||
header.column.columnDef.header,
|
header.getContext(),
|
||||||
header.getContext(),
|
)}
|
||||||
)}
|
</TableHead>
|
||||||
</TableHead>
|
);
|
||||||
);
|
})}
|
||||||
})}
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{table.getRowModel().rows?.length ? (
|
||||||
|
table.getRowModel().rows.map((row) => (
|
||||||
|
<TableRow
|
||||||
|
key={row.id}
|
||||||
|
data-state={row.getIsSelected() && 'selected'}
|
||||||
|
>
|
||||||
|
{row.getVisibleCells().map((cell) => (
|
||||||
|
<TableCell key={cell.id}>
|
||||||
|
{flexRender(
|
||||||
|
cell.column.columnDef.cell,
|
||||||
|
cell.getContext(),
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))
|
||||||
</TableHeader>
|
) : (
|
||||||
<TableBody>
|
<TableRow>
|
||||||
{table.getRowModel().rows?.length ? (
|
<TableCell
|
||||||
table.getRowModel().rows.map((row) => (
|
colSpan={columns.length}
|
||||||
<TableRow
|
className="h-24 text-center"
|
||||||
key={row.id}
|
>
|
||||||
data-state={row.getIsSelected() && 'selected'}
|
No results.
|
||||||
>
|
</TableCell>
|
||||||
{row.getVisibleCells().map((cell) => (
|
</TableRow>
|
||||||
<TableCell key={cell.id}>
|
)}
|
||||||
{flexRender(
|
</TableBody>
|
||||||
cell.column.columnDef.cell,
|
</Table>
|
||||||
cell.getContext(),
|
</div>
|
||||||
)}
|
<div className="flex items-center justify-end space-x-2 py-4">
|
||||||
</TableCell>
|
<div className="flex-1 text-sm text-muted-foreground">
|
||||||
))}
|
{selectedRowLength} of {table.getFilteredRowModel().rows.length}{' '}
|
||||||
</TableRow>
|
row(s) selected.
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell
|
|
||||||
colSpan={columns.length}
|
|
||||||
className="h-24 text-center"
|
|
||||||
>
|
|
||||||
No results.
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-end space-x-2 py-4">
|
<div className="space-x-2">
|
||||||
<div className="flex-1 text-sm text-muted-foreground">
|
<Button
|
||||||
{selectedRowLength} of {table.getFilteredRowModel().rows.length}{' '}
|
variant="outline"
|
||||||
row(s) selected.
|
size="sm"
|
||||||
</div>
|
onClick={() => table.previousPage()}
|
||||||
<div className="space-x-2">
|
disabled={!table.getCanPreviousPage()}
|
||||||
<Button
|
>
|
||||||
variant="outline"
|
{t('common.previousPage')}
|
||||||
size="sm"
|
</Button>
|
||||||
onClick={() => table.previousPage()}
|
<Button
|
||||||
disabled={!table.getCanPreviousPage()}
|
variant="outline"
|
||||||
>
|
size="sm"
|
||||||
{t('common.previousPage')}
|
onClick={() => table.nextPage()}
|
||||||
</Button>
|
disabled={!table.getCanNextPage()}
|
||||||
<Button
|
>
|
||||||
variant="outline"
|
{t('common.nextPage')}
|
||||||
size="sm"
|
</Button>
|
||||||
onClick={() => table.nextPage()}
|
|
||||||
disabled={!table.getCanNextPage()}
|
|
||||||
>
|
|
||||||
{t('common.nextPage')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{tagRenameVisible && (
|
{tagRenameVisible && (
|
||||||
|
|||||||
@ -2,12 +2,12 @@ import { Images } from '@/constants/common';
|
|||||||
import { api_host } from '@/utils/api';
|
import { api_host } from '@/utils/api';
|
||||||
import { Flex } from 'antd';
|
import { Flex } from 'antd';
|
||||||
import { useParams, useSearchParams } from 'umi';
|
import { useParams, useSearchParams } from 'umi';
|
||||||
import Md from './md';
|
|
||||||
import Text from './text';
|
|
||||||
import Docx from './docx';
|
import Docx from './docx';
|
||||||
import Excel from './excel';
|
import Excel from './excel';
|
||||||
import Image from './image';
|
import Image from './image';
|
||||||
|
import Md from './md';
|
||||||
import Pdf from './pdf';
|
import Pdf from './pdf';
|
||||||
|
import Text from './text';
|
||||||
|
|
||||||
import { previewHtmlFile } from '@/utils/file-util';
|
import { previewHtmlFile } from '@/utils/file-util';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|||||||
@ -19,13 +19,13 @@ const Md: React.FC<MdProps> = ({ filePath }) => {
|
|||||||
return res.text();
|
return res.text();
|
||||||
})
|
})
|
||||||
.then((text) => setContent(text))
|
.then((text) => setContent(text))
|
||||||
.catch((err) => setError(err.message))
|
.catch((err) => setError(err.message));
|
||||||
}, [filePath]);
|
}, [filePath]);
|
||||||
|
|
||||||
if (error) return (<FileError>{error}</FileError>);
|
if (error) return <FileError>{error}</FileError>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: 24, height: "100vh", overflow: "scroll" }}>
|
<div style={{ padding: 24, height: '100vh', overflow: 'scroll' }}>
|
||||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
|
<ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -17,14 +17,14 @@ const Md: React.FC<TxtProps> = ({ filePath }) => {
|
|||||||
return res.text();
|
return res.text();
|
||||||
})
|
})
|
||||||
.then((text) => setContent(text))
|
.then((text) => setContent(text))
|
||||||
.catch((err) => setError(err.message))
|
.catch((err) => setError(err.message));
|
||||||
}, [filePath]);
|
}, [filePath]);
|
||||||
|
|
||||||
if (error) return (<FileError>{error}</FileError>);
|
if (error) return <FileError>{error}</FileError>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: 24, height: "100vh", overflow: "scroll" }}>
|
<div style={{ padding: 24, height: '100vh', overflow: 'scroll' }}>
|
||||||
{content}
|
{content}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user