Przeglądaj źródła

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)
tags/v0.20.0
chanx 3 miesięcy temu
rodzic
commit
ed8d7291ff
No account linked to committer's email address
27 zmienionych plików z 917 dodań i 461 usunięć
  1. 0
    22
      web/src/components/edit-tag/index.less
  2. 56
    36
      web/src/components/edit-tag/index.tsx
  3. 0
    15
      web/src/components/image/index.less
  4. 9
    9
      web/src/components/image/index.tsx
  5. 4
    2
      web/src/components/originui/input.tsx
  6. 105
    131
      web/src/components/originui/select-with-search.tsx
  7. 5
    1
      web/src/components/ui/button.tsx
  8. 64
    0
      web/src/components/ui/divider.tsx
  9. 1
    1
      web/src/components/ui/hover-card.tsx
  10. 199
    0
      web/src/components/ui/modal.tsx
  11. 57
    0
      web/src/components/ui/space.tsx
  12. 86
    32
      web/src/components/ui/textarea.tsx
  13. 4
    2
      web/src/hooks/use-knowledge-request.ts
  14. 4
    0
      web/src/locales/en.ts
  15. 4
    0
      web/src/locales/zh-traditional.ts
  16. 4
    0
      web/src/locales/zh.ts
  17. 127
    55
      web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/index.tsx
  18. 93
    64
      web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/tag-feature-item.tsx
  19. 8
    5
      web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/chunk-result-bar/index.tsx
  20. 6
    2
      web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/document-preview/pdf-preview.tsx
  21. 1
    1
      web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/document-preview/txt-preview.tsx
  22. 1
    2
      web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/index.less
  23. 1
    1
      web/src/pages/dataset/dataset/parsing-status-cell.tsx
  24. 69
    71
      web/src/pages/dataset/setting/tag-table/index.tsx
  25. 2
    2
      web/src/pages/document-viewer/index.tsx
  26. 3
    3
      web/src/pages/document-viewer/md/index.tsx
  27. 4
    4
      web/src/pages/document-viewer/text/index.tsx

+ 0
- 22
web/src/components/edit-tag/index.less Wyświetl plik

@@ -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;
}
}

+ 56
- 36
web/src/components/edit-tag/index.tsx Wyświetl plik

@@ -1,21 +1,24 @@
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 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 {
value?: string[];
onChange?: (tags: string[]) => void;
}

const EditTag = ({ value = [], onChange }: EditTagsProps) => {
const { token } = theme.useToken();
const [inputVisible, setInputVisible] = useState(false);
const [inputValue, setInputValue] = useState('');
const inputRef = useRef<InputRef>(null);
const inputRef = useRef<HTMLInputElement>(null);

useEffect(() => {
if (inputVisible) {
@@ -50,34 +53,66 @@ const EditTag = ({ value = [], onChange }: EditTagsProps) => {

const forMap = (tag: string) => {
return (
<Tooltip title={tag}>
<Tag
key={tag}
className={styles.tag}
closable
onClose={(e) => {
e.preventDefault();
handleClose(tag);
}}
>
{tag}
</Tag>
</Tooltip>
<HoverCard>
<HoverCardContent side="top">{tag}</HoverCardContent>
<HoverCardTrigger>
<div
key={tag}
className="w-fit flex items-center justify-center gap-2 border-dashed border px-1 rounded-sm bg-background-card"
>
<div className="flex gap-2 items-center">
<div className="max-w-80 overflow-hidden text-ellipsis">
{tag}
</div>
<X
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 tagPlusStyle: React.CSSProperties = {
background: token.colorBgContainer,
borderStyle: 'dashed',
};

return (
<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 && (
<TweenOneGroup
className={styles.tweenGroup}
className="flex gap-2 flex-wrap mt-2"
enter={{
scale: 0.8,
opacity: 0,
@@ -95,21 +130,6 @@ const EditTag = ({ value = [], onChange }: EditTagsProps) => {
{tagChild}
</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>
);
};

+ 0
- 15
web/src/components/image/index.less Wyświetl plik

@@ -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;
}

+ 9
- 9
web/src/components/image/index.tsx Wyświetl plik

@@ -1,8 +1,6 @@
import { api_host } from '@/utils/api';
import { Popover } from 'antd';
import classNames from 'classnames';

import styles from './index.less';
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';

interface IImage {
id: string;
@@ -16,7 +14,7 @@ const Image = ({ id, className, ...props }: IImage) => {
{...props}
src={`${api_host}/document/image/${id}`}
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 }) => {
return (
<Popover
placement="left"
content={<Image id={id} className={styles.imagePreview}></Image>}
>
<Image id={id} className={styles.image}></Image>
<Popover>
<PopoverTrigger>
<Image id={id} className="max-h-[100px] inline-block"></Image>
</PopoverTrigger>
<PopoverContent>
<Image id={id} className="max-w-[100px] object-contain"></Image>
</PopoverContent>
</Popover>
);
};

+ 4
- 2
web/src/components/originui/input.tsx Wyświetl plik

@@ -6,11 +6,12 @@ type InputProps = React.ComponentProps<'input'> & {
iconPosition?: 'left' | 'right';
};

function Input({
const Input = function ({
className,
type,
icon,
iconPosition = 'left',
ref,
...props
}: InputProps) {
return (
@@ -27,6 +28,7 @@ function Input({
</div>
)}
<input
ref={ref}
type={type}
data-slot="input"
className={cn(
@@ -45,6 +47,6 @@ function Input({
/>
</div>
);
}
};

export { Input };

+ 105
- 131
web/src/components/originui/select-with-search.tsx Wyświetl plik

@@ -7,6 +7,7 @@ import {
useCallback,
useEffect,
useId,
useMemo,
useState,
} from 'react';

@@ -27,50 +28,9 @@ import {
import { cn } from '@/lib/utils';
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 = {
label: string;
value?: string;
options: RAGFlowSelectOptionType[];
};

@@ -84,99 +44,113 @@ export type SelectWithSearchFlagProps = {
export const SelectWithSearch = forwardRef<
React.ElementRef<typeof Button>,
SelectWithSearchFlagProps
>(
(
{ value: val = '', onChange, options = countries, triggerClassName },
ref,
) => {
const id = useId();
const [open, setOpen] = useState<boolean>(false);
const [value, setValue] = useState<string>('');

const handleSelect = useCallback(
(val: string) => {
setValue(val);
setOpen(false);
onChange?.(val);
},
[onChange],
);
>(({ value: val = '', onChange, options = [], triggerClassName }, ref) => {
const id = useId();
const [open, setOpen] = useState<boolean>(false);
const [value, setValue] = useState<string>('');

useEffect(() => {
const handleSelect = useCallback(
(val: string) => {
setValue(val);
}, [val]);
setOpen(false);
onChange?.(val);
},
[onChange],
);

return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
id={id}
variant="outline"
role="combobox"
aria-expanded={open}
ref={ref}
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]',
triggerClassName,
)}
>
{value ? (
<span className="flex min-w-0 options-center gap-2">
<span className="text-lg leading-none truncate">
{
options
.map((group) =>
group.options.find((item) => item.value === value),
)
.filter(Boolean)[0]?.label
}
</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"
useEffect(() => {
setValue(val);
}, [val]);
const selectLabel = useMemo(() => {
const optionTemp = options[0];
if (optionTemp?.options) {
return optionTemp.options.find((opt) => opt.value === value)?.label || '';
} else {
return options.find((opt) => opt.value === value)?.label || '';
}
}, [options, value]);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
id={id}
variant="outline"
role="combobox"
aria-expanded={open}
ref={ref}
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]',
triggerClassName,
)}
>
<Command>
<CommandInput placeholder="Search ..." />
<CommandList>
<CommandEmpty>No data found.</CommandEmpty>
{options.map((group) => (
<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 ? (
<span className="flex min-w-0 options-center gap-2">
<span className="text-lg leading-none truncate">
{selectLabel}
</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>
<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 && (
<CheckIcon size={16} className="ml-auto" />
)}
</CommandItem>
))}
</CommandGroup>
</Fragment>
);
} else {
return (
<CommandItem
key={group.value}
value={group.value}
onSelect={handleSelect}
>
<span className="text-lg leading-none">{group.label}</span>

{value === option.value && (
<CheckIcon size={16} className="ml-auto" />
)}
</CommandItem>
))}
</CommandGroup>
</Fragment>
))}
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
},
);
{value === group.value && (
<CheckIcon size={16} className="ml-auto" />
)}
</CommandItem>
);
}
})}
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
});

SelectWithSearch.displayName = 'SelectWithSearch';

+ 5
- 1
web/src/components/ui/button.tsx Wyświetl plik

@@ -22,6 +22,7 @@ const buttonVariants = cva(
tertiary:
'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',
dashed: 'border border-dashed border-input hover:bg-accent',
},
size: {
default: 'h-8 px-2.5 py-1.5 ',
@@ -49,7 +50,10 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
const Comp = asChild ? Slot : 'button';
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
className={cn(
'bg-background-card',
buttonVariants({ variant, size, className }),
)}
ref={ref}
{...props}
/>

+ 64
- 0
web/src/components/ui/divider.tsx Wyświetl plik

@@ -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;

+ 1
- 1
web/src/components/ui/hover-card.tsx Wyświetl plik

@@ -18,7 +18,7 @@ const HoverCardContent = React.forwardRef<
align={align}
sideOffset={sideOffset}
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,
)}
{...props}

+ 199
- 0
web/src/components/ui/modal.tsx Wyświetl plik

@@ -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
- 0
web/src/components/ui/space.tsx Wyświetl plik

@@ -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;

+ 86
- 32
web/src/components/ui/textarea.tsx Wyświetl plik

@@ -1,51 +1,105 @@
import * as React from 'react';

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<
HTMLTextAreaElement,
React.ComponentProps<'textarea'>
>(({ className, ...props }, 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,
)}
ref={ref}
{...props}
/>
);
});
requestAnimationFrame(() => {
if (!textareaRef.current) return;

const scrollHeight = textareaRef.current.scrollHeight;
textareaRef.current.style.height = `${Math.min(scrollHeight, maxHeight)}px`;
});
}, [autoSize]);

useEffect(() => {
if (autoSize) {
adjustHeight();
}
}, [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';

export { Textarea };

type Value = string | readonly string[] | number | undefined;

export const BlurTextarea = React.forwardRef<
export const BlurTextarea = forwardRef<
HTMLTextAreaElement,
React.ComponentProps<'textarea'> & {
ComponentProps<'textarea'> & {
value: Value;
onChange(value: Value): void;
}
>(({ value, onChange, ...props }, ref) => {
const [val, setVal] = React.useState<Value>();
const [val, setVal] = useState<Value>();

const handleChange: React.ChangeEventHandler<HTMLTextAreaElement> =
React.useCallback((e) => {
const handleChange: ChangeEventHandler<HTMLTextAreaElement> = useCallback(
(e) => {
setVal(e.target.value);
}, []);

const handleBlur: React.FocusEventHandler<HTMLTextAreaElement> =
React.useCallback(
(e) => {
onChange?.(e.target.value);
},
[onChange],
);
},
[],
);

const handleBlur: FocusEventHandler<HTMLTextAreaElement> = useCallback(
(e) => {
onChange?.(e.target.value);
},
[onChange],
);

React.useEffect(() => {
useEffect(() => {
setVal(value);
}, [value]);


+ 4
- 2
web/src/hooks/use-knowledge-request.ts Wyświetl plik

@@ -11,7 +11,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useDebounce } from 'ahooks';
import { message } from 'antd';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useParams } from 'umi';
import { useParams, useSearchParams } from 'umi';
import {
useGetPaginationWithRouter,
useHandleSearchChange,
@@ -230,6 +230,8 @@ export const useUpdateKnowledge = (shouldFetchList = false) => {

export const useFetchKnowledgeBaseConfiguration = (refreshCount?: number) => {
const { id } = useParams();
const [searchParams] = useSearchParams();
const knowledgeBaseId = searchParams.get('id') || id;

let queryKey: (KnowledgeApiAction | number)[] = [
KnowledgeApiAction.FetchKnowledgeDetail,
@@ -244,7 +246,7 @@ export const useFetchKnowledgeBaseConfiguration = (refreshCount?: number) => {
gcTime: 0,
queryFn: async () => {
const { data } = await kbService.get_kb_detail({
kb_id: id,
kb_id: knowledgeBaseId,
});
return data?.data ?? {};
},

+ 4
- 0
web/src/locales/en.ts Wyświetl plik

@@ -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: {
export: 'Export',
import: 'Import',

+ 4
- 0
web/src/locales/zh-traditional.ts Wyświetl plik

@@ -1185,4 +1185,8 @@ export default {
chat: '聊天',
},
},
modal: {
okText: '確認',
cancelText: '取消',
},
};

+ 4
- 0
web/src/locales/zh.ts Wyświetl plik

@@ -1264,4 +1264,8 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
},
},
},
modal: {
okText: '确认',
cancelText: '取消',
},
};

+ 127
- 55
web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/index.tsx Wyświetl plik

@@ -1,10 +1,28 @@
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 { IModalProps } from '@/interfaces/common';
import { IChunk } from '@/interfaces/database/knowledge';
import { DeleteOutlined } from '@ant-design/icons';
import { Divider, Form, Input, Modal, Space, Switch } from 'antd';
import { Trash2 } from 'lucide-react';
import React, { useCallback, useEffect, useState } from 'react';
import { FieldValues, FormProvider, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useDeleteChunkByIds } from '../../hooks';
import {
@@ -32,28 +50,35 @@ const ChunkCreatingModal: React.FC<IModalProps<any> & kFProps> = ({
loading,
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 { removeChunk } = useDeleteChunkByIds();
const { data } = useFetchChunk(chunkId);
const { t } = useTranslation();

const isTagParser = parserId === 'tag';

const handleOk = useCallback(async () => {
try {
const values = await form.validateFields();
console.log('🚀 ~ handleOk ~ values:', values);

const onSubmit = useCallback(
(values: FieldValues) => {
onOk?.({
...values,
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, form, onOk]);
},
[checked, onOk],
);

const handleOk = form.handleSubmit(onSubmit);

const handleRemove = useCallback(() => {
if (chunkId) {
@@ -68,8 +93,8 @@ const ChunkCreatingModal: React.FC<IModalProps<any> & kFProps> = ({
useEffect(() => {
if (data?.code === 0) {
const { available_int, tag_feas } = data.data;
form.setFieldsValue({
...(data.data || {}),
form.reset({
...data.data,
tag_feas: transformTagFeaturesObjectToArray(tag_feas),
});

@@ -83,54 +108,101 @@ const ChunkCreatingModal: React.FC<IModalProps<any> & kFProps> = ({
open={true}
onOk={handleOk}
onCancel={hideModal}
okButtonProps={{ loading }}
confirmLoading={loading}
destroyOnClose
>
<Form form={form} autoComplete="off" layout={'vertical'}>
<Form.Item<FieldType>
label={t('chunk.chunk')}
name="content_with_weight"
rules={[{ required: true, message: t('chunk.chunkMessage') }]}
>
<Input.TextArea autoSize={{ minRows: 4, maxRows: 10 }} />
</Form.Item>
<Form {...form}>
<div className="flex flex-col gap-4">
<FormField
control={form.control}
name="content_with_weight"
render={({ field }) => (
<FormItem>
<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>
)}
/>

<FormField
control={form.control}
name="question_kwd"
render={({ field }) => (
<FormItem>
<FormLabel className="flex justify-start items-start">
<div className="flex items-center gap-0">
<span>{t('chunk.question')}</span>
<HoverCard>
<HoverCardTrigger asChild>
<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">
?
</span>
</HoverCardTrigger>
<HoverCardContent className="w-80" side="top">
{t('chunk.questionTip')}
</HoverCardContent>
</HoverCard>
</div>
</FormLabel>
<FormControl>
<EditTag {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<Form.Item<FieldType> label={t('chunk.keyword')} name="important_kwd">
<EditTag></EditTag>
</Form.Item>
<Form.Item<FieldType>
label={t('chunk.question')}
name="question_kwd"
tooltip={t('chunk.questionTip')}
>
<EditTag></EditTag>
</Form.Item>
{isTagParser && (
<Form.Item<FieldType>
label={t('knowledgeConfiguration.tagName')}
name="tag_kwd"
>
<EditTag></EditTag>
</Form.Item>
)}
{isTagParser && (
<FormField
control={form.control}
name="tag_kwd"
render={({ field }) => (
<FormItem>
<FormLabel>{t('knowledgeConfiguration.tagName')}</FormLabel>
<FormControl>
<EditTag {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}

{!isTagParser && <TagFeatureItem></TagFeatureItem>}
{!isTagParser && (
<FormProvider {...form}>
<TagFeatureItem />
</FormProvider>
)}
</div>
</Form>

{chunkId && (
<section>
<Divider></Divider>
<Divider />
<Space size={'large'}>
<Switch
checkedChildren={t('chunk.enabled')}
unCheckedChildren={t('chunk.disabled')}
onChange={handleCheck}
checked={checked}
/>

<span onClick={handleRemove}>
<DeleteOutlined /> {t('common.delete')}
</span>
<div className="flex items-center gap-2">
{t('chunk.enabled')}
<Switch checked={checked} onCheckedChange={handleCheck} />
</div>
<div className="flex items-center gap-1" onClick={handleRemove}>
<Trash2 size={16} /> {t('common.delete')}
</div>
</Space>
</section>
)}

+ 93
- 64
web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/tag-feature-item.tsx Wyświetl plik

@@ -1,22 +1,28 @@
import { SelectWithSearch } from '@/components/originui/select-with-search';
import { Button } from '@/components/ui/button';
import {
useFetchKnowledgeBaseConfiguration,
useFetchTagListByKnowledgeIds,
} from '@/hooks/knowledge-hooks';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, Form, InputNumber, Select } from 'antd';
FormControl,
FormField,
FormItem,
FormLabel,
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 { useFieldArray, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { FormListItem } from '../../utils';

const FieldKey = 'tag_feas';

export const TagFeatureItem = () => {
const form = Form.useFormInstance();
const { t } = useTranslation();
const { data: knowledgeConfiguration } = useFetchKnowledgeBaseConfiguration();

const { setKnowledgeIds, list } = useFetchTagListByKnowledgeIds();

const { data: knowledgeConfiguration } = useFetchKnowledgeBaseConfiguration();
const form = useFormContext();
const tagKnowledgeIds = useMemo(() => {
return knowledgeConfiguration?.parser_config?.tag_kb_ids ?? [];
}, [knowledgeConfiguration?.parser_config?.tag_kb_ids]);
@@ -30,15 +36,17 @@ export const TagFeatureItem = () => {

const filterOptions = useCallback(
(index: number) => {
const tags: FormListItem[] = form.getFieldValue(FieldKey) ?? [];
const tags: FormListItem[] = form.getValues(FieldKey) ?? [];

// Exclude it's own current data
const list = tags
.filter((x, idx) => x && index !== idx)
.map((x) => x.tag);

// 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],
);
@@ -47,61 +55,82 @@ export const TagFeatureItem = () => {
setKnowledgeIds(tagKnowledgeIds);
}, [setKnowledgeIds, tagKnowledgeIds]);

const { fields, append, remove } = useFieldArray({
control: form.control,
name: FieldKey,
});
return (
<Form.Item label={t('knowledgeConfiguration.tags')}>
<Form.List name={FieldKey} initialValue={[]}>
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, ...restField }) => (
<div key={key} className="flex gap-3 items-center">
<div className="flex flex-1 gap-8">
<Form.Item
{...restField}
name={[name, 'tag']}
rules={[
{ required: true, message: t('common.pleaseSelect') },
]}
className="w-2/3"
>
<Select
showSearch
placeholder={t('knowledgeConfiguration.tagName')}
options={filterOptions(name)}
<FormField
control={form.control}
name={FieldKey as any}
render={() => (
<FormItem>
<FormLabel>{t('knowledgeConfiguration.tags')}</FormLabel>
<div>
{fields.map((item, name) => {
return (
<div key={item.id} className="flex gap-3 items-center mb-4">
<div className="flex flex-1 gap-8">
<FormField
control={form.control}
name={`${FieldKey}.${name}.tag` as any}
render={({ field }) => (
<FormItem className="w-2/3">
<FormControl className="w-full">
<div>
<SelectWithSearch
options={filterOptions(name)}
placeholder={t(
'knowledgeConfiguration.tagName',
)}
value={field.value}
onChange={field.onChange}
/>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</Form.Item>
<Form.Item
{...restField}
name={[name, 'frequency']}
rules={[
{ required: true, message: t('common.pleaseInput') },
]}
>
<InputNumber
placeholder={t('knowledgeConfiguration.frequency')}
max={10}
min={0}
<FormField
control={form.control}
name={`${FieldKey}.${name}.frequency`}
render={({ field }) => (
<FormItem>
<FormControl>
<NumberInput
value={field.value}
onChange={field.onChange}
placeholder={t(
'knowledgeConfiguration.frequency',
)}
max={10}
min={0}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</Form.Item>
</div>
<CircleMinus
onClick={() => remove(name)}
className="text-red-500"
/>
</div>
<MinusCircleOutlined
onClick={() => remove(name)}
className="mb-6"
/>
</div>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => add()}
block
icon={<PlusOutlined />}
>
{t('knowledgeConfiguration.addTag')}
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form.Item>
);
})}
<Button
variant="dashed"
className="w-full flex items-center justify-center gap-2"
onClick={() => append({ tag: '', frequency: 0 })}
>
<Plus size={16} />
{t('knowledgeConfiguration.addTag')}
</Button>
</div>
</FormItem>
)}
/>
);
};

+ 8
- 5
web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/chunk-result-bar/index.tsx Wyświetl plik

@@ -61,12 +61,12 @@ export default ({
};
return (
<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) => (
<div
key={option.value}
className={cn('flex items-center cursor-pointer', {
'text-white': option.value === textSelectValue,
'text-primary': option.value === textSelectValue,
})}
onClick={() => changeTextSelectValue(option.value)}
>
@@ -76,7 +76,7 @@ export default ({
</div>
<div className="ml-auto"></div>
<Input
className="bg-card text-muted-foreground"
className="bg-background-card text-muted-foreground"
style={{ width: 200 }}
placeholder={t('search')}
icon={<SearchOutlined />}
@@ -86,7 +86,7 @@ export default ({
<div className="w-[20px]"></div>
<Popover>
<PopoverTrigger asChild>
<Button className="bg-card text-muted-foreground hover:bg-card">
<Button className="bg-background-card text-muted-foreground hover:bg-card">
<ListFilter />
</Button>
</PopoverTrigger>
@@ -95,7 +95,10 @@ export default ({
</PopoverContent>
</Popover>
<div className="w-[20px]"></div>
<Button onClick={() => createChunk()}>
<Button
onClick={() => createChunk()}
className="bg-background-card text-primary"
>
<Plus size={44} />
</Button>
</div>

+ 6
- 2
web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/document-preview/pdf-preview.tsx Wyświetl plik

@@ -1,4 +1,3 @@
import { Skeleton } from 'antd';
import { memo, useEffect, useRef } from 'react';
import {
AreaHighlight,
@@ -11,6 +10,7 @@ import {
import { useGetDocumentUrl } from './hooks';

import { useCatchDocumentError } from '@/components/pdf-previewer/hooks';
import { Spin } from '@/components/ui/spin';
import FileError from '@/pages/document-viewer/file-error';
import styles from './index.less';

@@ -50,7 +50,11 @@ const PdfPreview = ({ highlights: state, setWidthAndHeight }: IProps) => {
>
<PdfLoader
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"
errorMessage={<FileError>{error}</FileError>}
>

+ 1
- 1
web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/document-preview/txt-preview.tsx Wyświetl plik

@@ -1,6 +1,6 @@
import message from '@/components/ui/message';
import { Spin } from '@/components/ui/spin';
import request from '@/utils/request';
import { Spin } from 'antd';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import { useGetDocumentUrl } from './hooks';

+ 1
- 2
web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/index.less Wyświetl plik

@@ -41,8 +41,7 @@

.chunkContainer {
display: flex;
// height: calc(100vh - 332px);
height: calc(100vh - 300px);
height: calc(100vh - 332px);
}

.chunkOtherContainer {

+ 1
- 1
web/src/pages/dataset/dataset/parsing-status-cell.tsx Wyświetl plik

@@ -61,7 +61,7 @@ export function ParsingStatusCell({
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant={'ghost'} size={'sm'}>
{parser_id}
{parser_id === 'naive' ? 'general' : parser_id}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>

+ 69
- 71
web/src/pages/dataset/setting/tag-table/index.tsx Wyświetl plik

@@ -221,79 +221,77 @@ export function TagTable() {
</ConfirmDeleteDialog>
)}
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</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>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
<Table rootClassName="rounded-none border max-h-80 overflow-y-auto">
<TableHeader className="bg-[#39393b]">
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</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>
)}
</TableBody>
</Table>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<div className="flex-1 text-sm text-muted-foreground">
{selectedRowLength} of {table.getFilteredRowModel().rows.length}{' '}
row(s) selected.
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<div className="flex-1 text-sm text-muted-foreground">
{selectedRowLength} of {table.getFilteredRowModel().rows.length}{' '}
row(s) selected.
</div>
<div className="space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
{t('common.previousPage')}
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
{t('common.nextPage')}
</Button>
</div>
<div className="space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
{t('common.previousPage')}
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
{t('common.nextPage')}
</Button>
</div>
</div>
{tagRenameVisible && (

+ 2
- 2
web/src/pages/document-viewer/index.tsx Wyświetl plik

@@ -2,12 +2,12 @@ import { Images } from '@/constants/common';
import { api_host } from '@/utils/api';
import { Flex } from 'antd';
import { useParams, useSearchParams } from 'umi';
import Md from './md';
import Text from './text';
import Docx from './docx';
import Excel from './excel';
import Image from './image';
import Md from './md';
import Pdf from './pdf';
import Text from './text';

import { previewHtmlFile } from '@/utils/file-util';
import styles from './index.less';

+ 3
- 3
web/src/pages/document-viewer/md/index.tsx Wyświetl plik

@@ -19,13 +19,13 @@ const Md: React.FC<MdProps> = ({ filePath }) => {
return res.text();
})
.then((text) => setContent(text))
.catch((err) => setError(err.message))
.catch((err) => setError(err.message));
}, [filePath]);

if (error) return (<FileError>{error}</FileError>);
if (error) return <FileError>{error}</FileError>;

return (
<div style={{ padding: 24, height: "100vh", overflow: "scroll" }}>
<div style={{ padding: 24, height: '100vh', overflow: 'scroll' }}>
<ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
</div>
);

+ 4
- 4
web/src/pages/document-viewer/text/index.tsx Wyświetl plik

@@ -17,14 +17,14 @@ const Md: React.FC<TxtProps> = ({ filePath }) => {
return res.text();
})
.then((text) => setContent(text))
.catch((err) => setError(err.message))
.catch((err) => setError(err.message));
}, [filePath]);

if (error) return (<FileError>{error}</FileError>);
if (error) return <FileError>{error}</FileError>;

return (
<div style={{ padding: 24, height: "100vh", overflow: "scroll" }}>
{content}
<div style={{ padding: 24, height: '100vh', overflow: 'scroll' }}>
{content}
</div>
);
};

Ładowanie…
Anuluj
Zapisz