mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-05-03 08:47:48 +08:00
refactor(ui): update knowledge graph, chunk, metadata, agent log styles (#13518)
### What problem does this PR solve? Update UI styles: - **Dataset** > **Knowledge graph** tooltip - **Dataset** > **Files** > **Manage metadata** modal - **Dataset** > **Files** > **Modify Chunking Method** > **Auto metadata** > **Manage generation settings** modal - **Agent** > **Canvas (Ingestion pipeline)** > **Dataflow result** ### Type of change - [x] Refactoring
This commit is contained in:
@ -1,21 +1,35 @@
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { formatBytes } from '@/utils/file-util';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
size: number;
|
||||
name: string;
|
||||
create_date: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export default ({ size, name, create_date }: Props) => {
|
||||
export default ({ size, name, create_date, className }: Props) => {
|
||||
const sizeName = formatBytes(size);
|
||||
const dateStr = formatDate(create_date);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-[16px] truncate">{name}</h2>
|
||||
<div className="text-text-secondary text-[12px] pt-[5px]">
|
||||
Size:{sizeName} Uploaded Time:{dateStr}
|
||||
</div>
|
||||
</div>
|
||||
<header className={className}>
|
||||
<h2 className="text-2xl font-semibold truncate">{name}</h2>
|
||||
<dl
|
||||
className="
|
||||
text-text-secondary text-sm flex
|
||||
[&_dt]:after:content-[':'] [&_dt]:after:me-[.5ch]
|
||||
[&_dd]:me-[2ch]"
|
||||
>
|
||||
<dt>{t('chunk.size')}</dt>
|
||||
<dd>{sizeName}</dd>
|
||||
|
||||
<dt>{t('chunk.uploadedTime')}</dt>
|
||||
<dd>{dateStr}</dd>
|
||||
</dl>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
@ -25,7 +25,7 @@ const Preview = ({
|
||||
return (
|
||||
<>
|
||||
{fileType === 'pdf' && highlights && setWidthAndHeight && (
|
||||
<section>
|
||||
<section className="h-full">
|
||||
<PdfPreviewer
|
||||
className={className}
|
||||
highlights={highlights}
|
||||
|
||||
@ -11,10 +11,10 @@ import {
|
||||
import { Spin } from '@/components/ui/spin';
|
||||
// import FileError from '@/pages/document-viewer/file-error';
|
||||
import { Authorization } from '@/constants/authorization';
|
||||
import { cn } from '@/lib/utils';
|
||||
import FileError from '@/pages/document-viewer/file-error';
|
||||
import { getAuthorization } from '@/utils/authorization-util';
|
||||
import { useCatchDocumentError } from './hooks';
|
||||
import styles from './index.module.less';
|
||||
type PdfLoaderProps = React.ComponentProps<typeof PdfLoader> & {
|
||||
httpHeaders?: Record<string, string>;
|
||||
};
|
||||
@ -69,7 +69,11 @@ const PdfPreview = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${styles.documentContainer} rounded-[10px] overflow-hidden ${className}`}
|
||||
className={cn(
|
||||
'relative size-full rounded overflow-hidden',
|
||||
'[&_.pdfViewer.removePageBorders_.page]:last-of-type:mb-0',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Loader
|
||||
url={url}
|
||||
|
||||
@ -1,14 +1,11 @@
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
import { Button } from '../ui/button';
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from '../ui/hover-card';
|
||||
import { Input } from '../ui/input';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
|
||||
interface EditTagsProps {
|
||||
value?: string[];
|
||||
onChange?: (tags: string[]) => void;
|
||||
@ -59,28 +56,35 @@ const EditTag = React.forwardRef<HTMLDivElement, EditTagsProps>(
|
||||
|
||||
const forMap = (tag: string) => {
|
||||
return (
|
||||
<HoverCard key={tag}>
|
||||
<HoverCardContent side="top">{tag}</HoverCardContent>
|
||||
<HoverCardTrigger asChild>
|
||||
<div className="w-fit flex items-center justify-center gap-2 border border-border-button px-2 py-1 rounded-sm bg-bg-card">
|
||||
<Tooltip key={tag}>
|
||||
<TooltipContent side="top">{tag}</TooltipContent>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
className={cn(
|
||||
'w-fit h-8 flex items-center justify-center gap-2 border border-border-button rounded-sm bg-bg-card',
|
||||
disabled ? 'px-2' : 'ps-2 pe-1',
|
||||
)}
|
||||
>
|
||||
<div className="flex gap-2 items-center">
|
||||
<div className="max-w-80 overflow-hidden text-ellipsis">
|
||||
{tag}
|
||||
</div>
|
||||
{!disabled && (
|
||||
<Trash2
|
||||
size={14}
|
||||
className="text-text-secondary hover:text-state-error"
|
||||
<Button
|
||||
variant="delete"
|
||||
size="icon-xs"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleClose(tag);
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<Trash2 className="size-[1em]" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</HoverCardTrigger>
|
||||
</HoverCard>
|
||||
</TooltipTrigger>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
@ -107,10 +111,11 @@ const EditTag = React.forwardRef<HTMLDivElement, EditTagsProps>(
|
||||
)}
|
||||
<div className="flex gap-2 py-1 flex-wrap">
|
||||
{Array.isArray(tagChild) && tagChild.length > 0 && <>{tagChild}</>}
|
||||
|
||||
{!inputVisible && !disabled && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="w-fit flex items-center justify-center gap-2 bg-bg-card border-border-button border"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={showInput}
|
||||
disabled={disabled}
|
||||
data-testid={addButtonTestId}
|
||||
|
||||
@ -65,10 +65,12 @@ export default function ListFilterBar({
|
||||
filters,
|
||||
className,
|
||||
icon,
|
||||
iconClassName,
|
||||
filterGroup,
|
||||
}: PropsWithChildren<IProps & Omit<CheckboxFormMultipleProps, 'setOpen'>> & {
|
||||
className?: string;
|
||||
icon?: ReactNode;
|
||||
iconClassName?: string;
|
||||
filterGroup?: Record<string, string[]>;
|
||||
}) {
|
||||
const filterCount = useMemo(() => {
|
||||
@ -95,7 +97,10 @@ export default function ListFilterBar({
|
||||
<div className="text-2xl font-semibold flex items-center gap-2.5">
|
||||
{typeof icon === 'string' ? (
|
||||
// <IconFont name={icon} className="size-6"></IconFont>
|
||||
<HomeIcon name={`${icon}`} width={'32'} />
|
||||
<HomeIcon
|
||||
name={`${icon}`}
|
||||
imgClass={cn('size-[1em]', iconClassName)}
|
||||
/>
|
||||
) : (
|
||||
icon
|
||||
)}
|
||||
|
||||
@ -80,7 +80,7 @@ function TimelineContent({
|
||||
return (
|
||||
<div
|
||||
data-slot="timeline-content"
|
||||
className={cn('text-muted-foreground text-sm', className)}
|
||||
className={cn('text-text-secondary text-sm', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@ -102,7 +102,7 @@ function TimelineDate({
|
||||
<Comp
|
||||
data-slot="timeline-date"
|
||||
className={cn(
|
||||
'text-muted-foreground mb-1 block text-xs font-medium group-data-[orientation=vertical]/timeline:max-sm:h-4',
|
||||
'text-text-secondary mb-1 block text-xs font-medium group-data-[orientation=vertical]/timeline:max-sm:h-4',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@ -158,7 +158,10 @@ function TimelineItem({ step, className, ...props }: TimelineItemProps) {
|
||||
<div
|
||||
data-slot="timeline-item"
|
||||
className={cn(
|
||||
'group/timeline-item has-[+[data-completed]]:[&_[data-slot=timeline-separator]]:bg-primary relative flex flex-1 flex-col gap-0.5 group-data-[orientation=horizontal]/timeline:mt-8 group-data-[orientation=horizontal]/timeline:not-last:pe-8 group-data-[orientation=vertical]/timeline:ms-8 group-data-[orientation=vertical]/timeline:not-last:pb-12',
|
||||
'group/timeline-item has-[+[data-completed]]:[&_[data-slot=timeline-separator]]:bg-primary',
|
||||
'relative flex flex-1 flex-col gap-0.5',
|
||||
'group-data-[orientation=horizontal]/timeline:mt-8 group-data-[orientation=horizontal]/timeline:not-last:pe-8',
|
||||
'group-data-[orientation=vertical]/timeline:ms-8 group-data-[orientation=vertical]/timeline:not-last:pb-12',
|
||||
className,
|
||||
)}
|
||||
data-completed={step <= activeStep || undefined}
|
||||
@ -176,7 +179,12 @@ function TimelineSeparator({
|
||||
<div
|
||||
data-slot="timeline-separator"
|
||||
className={cn(
|
||||
'bg-primary/10 absolute self-start group-last/timeline-item:hidden group-data-[orientation=horizontal]/timeline:-top-6 group-data-[orientation=horizontal]/timeline:h-0.5 group-data-[orientation=horizontal]/timeline:w-[calc(100%-1rem-0.25rem)] group-data-[orientation=horizontal]/timeline:translate-x-4.5 group-data-[orientation=horizontal]/timeline:-translate-y-1/2 group-data-[orientation=vertical]/timeline:-left-6 group-data-[orientation=vertical]/timeline:h-[calc(100%-1rem-0.25rem)] group-data-[orientation=vertical]/timeline:w-0.5 group-data-[orientation=vertical]/timeline:-translate-x-1/2 group-data-[orientation=vertical]/timeline:translate-y-4.5',
|
||||
'bg-primary/10 absolute self-start group-last/timeline-item:hidden',
|
||||
'group-data-[orientation=horizontal]/timeline:-top-6 group-data-[orientation=horizontal]/timeline:h-0.5',
|
||||
'group-data-[orientation=horizontal]/timeline:w-[calc(100%-1rem-0.25rem)] group-data-[orientation=horizontal]/timeline:translate-x-4.5',
|
||||
'group-data-[orientation=horizontal]/timeline:-translate-y-1/2 group-data-[orientation=vertical]/timeline:-left-6',
|
||||
'group-data-[orientation=vertical]/timeline:h-[calc(100%-1rem-0.25rem)] group-data-[orientation=vertical]/timeline:w-0.5',
|
||||
'group-data-[orientation=vertical]/timeline:-translate-x-1/2 group-data-[orientation=vertical]/timeline:translate-y-4.5',
|
||||
className,
|
||||
)}
|
||||
aria-hidden="true"
|
||||
@ -208,10 +216,8 @@ interface TimelineIndicatorNodeProps {
|
||||
indicatorBorderColor?: string;
|
||||
}
|
||||
interface TimelineNode
|
||||
extends Omit<
|
||||
React.HTMLAttributes<HTMLDivElement>,
|
||||
'id' | 'title' | 'content'
|
||||
>,
|
||||
extends
|
||||
Omit<React.HTMLAttributes<HTMLDivElement>, 'id' | 'title' | 'content'>,
|
||||
TimelineIndicatorNodeProps {
|
||||
id: string | number;
|
||||
title?: React.ReactNode;
|
||||
@ -231,7 +237,7 @@ interface CustomTimelineProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
nodeSize?: string | number;
|
||||
onStepChange?: (step: number, id: string | number) => void;
|
||||
orientation?: 'horizontal' | 'vertical';
|
||||
lineStyle?: 'solid' | 'dashed';
|
||||
lineStyle?: 'solid' | 'dashed' | 'none';
|
||||
lineColor?: string;
|
||||
indicatorColor?: string;
|
||||
defaultValue?: number;
|
||||
@ -254,7 +260,7 @@ const CustomTimeline = ({
|
||||
}: CustomTimelineProps) => {
|
||||
const [internalActiveStep, setInternalActiveStep] =
|
||||
React.useState(defaultValue);
|
||||
const _lineColor = `rgb(${parseColorToRGB(lineColor)})`;
|
||||
// const _lineColor = `rgb(${parseColorToRGB(lineColor)})`;
|
||||
const currentActiveStep = activeStep ?? internalActiveStep;
|
||||
|
||||
const handleStepChange = (step: number, id: string | number) => {
|
||||
@ -281,7 +287,7 @@ const CustomTimeline = ({
|
||||
const _nodeSizeTemp =
|
||||
isActive && _activeStyle?.nodeSize
|
||||
? _activeStyle?.nodeSize
|
||||
: node.nodeSize ?? nodeSize;
|
||||
: (node.nodeSize ?? nodeSize);
|
||||
const _nodeSize =
|
||||
typeof _nodeSizeTemp === 'number'
|
||||
? `${_nodeSizeTemp}px`
|
||||
@ -302,25 +308,22 @@ const CustomTimeline = ({
|
||||
>
|
||||
<TimelineSeparator
|
||||
className={cn(
|
||||
'border-0.5 border-border-default',
|
||||
'group-data-[orientation=horizontal]/timeline:-top-6 group-data-[orientation=horizontal]/timeline:h-0.1 group-data-[orientation=horizontal]/timeline:-translate-y-1/2',
|
||||
'group-data-[orientation=vertical]/timeline:-left-6 group-data-[orientation=vertical]/timeline:w-0.1 group-data-[orientation=vertical]/timeline:-translate-x-1/2 ',
|
||||
// `group-data-[orientation=horizontal]/timeline:w-[calc(100%-0.5rem-1rem)] group-data-[orientation=vertical]/timeline:h-[calc(100%-1rem-1rem)] group-data-[orientation=vertical]/timeline:translate-y-7 group-data-[orientation=horizontal]/timeline:translate-x-7`,
|
||||
)}
|
||||
style={{
|
||||
border:
|
||||
lineStyle === 'dashed'
|
||||
? `1px dashed ${isActive ? _activeStyle.lineColor || _lineColor : _lineColor}`
|
||||
: lineStyle === 'solid'
|
||||
? `1px solid ${isActive ? _activeStyle.lineColor || _lineColor : _lineColor}`
|
||||
: 'none',
|
||||
borderStyle: lineStyle,
|
||||
// borderColor: isActive ? _activeStyle.lineColor || _lineColor : _lineColor,
|
||||
backgroundColor: 'transparent',
|
||||
width:
|
||||
orientation === 'horizontal'
|
||||
? `calc(100% - ${_nodeSize} - 2px - 0.1rem)`
|
||||
? `calc(100% - ${_nodeSize})`
|
||||
: '1px',
|
||||
height:
|
||||
orientation === 'vertical'
|
||||
? `calc(100% - ${_nodeSize} - 2px - 0.1rem)`
|
||||
? `calc(100% - ${_nodeSize})`
|
||||
: '1px',
|
||||
transform: `translate(${
|
||||
orientation === 'horizontal' ? `${_nodeSize}` : '0'
|
||||
@ -330,9 +333,11 @@ const CustomTimeline = ({
|
||||
|
||||
<TimelineIndicator
|
||||
className={cn(
|
||||
'flex items-center justify-center p-1',
|
||||
isCompleted && 'bg-primary border-primary',
|
||||
!isCompleted && 'border-text-secondary bg-bg-base',
|
||||
'flex items-center justify-center p-1 border bg-bg-card',
|
||||
isCompleted
|
||||
? 'border-border-accent text-text-primary'
|
||||
: 'border-border-default text-text-secondary',
|
||||
isActive && 'text-accent-primary bg-accent-primary/10',
|
||||
)}
|
||||
style={{
|
||||
width: _nodeSize,
|
||||
@ -347,29 +352,15 @@ const CustomTimeline = ({
|
||||
// : isCompleted
|
||||
// ? indicatorColor
|
||||
// : '',
|
||||
backgroundColor: isActive
|
||||
? _activeStyle.indicatorBgColor ||
|
||||
`rgba(${r}, ${g}, ${b}, 0.1)`
|
||||
: isCompleted
|
||||
? `rgba(${r}, ${g}, ${b}, 0.1)`
|
||||
: '',
|
||||
// backgroundColor: isActive
|
||||
// ? _activeStyle.indicatorBgColor ||
|
||||
// `rgba(${r}, ${g}, ${b}, 0.1)`
|
||||
// : isCompleted
|
||||
// ? `rgba(${r}, ${g}, ${b}, 0.1)`
|
||||
// : '',
|
||||
}}
|
||||
>
|
||||
{node.icon && (
|
||||
<div
|
||||
className={cn(
|
||||
'text-current',
|
||||
`w-[${_nodeSize}] h-[${_nodeSize}]`,
|
||||
isActive &&
|
||||
`text-primary w-[${_activeStyle.nodeSize || _nodeSize}] h-[${_activeStyle.nodeSize || _nodeSize}]`,
|
||||
)}
|
||||
style={{
|
||||
color: isActive ? _activeStyle.iconColor : undefined,
|
||||
}}
|
||||
>
|
||||
{node.icon}
|
||||
</div>
|
||||
)}
|
||||
{node.icon}
|
||||
</TimelineIndicator>
|
||||
|
||||
<TimelineHeader className="transform -translate-x-[40%] text-center">
|
||||
|
||||
@ -88,9 +88,7 @@ const RaptorFormFields = ({
|
||||
tooltip={t('useRaptorTip')}
|
||||
className="text-sm w-1/4 whitespace-break-spaces"
|
||||
>
|
||||
<div className="w-auto xl:w-20 2xl:w-24 3xl:w-28 4xl:w-auto ">
|
||||
{t('useRaptor')}
|
||||
</div>
|
||||
{t('useRaptor')}
|
||||
</FormLabel>
|
||||
<div className="w-3/4">
|
||||
<FormControl>
|
||||
|
||||
@ -86,7 +86,7 @@ const buttonVariants = cva(
|
||||
|
||||
// Static
|
||||
// Button has no interaction transitions
|
||||
static: '',
|
||||
static: 'text-text-secondary',
|
||||
},
|
||||
size: {
|
||||
auto: '',
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
||||
import { Check } from 'lucide-react';
|
||||
import { LucideCheck } from 'lucide-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
@ -13,7 +13,7 @@ const Checkbox = React.forwardRef<
|
||||
<CheckboxPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'peer size-4 shrink-0 rounded-2xs border border-text-disabled outline-0 transition-colors bg-transparent',
|
||||
'peer size-3.5 shrink-0 rounded-2xs border border-text-disabled outline-0 transition-colors bg-transparent',
|
||||
'hover:border-border-default hover:bg-border-button',
|
||||
'focus-visible:border-border-default focus-visible:bg-border-default',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
@ -22,10 +22,8 @@ const Checkbox = React.forwardRef<
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator
|
||||
className={cn('flex items-center justify-center text-current')}
|
||||
>
|
||||
<Check className="size-3" />
|
||||
<CheckboxPrimitive.Indicator className="flex items-center justify-center text-current">
|
||||
<LucideCheck className="size-2.5 stroke-[3]" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
));
|
||||
|
||||
@ -102,13 +102,14 @@ const FormLabel = React.forwardRef<
|
||||
return (
|
||||
<Label
|
||||
ref={ref}
|
||||
className={cn(className, 'flex pb-0.5')}
|
||||
className={cn(
|
||||
className,
|
||||
required && 'before:content-["*"] before:text-state-error',
|
||||
)}
|
||||
htmlFor={formItemId}
|
||||
{...props}
|
||||
>
|
||||
{required && <span className="text-state-error">*</span>}
|
||||
{props.children}
|
||||
|
||||
<span>{props.children}</span>
|
||||
{tooltip && <FormTooltip tooltip={tooltip}></FormTooltip>}
|
||||
</Label>
|
||||
);
|
||||
|
||||
@ -14,16 +14,19 @@ type RadioProps = {
|
||||
disabled?: boolean;
|
||||
onChange?: (checked: boolean) => void;
|
||||
children?: React.ReactNode;
|
||||
testId?: string;
|
||||
};
|
||||
} & Omit<
|
||||
React.InputHTMLAttributes<HTMLInputElement>,
|
||||
'value' | 'checked' | 'onChange'
|
||||
>;
|
||||
|
||||
function Radio({
|
||||
className,
|
||||
value,
|
||||
checked,
|
||||
disabled,
|
||||
onChange,
|
||||
children,
|
||||
testId,
|
||||
...props
|
||||
}: RadioProps) {
|
||||
const groupContext = useContext(RadioGroupContext);
|
||||
const isControlled = checked !== undefined;
|
||||
@ -57,24 +60,23 @@ function Radio({
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name={groupContext?.name}
|
||||
value={value}
|
||||
checked={isChecked}
|
||||
onClick={handleClick}
|
||||
disabled={mergedDisabled}
|
||||
data-testid={testId}
|
||||
className="peer absolute size-[1px] opacity-0"
|
||||
className={cn('peer absolute size-[1px] opacity-0', className)}
|
||||
{...props}
|
||||
name={groupContext?.name}
|
||||
/>
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-4 w-4 items-center justify-center rounded-full border border-border-button transition-colors',
|
||||
'group-hover/radio:border-border-default hover:border-border-default',
|
||||
'peer-focus:border-primary',
|
||||
'flex h-4 w-4 items-center justify-center rounded-full text-border-button border border-current transition-colors',
|
||||
'group-hover/radio:text-border-default hover:text-border-default',
|
||||
'peer-focus:text-text-primary',
|
||||
isChecked && 'border-primary bg-primary/10',
|
||||
mergedDisabled && 'border-muted',
|
||||
)}
|
||||
data-testid={testId}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
import { supportsCssAnchor } from '@/utils/css-support';
|
||||
import * as React from 'react';
|
||||
import { useId } from 'react';
|
||||
import { Button, ButtonVariants } from './button';
|
||||
export declare type SegmentedValue = string | number;
|
||||
export declare type SegmentedRawOption = SegmentedValue;
|
||||
@ -60,71 +62,152 @@ export interface SegmentedProps extends Omit<
|
||||
buttonSize?: ButtonVariants['size'];
|
||||
}
|
||||
|
||||
export const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
|
||||
(
|
||||
{
|
||||
options,
|
||||
value,
|
||||
onChange,
|
||||
className,
|
||||
activeClassName,
|
||||
itemClassName,
|
||||
rounded = 'default',
|
||||
sizeType = 'default',
|
||||
buttonSize = 'default',
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const [selectedValue, setSelectedValue] = React.useState<
|
||||
SegmentedValue | undefined
|
||||
>(value);
|
||||
React.useEffect(() => {
|
||||
setSelectedValue(value);
|
||||
}, [value]);
|
||||
const handleOnChange = (e: SegmentedValue) => {
|
||||
if (onChange) {
|
||||
onChange(e);
|
||||
}
|
||||
setSelectedValue(e);
|
||||
};
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex items-center p-1 gap-2 bg-bg-card',
|
||||
segmentedVariants.round[rounded],
|
||||
segmentedVariants.size[sizeType],
|
||||
const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
|
||||
supportsCssAnchor
|
||||
? (
|
||||
{
|
||||
options,
|
||||
value,
|
||||
onChange,
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{options.map((option) => {
|
||||
const isObject = typeof option === 'object';
|
||||
const actualValue = isObject ? option.value : option;
|
||||
activeClassName,
|
||||
itemClassName,
|
||||
rounded = 'default',
|
||||
sizeType = 'default',
|
||||
buttonSize = 'default',
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const anchorNamePrefix = useId().replace(/:/g, '');
|
||||
const [selectedValue, setSelectedValue] = React.useState<
|
||||
SegmentedValue | undefined
|
||||
>(value);
|
||||
React.useEffect(() => {
|
||||
setSelectedValue(value);
|
||||
}, [value]);
|
||||
const handleOnChange = (e: SegmentedValue) => {
|
||||
if (onChange) {
|
||||
onChange(e);
|
||||
}
|
||||
setSelectedValue(e);
|
||||
};
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex items-center p-1 gap-2 bg-bg-card',
|
||||
segmentedVariants.round[rounded],
|
||||
segmentedVariants.size[sizeType],
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{options.map((option) => {
|
||||
const isObject = typeof option === 'object';
|
||||
const actualValue = isObject ? option.value : option;
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={actualValue}
|
||||
type="button"
|
||||
size={buttonSize}
|
||||
variant="static"
|
||||
console.log(actualValue);
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={actualValue}
|
||||
type="button"
|
||||
size={buttonSize}
|
||||
variant="static"
|
||||
className={cn(
|
||||
selectedValue === actualValue && 'text-text-primary',
|
||||
itemClassName,
|
||||
selectedValue === actualValue && activeClassName,
|
||||
'relative z-10',
|
||||
)}
|
||||
onClick={() => handleOnChange(actualValue)}
|
||||
style={{
|
||||
anchorName: `--${anchorNamePrefix}-${String(actualValue).replace('/', '')}`,
|
||||
}}
|
||||
>
|
||||
{isObject ? option.label : option}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
{
|
||||
'text-text-primary bg-bg-base': selectedValue === actualValue,
|
||||
},
|
||||
itemClassName,
|
||||
activeClassName && selectedValue === actualValue
|
||||
? activeClassName
|
||||
: '',
|
||||
'absolute bg-bg-base rounded-sm transition-all',
|
||||
)}
|
||||
onClick={() => handleOnChange(actualValue)}
|
||||
>
|
||||
{isObject ? option.label : option}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
style={{
|
||||
positionAnchor: `--${anchorNamePrefix}-${String(selectedValue).replace('/', '')}`,
|
||||
width: 'anchor-size(width)',
|
||||
height: 'anchor-size(height)',
|
||||
top: 'anchor(top)',
|
||||
left: 'anchor(left)',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
: (
|
||||
{
|
||||
options,
|
||||
value,
|
||||
onChange,
|
||||
className,
|
||||
activeClassName,
|
||||
itemClassName,
|
||||
rounded = 'default',
|
||||
sizeType = 'default',
|
||||
buttonSize = 'default',
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const [selectedValue, setSelectedValue] = React.useState<
|
||||
SegmentedValue | undefined
|
||||
>(value);
|
||||
React.useEffect(() => {
|
||||
setSelectedValue(value);
|
||||
}, [value]);
|
||||
const handleOnChange = (e: SegmentedValue) => {
|
||||
if (onChange) {
|
||||
onChange(e);
|
||||
}
|
||||
setSelectedValue(e);
|
||||
};
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex items-center p-1 gap-2 bg-bg-card',
|
||||
segmentedVariants.round[rounded],
|
||||
segmentedVariants.size[sizeType],
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{options.map((option) => {
|
||||
const isObject = typeof option === 'object';
|
||||
const actualValue = isObject ? option.value : option;
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={actualValue}
|
||||
type="button"
|
||||
size={buttonSize}
|
||||
variant="static"
|
||||
className={cn(
|
||||
{
|
||||
'text-text-primary bg-bg-base':
|
||||
selectedValue === actualValue,
|
||||
},
|
||||
itemClassName,
|
||||
selectedValue === actualValue && activeClassName,
|
||||
)}
|
||||
onClick={() => handleOnChange(actualValue)}
|
||||
>
|
||||
{isObject ? option.label : option}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Segmented.displayName = 'Segmented';
|
||||
export { Segmented };
|
||||
|
||||
@ -16,15 +16,17 @@ const TooltipContent = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<TooltipPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'z-50 overflow-auto scrollbar-auto rounded-md whitespace-pre-wrap border bg-bg-base px-3 py-1.5 text-sm text-text-primary shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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 max-w-[30vw]',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
<TooltipPrimitive.Portal>
|
||||
<TooltipPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'z-50 overflow-auto scrollbar-auto rounded-md whitespace-pre-wrap border bg-bg-base px-3 py-1.5 text-sm text-text-primary shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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 max-w-[30vw]',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</TooltipPrimitive.Portal>
|
||||
));
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||
|
||||
@ -35,11 +37,12 @@ export const FormTooltip = ({ tooltip }: { tooltip: React.ReactNode }) => {
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
tabIndex={-1}
|
||||
className="align-text-top"
|
||||
onClick={(e) => {
|
||||
e.preventDefault(); // Prevent clicking the tooltip from triggering form save
|
||||
}}
|
||||
>
|
||||
<CircleQuestionMark className="size-3 ml-[2px] -translate-y-1" />
|
||||
<CircleQuestionMark className="size-[.85em] ml-[.25em]" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{tooltip}</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
@ -17,8 +17,8 @@
|
||||
caption {
|
||||
color: @blurBackground;
|
||||
font-size: 14px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
// height: 20px;
|
||||
line-height: 1.25;
|
||||
font-weight: 600;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
@ -584,8 +584,9 @@ Example: A 1 KB message with 1024-dim embedding uses ~9 KB. The 5 MB default lim
|
||||
naive: `<p>Supported file formats are <b>MD, MDX, DOCX, XLSX, XLS (Excel 97-2003), PPTX, PDF, TXT, JPEG, JPG, PNG, TIF, GIF, CSV, JSON, EML, HTML</b>.</p>
|
||||
<p>This method chunks files using a 'naive' method: </p>
|
||||
<p>
|
||||
<ul>
|
||||
<li>Use vision detection model to split the texts into smaller segments.</li>
|
||||
<li>Then, combine adjacent segments until the token count exceeds the threshold specified by 'Chunk token number for text', at which point a chunk is created.</li></p>`,
|
||||
<li>Then, combine adjacent segments until the token count exceeds the threshold specified by 'Chunk token number for text', at which point a chunk is created.</li></ul></p>`,
|
||||
paper: `<p>Only <b>PDF</b> file is supported.</p><p>
|
||||
Papers will be split by section, such as <i>abstract, 1.1, 1.2</i>. </p><p>
|
||||
This approach enables the LLM to summarize the paper more effectively and to provide more comprehensive, understandable responses.
|
||||
@ -597,6 +598,7 @@ Example: A 1 KB message with 1024-dim embedding uses ~9 KB. The 5 MB default lim
|
||||
<p>
|
||||
This chunking method supports <b>XLSX</b> and <b>CSV/TXT</b> file formats.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
If a file is in <b>XLSX</b> or <b>XLS (Excel 97-2003)</b> format, it should contain two columns without headers: one for questions and the other for answers, with the question column preceding the answer column. Multiple sheets are
|
||||
acceptable, provided the columns are properly structured.
|
||||
@ -604,6 +606,7 @@ Example: A 1 KB message with 1024-dim embedding uses ~9 KB. The 5 MB default lim
|
||||
<li>
|
||||
If a file is in <b>CSV/TXT</b> format, it must be UTF-8 encoded with TAB as the delimiter to separate questions and answers.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
<i>
|
||||
Lines of texts that fail to follow the above rules will be ignored, and
|
||||
@ -726,6 +729,8 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
|
||||
table: 'Table',
|
||||
text: 'Text',
|
||||
},
|
||||
size: 'Size',
|
||||
uploadedTime: 'Uploaded time',
|
||||
chunk: 'Chunk',
|
||||
bulk: 'Bulk',
|
||||
selectAll: 'Select all',
|
||||
|
||||
@ -73,10 +73,11 @@ const ChunkCard = ({
|
||||
|
||||
return (
|
||||
<Card
|
||||
className={classNames('relative flex-none', styles.chunkCard, {
|
||||
[`${theme === 'dark' ? styles.cardSelectedDark : styles.cardSelected}`]:
|
||||
selected,
|
||||
})}
|
||||
as="article"
|
||||
className={classNames(
|
||||
'relative flex-none p-3 pt-6 shadow-none transition-colors',
|
||||
selected && 'bg-text-primary/15',
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className="
|
||||
@ -88,8 +89,12 @@ const ChunkCard = ({
|
||||
{t(`chunk.docType.${chunkType}`)}
|
||||
</span>
|
||||
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<Checkbox onCheckedChange={handleCheck} checked={checked}></Checkbox>
|
||||
<div className="flex items-start justify-between gap-2.5">
|
||||
<Checkbox
|
||||
className="mt-1"
|
||||
onCheckedChange={handleCheck}
|
||||
checked={checked}
|
||||
/>
|
||||
|
||||
{/* Using <Tooltip> instead of <Popover> to avoid flickering when hovering over the image */}
|
||||
{item.image_id && (
|
||||
@ -98,41 +103,44 @@ const ChunkCard = ({
|
||||
<Image
|
||||
t={imageCacheKey}
|
||||
id={item.image_id}
|
||||
className={styles.image}
|
||||
className="mt-1 rounded !w-28 object-contain"
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent
|
||||
className="p-0"
|
||||
align={'start'}
|
||||
side={'left'}
|
||||
align="start"
|
||||
side="left"
|
||||
sideOffset={-20}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<Image
|
||||
t={imageCacheKey}
|
||||
id={item.image_id}
|
||||
className={styles.imagePreview}
|
||||
className="size-full max-w-[50vw] max-h-[50vh] object-contain"
|
||||
/>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<section
|
||||
className={cn(styles.content, 'flex-1')}
|
||||
onDoubleClick={handleContentDoubleClick}
|
||||
onClick={handleContentClick}
|
||||
className={cn(styles.content, 'mt-2')}
|
||||
>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(item.content_with_weight),
|
||||
__html: DOMPurify.sanitize(item.content_with_weight).trim(),
|
||||
}}
|
||||
className={classNames(styles.contentText, {
|
||||
[styles.contentEllipsis]: textMode === ChunkTextMode.Ellipse,
|
||||
})}
|
||||
></div>
|
||||
className={classNames(
|
||||
// Keep whitespaces?
|
||||
'text-wrap break-words whitespace-pre',
|
||||
textMode === ChunkTextMode.Ellipse && 'line-clamp-3',
|
||||
)}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<div className="mt-2">
|
||||
<div>
|
||||
<Switch
|
||||
checked={enabled}
|
||||
onCheckedChange={onChange}
|
||||
|
||||
@ -1,10 +1,17 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Ban, CircleCheck, Trash2 } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
LucideToggleLeft,
|
||||
LucideToggleRight,
|
||||
LucideTrash2,
|
||||
} from 'lucide-react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type ICheckboxSetProps = {
|
||||
className?: string;
|
||||
selectAllChunk: (e: any) => void;
|
||||
removeChunk: (e?: any) => void;
|
||||
switchChunk: (available: number) => void;
|
||||
@ -13,6 +20,7 @@ type ICheckboxSetProps = {
|
||||
};
|
||||
export default (props: ICheckboxSetProps) => {
|
||||
const {
|
||||
className,
|
||||
selectAllChunk,
|
||||
removeChunk,
|
||||
switchChunk,
|
||||
@ -45,39 +53,28 @@ export default (props: ICheckboxSetProps) => {
|
||||
}, [selectedChunkIds]);
|
||||
|
||||
return (
|
||||
<div className="flex gap-[40px] py-4 px-2 h-14">
|
||||
<div className="flex items-center gap-3 cursor-pointer text-muted-foreground hover:text-text-primary">
|
||||
<Checkbox
|
||||
id="all_chunks_checkbox"
|
||||
onCheckedChange={handleSelectAllCheck}
|
||||
checked={checked}
|
||||
className=" data-[state=checked]:bg-text-primary data-[state=checked]:border-text-primary data-[state=checked]:text-bg-base border-muted-foreground text-muted-foreground hover:text-bg-base hover:border-text-primary "
|
||||
/>
|
||||
<Label htmlFor="all_chunks_checkbox">{t('chunk.selectAll')}</Label>
|
||||
</div>
|
||||
<div className={cn('flex gap-4 text-sm text-text-secondary', className)}>
|
||||
<Label className="flex items-center gap-2">
|
||||
<Checkbox onCheckedChange={handleSelectAllCheck} checked={checked} />
|
||||
<span>{t('chunk.selectAll')}</span>
|
||||
</Label>
|
||||
|
||||
{isSelected && (
|
||||
<>
|
||||
<div
|
||||
className="flex items-center cursor-pointer text-muted-foreground hover:text-text-primary"
|
||||
onClick={handleEnabledClick}
|
||||
>
|
||||
<CircleCheck size={16} />
|
||||
<span className="block ml-1">{t('chunk.enable')}</span>
|
||||
</div>
|
||||
<div
|
||||
className="flex items-center cursor-pointer text-muted-foreground hover:text-text-primary"
|
||||
onClick={handleDisabledClick}
|
||||
>
|
||||
<Ban size={16} />
|
||||
<Button variant="outline" onClick={handleEnabledClick}>
|
||||
<LucideToggleRight size={16} />
|
||||
{t('chunk.enable')}
|
||||
</Button>
|
||||
|
||||
<Button variant="outline" onClick={handleDisabledClick}>
|
||||
<LucideToggleLeft size={16} />
|
||||
<span className="block ml-1">{t('chunk.disable')}</span>
|
||||
</div>
|
||||
<div
|
||||
className="flex items-center cursor-pointer text-red-400 hover:text-red-500"
|
||||
onClick={handleDeleteClick}
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
<span className="block ml-1">{t('chunk.delete')}</span>
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
<Button variant="danger" onClick={handleDeleteClick}>
|
||||
<LucideTrash2 className="size-[1em]" />
|
||||
{t('chunk.delete')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -8,7 +8,8 @@ import {
|
||||
import { Radio } from '@/components/ui/radio';
|
||||
import { Segmented } from '@/components/ui/segmented';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { ListFilter, Plus } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { LucideFilter, Plus } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { ChunkTextMode } from '../../constant';
|
||||
interface ChunkResultBarProps {
|
||||
@ -21,6 +22,7 @@ interface ChunkResultBarProps {
|
||||
searchString: string;
|
||||
}
|
||||
export default function ChunkResultBar({
|
||||
className,
|
||||
changeChunkTextMode,
|
||||
available,
|
||||
selectAllChunk,
|
||||
@ -59,42 +61,46 @@ export default function ChunkResultBar({
|
||||
changeChunkTextMode(value);
|
||||
};
|
||||
return (
|
||||
<div className="flex pr-[25px]">
|
||||
<div className={cn('flex justify-end gap-4', className)}>
|
||||
<Segmented
|
||||
className="gap-0 me-auto"
|
||||
buttonSize="xs"
|
||||
itemClassName="px-2"
|
||||
options={textSelectOptions}
|
||||
value={textSelectValue}
|
||||
onChange={changeTextSelectValue}
|
||||
/>
|
||||
<div className="ml-auto"></div>
|
||||
<div className="h-8 flex items-center gap-5">
|
||||
<SearchInput
|
||||
// style={{ width: 200 }}
|
||||
placeholder={t('search')}
|
||||
// icon={<SearchOutlined />}
|
||||
onChange={handleInputChange}
|
||||
value={searchString}
|
||||
/>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
// className="bg-bg-card text-text-secondary hover:bg-card"
|
||||
>
|
||||
<ListFilter />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0 w-[200px]">
|
||||
{filterContent}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
onClick={() => createChunk()}
|
||||
// className="bg-bg-card text-primary hover:bg-card"
|
||||
>
|
||||
<Plus size={44} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
// className="bg-bg-card text-text-secondary hover:bg-card"
|
||||
>
|
||||
<LucideFilter />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0 w-[200px]">
|
||||
{filterContent}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
<SearchInput
|
||||
className="w-28"
|
||||
placeholder={t('search')}
|
||||
onChange={handleInputChange}
|
||||
value={searchString}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => createChunk()}
|
||||
// className="bg-bg-card text-primary hover:bg-card"
|
||||
>
|
||||
<Plus size={44} />
|
||||
</Button>
|
||||
{/* <div className="w-[20px]"></div>
|
||||
<div className="w-[20px]"></div> */}
|
||||
</div>
|
||||
|
||||
@ -24,6 +24,7 @@ import DocumentHeader from '@/components/document-preview/document-header';
|
||||
import { useGetDocumentUrl } from '@/components/document-preview/hooks';
|
||||
import { PageHeader } from '@/components/page-header';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import message from '@/components/ui/message';
|
||||
import {
|
||||
RAGFlowPagination,
|
||||
@ -174,7 +175,7 @@ const Chunk = () => {
|
||||
}, [documentInfo]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className="h-dvh flex flex-col">
|
||||
<PageHeader>
|
||||
<Button
|
||||
variant="outline"
|
||||
@ -186,49 +187,58 @@ const Chunk = () => {
|
||||
{t('common.back')}
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<div className={styles.chunkPage}>
|
||||
<div className="flex flex-1 gap-8">
|
||||
<div className="w-2/5">
|
||||
<div className="h-[100px] flex flex-col justify-end pb-[5px]">
|
||||
<DocumentHeader {...documentInfo} />
|
||||
</div>
|
||||
<section className={styles.documentPreview}>
|
||||
|
||||
<Card className="mx-5 mb-5 flex-1 h-0 p-0 bg-transparent shadow-none">
|
||||
<CardContent className="p-0 h-full flex flex-row divide-x-0.5 rtl:divide-x-reverse">
|
||||
<article className="w-2/5 flex flex-col">
|
||||
<DocumentHeader className="flex-0 p-5 pb-0" {...documentInfo} />
|
||||
|
||||
<div className="flex-1 h-0 p-5 pt-2.5">
|
||||
<DocumentPreview
|
||||
className={styles.documentPreview}
|
||||
fileType={fileType}
|
||||
highlights={highlights}
|
||||
setWidthAndHeight={setWidthAndHeight}
|
||||
url={fileUrl}
|
||||
></DocumentPreview>
|
||||
</section>
|
||||
</div>
|
||||
<div
|
||||
/>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article
|
||||
className={classNames(
|
||||
{ [styles.pagePdfWrapper]: isPdf },
|
||||
'flex flex-col w-3/5',
|
||||
)}
|
||||
>
|
||||
<Spin spinning={loading} className={styles.spin} size="large">
|
||||
<div className="h-[100px] flex flex-col justify-end pb-[5px]">
|
||||
<div>
|
||||
<h2 className="text-[24px]">{t('chunk.chunkResult')}</h2>
|
||||
<div className="text-[14px] text-text-secondary">
|
||||
{t('chunk.chunkResultTip')}
|
||||
</div>
|
||||
</div>
|
||||
<header className="flex-0 p-5 pb-2.5 border-b-0.5 border-b-border-button">
|
||||
<h2 className="text-[24px]">{t('chunk.chunkResult')}</h2>
|
||||
<div className="text-[14px] text-text-secondary">
|
||||
{t('chunk.chunkResultTip')}
|
||||
</div>
|
||||
<div className=" rounded-[16px] bg-[#FFF]/10 pl-[20px] pb-[20px] pt-[20px] box-border mb-2">
|
||||
<ChunkResultBar
|
||||
handleInputChange={handleInputChange}
|
||||
searchString={searchString}
|
||||
changeChunkTextMode={changeChunkTextMode}
|
||||
createChunk={showChunkUpdatingModal}
|
||||
available={available}
|
||||
selectAllChunk={selectAllChunk}
|
||||
handleSetAvailable={handleSetAvailable}
|
||||
/>
|
||||
<div className="pt-[5px] pb-[5px]">
|
||||
</header>
|
||||
|
||||
<Spin spinning={loading} className="flex-1 h-0" size="large">
|
||||
<div className="relative @container h-full px-5 pb-5 overflow-x-hidden overflow-y-auto">
|
||||
<div
|
||||
className="
|
||||
sticky top-0 z-[1] bg-bg-base space-y-4 py-5
|
||||
@4xl:flex @4xl:justify-between @4xl:items-center
|
||||
@4xl:space-y-0 @4xl:gap-4
|
||||
"
|
||||
role="toolbar"
|
||||
>
|
||||
<ChunkResultBar
|
||||
className="@4xl:order-2"
|
||||
handleInputChange={handleInputChange}
|
||||
searchString={searchString}
|
||||
changeChunkTextMode={changeChunkTextMode}
|
||||
createChunk={showChunkUpdatingModal}
|
||||
available={available}
|
||||
selectAllChunk={selectAllChunk}
|
||||
handleSetAvailable={handleSetAvailable}
|
||||
/>
|
||||
|
||||
<CheckboxSets
|
||||
className="h-8"
|
||||
selectAllChunk={selectAllChunk}
|
||||
switchChunk={handleSwitchChunk}
|
||||
removeChunk={handleRemoveChunk}
|
||||
@ -236,35 +246,27 @@ const Chunk = () => {
|
||||
selectedChunkIds={selectedChunkIds}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.pageContent}>
|
||||
<div
|
||||
className={classNames(
|
||||
styles.chunkContainer,
|
||||
{
|
||||
[styles.chunkOtherContainer]: !isPdf,
|
||||
},
|
||||
'flex flex-col gap-4',
|
||||
)}
|
||||
>
|
||||
{chunkList.map((item) => (
|
||||
<ChunkCard
|
||||
item={item}
|
||||
key={item.chunk_id}
|
||||
editChunk={showChunkUpdatingModal}
|
||||
checked={selectedChunkIds.some(
|
||||
(x) => x === item.chunk_id,
|
||||
)}
|
||||
handleCheckboxClick={handleSingleCheckboxClick}
|
||||
switchChunk={handleSwitchChunk}
|
||||
clickChunkCard={handleChunkCardClick}
|
||||
selected={item.chunk_id === selectedChunkId}
|
||||
textMode={textMode}
|
||||
t={dataUpdatedAt}
|
||||
></ChunkCard>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{chunkList.map((item) => (
|
||||
<ChunkCard
|
||||
item={item}
|
||||
key={item.chunk_id}
|
||||
editChunk={showChunkUpdatingModal}
|
||||
checked={selectedChunkIds.some(
|
||||
(x) => x === item.chunk_id,
|
||||
)}
|
||||
handleCheckboxClick={handleSingleCheckboxClick}
|
||||
switchChunk={handleSwitchChunk}
|
||||
clickChunkCard={handleChunkCardClick}
|
||||
selected={item.chunk_id === selectedChunkId}
|
||||
textMode={textMode}
|
||||
t={dataUpdatedAt}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className={styles.pageFooter}>
|
||||
|
||||
<footer className="mt-5">
|
||||
<RAGFlowPagination
|
||||
pageSize={pagination.pageSize}
|
||||
current={pagination.current}
|
||||
@ -272,13 +274,14 @@ const Chunk = () => {
|
||||
onChange={(page, pageSize) => {
|
||||
onPaginationChange(page, pageSize);
|
||||
}}
|
||||
></RAGFlowPagination>
|
||||
</div>
|
||||
/>
|
||||
</footer>
|
||||
</div>
|
||||
</Spin>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{chunkUpdatingVisible && (
|
||||
<CreatingModal
|
||||
doc_id={documentId}
|
||||
@ -290,7 +293,7 @@ const Chunk = () => {
|
||||
parserId={documentInfo.parser_id}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { CustomTimeline, TimelineNode } from '@/components/originui/timeline';
|
||||
import {
|
||||
Blocks,
|
||||
File,
|
||||
FilePlay,
|
||||
FileStack,
|
||||
Heading,
|
||||
ListPlus,
|
||||
LucideBlocks,
|
||||
LucideFile,
|
||||
LucideFilePlay,
|
||||
LucideFileStack,
|
||||
LucideHeading,
|
||||
LucideListPlus,
|
||||
} from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
import { TimelineNodeType } from '../../constant';
|
||||
@ -21,28 +21,28 @@ export type ITimelineNodeObj = {
|
||||
export const TimelineNodeObj = {
|
||||
[TimelineNodeType.begin]: {
|
||||
title: 'File',
|
||||
icon: <File size={13} />,
|
||||
icon: <LucideFile className="size-[1em]" />,
|
||||
clickable: false,
|
||||
},
|
||||
[TimelineNodeType.parser]: {
|
||||
title: 'Parser',
|
||||
icon: <FilePlay size={13} />,
|
||||
icon: <LucideFilePlay className="size-[1em]" />,
|
||||
},
|
||||
[TimelineNodeType.contextGenerator]: {
|
||||
title: 'Context Generator',
|
||||
icon: <FileStack size={13} />,
|
||||
icon: <LucideFileStack className="size-[1em]" />,
|
||||
},
|
||||
[TimelineNodeType.titleSplitter]: {
|
||||
title: 'Title Splitter',
|
||||
icon: <Heading size={13} />,
|
||||
icon: <LucideHeading className="size-[1em]" />,
|
||||
},
|
||||
[TimelineNodeType.characterSplitter]: {
|
||||
title: 'Character Splitter',
|
||||
icon: <Blocks size={13} />,
|
||||
icon: <LucideBlocks className="size-[1em]" />,
|
||||
},
|
||||
[TimelineNodeType.tokenizer]: {
|
||||
title: 'Tokenizer',
|
||||
icon: <ListPlus size={13} />,
|
||||
icon: <LucideListPlus className="size-[1em]" />,
|
||||
clickable: false,
|
||||
},
|
||||
};
|
||||
@ -80,6 +80,7 @@ const TimelineDataFlow = ({
|
||||
onStepChange={handleStepChange}
|
||||
orientation="horizontal"
|
||||
lineStyle="solid"
|
||||
lineColor="rgb(var(--))"
|
||||
nodeSize={24}
|
||||
activeStyle={{
|
||||
nodeSize: 30,
|
||||
|
||||
@ -19,27 +19,21 @@ import { useGetDocumentUrl } from '@/components/document-preview/hooks';
|
||||
import { TimelineNode } from '@/components/originui/timeline';
|
||||
import { PageHeader } from '@/components/page-header';
|
||||
import Spotlight from '@/components/spotlight';
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from '@/components/ui/breadcrumb';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { AgentCategory } from '@/constants/agent';
|
||||
import { AgentCategory, AgentQuery } from '@/constants/agent';
|
||||
import { Images } from '@/constants/common';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
|
||||
import { Routes } from '@/routes';
|
||||
import { LucideArrowBigLeft } from 'lucide-react';
|
||||
import TimelineDataFlow from './components/time-line';
|
||||
import { TimelineNodeType } from './constant';
|
||||
import styles from './index.module.less';
|
||||
import { IDslComponent, IPipelineFileLogDetail } from './interface';
|
||||
import ParserContainer from './parser';
|
||||
|
||||
const Chunk = () => {
|
||||
const DataflowResult = () => {
|
||||
const { isReadOnly, knowledgeId, agentId, agentTitle, documentExtension } =
|
||||
useGetPipelineResultSearchParams();
|
||||
|
||||
@ -158,46 +152,22 @@ const Chunk = () => {
|
||||
return (
|
||||
<>
|
||||
<PageHeader>
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink
|
||||
onClick={() => {
|
||||
if (knowledgeId) {
|
||||
navigateToDatasetList();
|
||||
}
|
||||
if (agentId) {
|
||||
navigateToAgents();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{knowledgeId ? t('knowledgeDetails.dataset') : t('header.flow')}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink
|
||||
onClick={() => {
|
||||
if (knowledgeId) {
|
||||
navigateToDatasetOverview(knowledgeId)();
|
||||
}
|
||||
if (isAgent) {
|
||||
navigateToAgent(agentId, AgentCategory.DataflowCanvas)();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{knowledgeId ? t('knowledgeDetails.overview') : agentTitle}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>
|
||||
{knowledgeId ? documentInfo?.name : t('flow.viewResult')}
|
||||
</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
<Button
|
||||
asLink
|
||||
variant="outline"
|
||||
to={
|
||||
knowledgeId
|
||||
? `${Routes.DatasetBase}${Routes.DataSetOverview}/${knowledgeId}`
|
||||
: isAgent
|
||||
? `${Routes.Agent}/${agentId}?${AgentQuery.Category}=${AgentCategory.DataflowCanvas}`
|
||||
: '#'
|
||||
}
|
||||
>
|
||||
<LucideArrowBigLeft />
|
||||
{t('common.back')}
|
||||
</Button>
|
||||
</PageHeader>
|
||||
|
||||
{type === 'dataflow' && (
|
||||
<div className=" absolute ml-[50%] translate-x-[-50%] top-4 flex justify-center">
|
||||
<TimelineDataFlow
|
||||
@ -264,4 +234,4 @@ const Chunk = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Chunk;
|
||||
export default DataflowResult;
|
||||
|
||||
@ -4,7 +4,7 @@ import { DatePicker } from '@/components/ui/date-picker';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { ColumnDef, Row, Table } from '@tanstack/react-table';
|
||||
import { ListChevronsDownUp, Settings, Trash2 } from 'lucide-react';
|
||||
import { ListChevronsDownUp, LucidePencil, Trash2 } from 'lucide-react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@ -147,7 +147,7 @@ export const useMetadataColumns = ({
|
||||
header: () => <span>{t('knowledgeDetails.metadata.description')}</span>,
|
||||
cell: ({ row }) => (
|
||||
<div className="text-sm truncate max-w-32">
|
||||
{row.getValue('description')}
|
||||
{row.getValue('description') || '-'}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
@ -347,17 +347,17 @@ export const useMetadataColumns = ({
|
||||
cell: ({ row }) => (
|
||||
<div className=" flex opacity-0 group-hover:opacity-100 gap-2">
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
className="bg-transparent px-1 py-0"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={() => {
|
||||
handleEditValueRow(row.original, row.index);
|
||||
}}
|
||||
>
|
||||
<Settings />
|
||||
<LucidePencil />
|
||||
</Button>
|
||||
<Button
|
||||
variant={'delete'}
|
||||
className="p-0 bg-transparent"
|
||||
variant="delete"
|
||||
size="icon-sm"
|
||||
onClick={() => {
|
||||
setDeleteDialogContent({
|
||||
visible: true,
|
||||
|
||||
@ -321,36 +321,8 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
||||
>
|
||||
<>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="w-1/2">
|
||||
{secondTitle || t('knowledgeDetails.metadata.metadata')}
|
||||
</div>
|
||||
<div>
|
||||
{/* {metadataType === MetadataType.Manage && (
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
className="border border-border-button"
|
||||
type="button"
|
||||
onClick={handleMenuClick(Routes.DataSetSetting, {
|
||||
openMetadata: true,
|
||||
})}
|
||||
>
|
||||
{t('knowledgeDetails.metadata.toMetadataSetting')}
|
||||
</Button>
|
||||
)} */}
|
||||
{isCanAdd && activeTab !== 'built-in' && (
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
className="border border-border-button"
|
||||
type="button"
|
||||
onClick={handAddValueRow}
|
||||
data-testid={addButtonTestId}
|
||||
>
|
||||
<Plus />
|
||||
{t('common.add')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-4 items-center justify-between text-truncate">
|
||||
{secondTitle || t('knowledgeDetails.metadata.metadata')}
|
||||
</div>
|
||||
|
||||
{rowSelectionIsEmpty || (
|
||||
@ -366,14 +338,43 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
||||
value={activeTab}
|
||||
onValueChange={(v) => setActiveTab(v as MetadataSettingsTab)}
|
||||
>
|
||||
<TabsList className="w-fit">
|
||||
<TabsTrigger value="generation">
|
||||
{t('knowledgeDetails.metadata.generation')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="built-in">
|
||||
{t('knowledgeDetails.metadata.builtIn')}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<div className="flex gap-4 items-center justify-between">
|
||||
<TabsList className="w-fit">
|
||||
<TabsTrigger value="generation">
|
||||
{t('knowledgeDetails.metadata.generation')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="built-in">
|
||||
{t('knowledgeDetails.metadata.builtIn')}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<div>
|
||||
{/* {metadataType === MetadataType.Manage && (
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
className="border border-border-button"
|
||||
type="button"
|
||||
onClick={handleMenuClick(Routes.DataSetSetting, {
|
||||
openMetadata: true,
|
||||
})}
|
||||
>
|
||||
{t('knowledgeDetails.metadata.toMetadataSetting')}
|
||||
</Button>
|
||||
)} */}
|
||||
{isCanAdd && activeTab !== 'built-in' && (
|
||||
<Button
|
||||
variant="outline"
|
||||
type="button"
|
||||
onClick={handAddValueRow}
|
||||
data-testid={addButtonTestId}
|
||||
>
|
||||
<Plus />
|
||||
{t('common.add')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TabsContent value="generation">
|
||||
<Table rootClassName="max-h-[800px]">
|
||||
<TableHeader>
|
||||
|
||||
@ -283,8 +283,8 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
||||
metaData.valueType === metadataValueTypeEnum['list'] && (
|
||||
<div>
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
className="border border-border-button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={handleAddValue}
|
||||
data-testid={addValueButtonTestId}
|
||||
>
|
||||
|
||||
@ -31,13 +31,14 @@ const CategoryPanel = ({ chunkMethod }: { chunkMethod: string }) => {
|
||||
}, [chunkMethod]);
|
||||
|
||||
return (
|
||||
<section>
|
||||
<div>
|
||||
{imageList.length > 0 ? (
|
||||
<>
|
||||
<h5 className="font-semibold text-base mt-0 mb-1">
|
||||
{`"${item.title}" ${t('methodTitle')}`}
|
||||
</h5>
|
||||
<p
|
||||
className="[&_ul]:list-disc [&_ol]:list-decimal [&_:is(ul,ol)]:pl-8"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(item.description),
|
||||
}}
|
||||
@ -68,7 +69,7 @@ const CategoryPanel = ({ chunkMethod }: { chunkMethod: string }) => {
|
||||
</div>
|
||||
)}
|
||||
{chunkMethod === 'tag' && <TagTabs></TagTabs>}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { t } from 'i18next';
|
||||
import { X } from 'lucide-react';
|
||||
import { LucideX } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import CategoryPanel from './category-panel';
|
||||
|
||||
@ -20,20 +21,25 @@ const ChunkMethodLearnMore = ({ parserId }: { parserId: string }) => {
|
||||
{t('knowledgeDetails.learnMore')}
|
||||
</Button>
|
||||
</div>
|
||||
<div
|
||||
className="bg-[#FFF]/10 p-[20px] rounded-[12px] mt-[10px] relative flex-1 overflow-auto"
|
||||
|
||||
<Card
|
||||
as="article"
|
||||
className="relative flex-1 overflow-auto mt-4"
|
||||
style={{ display: visible ? 'block' : 'none' }}
|
||||
>
|
||||
<CategoryPanel chunkMethod={parserId}></CategoryPanel>
|
||||
<div
|
||||
className="absolute right-1 top-1 cursor-pointer hover:text-[#FFF]/30"
|
||||
onClick={() => {
|
||||
setVisible(false);
|
||||
}}
|
||||
<Button
|
||||
className="absolute right-2 top-2"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={() => setVisible(false)}
|
||||
>
|
||||
<X />
|
||||
</div>
|
||||
</div>
|
||||
<LucideX />
|
||||
</Button>
|
||||
|
||||
<CardContent className="p-5">
|
||||
<CategoryPanel chunkMethod={parserId}></CategoryPanel>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -415,8 +415,8 @@ export function AutoMetadata({
|
||||
avatar={knowledgeBase.avatar}
|
||||
name={knowledgeBase.name}
|
||||
className="size-8"
|
||||
></RAGFlowAvatar>
|
||||
<div className=" text-text-primary text-base space-y-1 overflow-hidden">
|
||||
/>
|
||||
<div className="text-text-primary text-base space-y-1 truncate overflow-hidden">
|
||||
{knowledgeBase.name}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -46,7 +46,6 @@ export function useDatasetTableColumns({
|
||||
id: 'select',
|
||||
header: ({ table }) => (
|
||||
<Checkbox
|
||||
className="size-3"
|
||||
checked={
|
||||
table.getIsAllPageRowsSelected() ||
|
||||
(table.getIsSomePageRowsSelected() && 'indeterminate')
|
||||
@ -57,7 +56,6 @@ export function useDatasetTableColumns({
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<Checkbox
|
||||
className="size-3"
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
aria-label="Select row"
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import { ElementDatum, Graph, IElementEvent } from '@antv/g6';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { useCallback, useEffect, useId, useMemo, useRef } from 'react';
|
||||
import { buildNodesAndCombos, defaultComboLabel } from './util';
|
||||
|
||||
import { useIsDarkTheme } from '@/components/theme-provider';
|
||||
import { cn } from '@/lib/utils';
|
||||
import styles from './index.module.less';
|
||||
|
||||
const TooltipColorMap = {
|
||||
combo: 'red',
|
||||
node: 'black',
|
||||
edge: 'blue',
|
||||
combo: 'text-red-600',
|
||||
node: 'text-black',
|
||||
edge: 'text-blue-600',
|
||||
};
|
||||
|
||||
interface IProps {
|
||||
@ -18,6 +19,7 @@ interface IProps {
|
||||
}
|
||||
|
||||
const ForceGraph = ({ data, show }: IProps) => {
|
||||
const tooltipId = useId();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const graphRef = useRef<Graph | null>(null);
|
||||
const isDark = useIsDarkTheme();
|
||||
@ -52,23 +54,46 @@ const ForceGraph = ({ data, show }: IProps) => {
|
||||
getContent: (e: IElementEvent, items: ElementDatum) => {
|
||||
if (Array.isArray(items)) {
|
||||
if (items.some((x) => x?.isCombo)) {
|
||||
return `<p style="font-weight:600;color:red">${items?.[0]?.data?.label}</p>`;
|
||||
return `<p class="font-semibold text-red-600">${items?.[0]?.data?.label}</p>`;
|
||||
}
|
||||
let result = ``;
|
||||
items.forEach((item) => {
|
||||
result += `<section style="color:${TooltipColorMap[e['targetType'] as keyof typeof TooltipColorMap]};"><h3>${item?.id}</h3>`;
|
||||
if (item?.entity_type) {
|
||||
result += `<div style="padding-bottom: 6px;"><b>Entity type: </b>${item?.entity_type}</div>`;
|
||||
}
|
||||
if (item?.weight) {
|
||||
result += `<div><b>Weight: </b>${item?.weight}</div>`;
|
||||
}
|
||||
if (item?.description) {
|
||||
result += `<p>${item?.description}</p>`;
|
||||
}
|
||||
});
|
||||
return result + '</section>';
|
||||
|
||||
return items
|
||||
.flatMap((item) => {
|
||||
return [
|
||||
'<div ',
|
||||
`id="${tooltipId}"`,
|
||||
`aria-label="${item?.id}"`,
|
||||
`role="tooltip"`,
|
||||
`class="${TooltipColorMap[e['targetType'] as keyof typeof TooltipColorMap]}"`,
|
||||
'>',
|
||||
`<h3>${item?.id}</h3>`,
|
||||
'<dl class="mb-1 empty:hidden">',
|
||||
...(item?.entity_type
|
||||
? [
|
||||
'<div class="flex items-center gap-[.5ch]">',
|
||||
'<dt><b>Entity type: </b></dt>',
|
||||
`<dd>${item.entity_type}</dd>`,
|
||||
'</div>',
|
||||
]
|
||||
: []),
|
||||
...(item?.weight
|
||||
? [
|
||||
'<div class="flex items-center gap-[.5ch]">',
|
||||
'<dt><b>Weight: </b></dt>',
|
||||
`<dd>${item.weight}</dd>`,
|
||||
'</div>',
|
||||
]
|
||||
: []),
|
||||
'</dl>',
|
||||
item.description
|
||||
? `<p class="text-xs">${item.description}</p>`
|
||||
: '',
|
||||
'</div>',
|
||||
];
|
||||
})
|
||||
.join('');
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
},
|
||||
@ -82,34 +107,32 @@ const ForceGraph = ({ data, show }: IProps) => {
|
||||
node: {
|
||||
style: {
|
||||
size: (d) => {
|
||||
let size = 100 + ((d.rank as number) || 0) * 5;
|
||||
size = size > 300 ? 300 : size;
|
||||
return size;
|
||||
const size = 100 + ((d.rank as number) || 0) * 5;
|
||||
return Math.min(size, 300);
|
||||
},
|
||||
|
||||
labelText: (d) => d.id,
|
||||
labelFill: isDark ? 'rgba(255,255,255,1)' : 'rgba(0,0,0,1)',
|
||||
// labelPadding: 30,
|
||||
labelFontSize: 40,
|
||||
// labelOffsetX: 20,
|
||||
// labelOffsetX: 20,
|
||||
labelOffsetY: 20,
|
||||
labelPlacement: 'center',
|
||||
labelWordWrap: true,
|
||||
},
|
||||
palette: {
|
||||
type: 'group',
|
||||
field: (d) => {
|
||||
return d?.entity_type as string;
|
||||
},
|
||||
field: (d) => d?.entity_type as string,
|
||||
},
|
||||
},
|
||||
edge: {
|
||||
style: (model) => {
|
||||
const weight: number = Number(model?.weight) || 2;
|
||||
const lineWeight = weight * 4;
|
||||
|
||||
return {
|
||||
stroke: isDark ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.5)',
|
||||
lineDash: [10, 10],
|
||||
lineWidth: lineWeight > 8 ? 8 : lineWeight,
|
||||
lineWidth: Math.min(weight * 4, 8),
|
||||
};
|
||||
},
|
||||
},
|
||||
@ -149,12 +172,9 @@ const ForceGraph = ({ data, show }: IProps) => {
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={styles.forceContainer}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: show ? 'block' : 'none',
|
||||
}}
|
||||
className={cn(styles.forceContainer, 'size-full', !show && 'hidden')}
|
||||
aria-haspopup="true"
|
||||
aria-describedby={tooltipId}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
.forceContainer {
|
||||
:global(.tooltip) {
|
||||
border-radius: 10px !important;
|
||||
padding: 0.5rem 0.75rem !important;
|
||||
border-radius: 0.5rem !important;
|
||||
font-family: var(--font-sans) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { useFetchKnowledgeGraph } from '@/hooks/use-knowledge-request';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
import { LucideTrash2 } from 'lucide-react';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ForceGraph from './force-graph';
|
||||
@ -13,18 +14,23 @@ const KnowledgeGraph: React.FC = () => {
|
||||
const { handleDeleteKnowledgeGraph } = useDeleteKnowledgeGraph();
|
||||
|
||||
return (
|
||||
<section className={'w-full h-[90dvh] relative p-6'}>
|
||||
<Card
|
||||
as="article"
|
||||
className="relative me-5 mb-5 p-0 bg-transparent shadow-none overflow-hidden"
|
||||
>
|
||||
<ConfirmDeleteDialog onOk={handleDeleteKnowledgeGraph}>
|
||||
<Button
|
||||
variant="outline"
|
||||
size={'sm'}
|
||||
className="absolute right-0 top-0 z-50"
|
||||
size="sm"
|
||||
className="absolute right-5 top-5 z-50"
|
||||
>
|
||||
<Trash2 /> {t('common.delete')}
|
||||
<LucideTrash2 />
|
||||
{t('common.delete')}
|
||||
</Button>
|
||||
</ConfirmDeleteDialog>
|
||||
<ForceGraph data={data?.graph} show></ForceGraph>
|
||||
</section>
|
||||
|
||||
<ForceGraph data={data?.graph} show />
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -91,7 +91,7 @@ export default function Datasets() {
|
||||
value={filterValue}
|
||||
filters={owners}
|
||||
onChange={handleFilterSubmit}
|
||||
className="px-8"
|
||||
className="px-8 mb-4"
|
||||
icon={'datasets'}
|
||||
>
|
||||
<Button onClick={showModal}>
|
||||
|
||||
Reference in New Issue
Block a user