浏览代码

Feat: Modify the style of the dataset page #3221 (#7446)

### What problem does this PR solve?

Feat:  Modify the style of the dataset page #3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
tags/v0.19.0
balibabu 6 个月前
父节点
当前提交
cb37f00a8f
没有帐户链接到提交者的电子邮件

+ 4
- 1
web/src/app.tsx 查看文件

import weekday from 'dayjs/plugin/weekday'; import weekday from 'dayjs/plugin/weekday';
import React, { ReactNode, useEffect, useState } from 'react'; import React, { ReactNode, useEffect, useState } from 'react';
import { ThemeProvider, useTheme } from './components/theme-provider'; import { ThemeProvider, useTheme } from './components/theme-provider';
import { SidebarProvider } from './components/ui/sidebar';
import { TooltipProvider } from './components/ui/tooltip'; import { TooltipProvider } from './components/ui/tooltip';
import storage from './utils/authorization-util'; import storage from './utils/authorization-util';


}} }}
locale={locale} locale={locale}
> >
<App>{children}</App>
<SidebarProvider>
<App>{children}</App>
</SidebarProvider>
<Sonner position={'top-right'} expand richColors closeButton></Sonner> <Sonner position={'top-right'} expand richColors closeButton></Sonner>
<Toaster /> <Toaster />
</ConfigProvider> </ConfigProvider>

+ 17
- 0
web/src/components/icon-font.tsx 查看文件

import { FileIconMap } from '@/constants/file';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { getExtension } from '@/utils/document-util';


type IconFontType = { type IconFontType = {
name: string; name: string;
<use xlinkHref={`#icon-${name}`} /> <use xlinkHref={`#icon-${name}`} />
</svg> </svg>
); );

export function FileIcon({
name,
className,
type,
}: IconFontType & { type?: string }) {
const isFolder = type === 'folder';
return (
<span className={cn('size-4', className)}>
<IconFont
name={isFolder ? 'file' : FileIconMap[getExtension(name)]}
></IconFont>
</span>
);
}

+ 12
- 3
web/src/components/ragflow-avatar.tsx 查看文件

import { cn } from '@/lib/utils';
import * as AvatarPrimitive from '@radix-ui/react-avatar'; import * as AvatarPrimitive from '@radix-ui/react-avatar';
import { random } from 'lodash'; import { random } from 'lodash';
import { forwardRef } from 'react'; import { forwardRef } from 'react';
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> & { React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> & {
name?: string; name?: string;
avatar?: string; avatar?: string;
isPerson?: boolean;
} }
>(({ name, avatar, ...props }, ref) => {
>(({ name, avatar, isPerson = false, className, ...props }, ref) => {
const index = random(0, 3); const index = random(0, 3);
console.log('🚀 ~ index:', index); console.log('🚀 ~ index:', index);
const value = Colors[index]; const value = Colors[index];
return ( return (
<Avatar ref={ref} {...props}>
<Avatar
ref={ref}
{...props}
className={cn(className, { 'rounded-md': !isPerson })}
>
<AvatarImage src={avatar} /> <AvatarImage src={avatar} />
<AvatarFallback <AvatarFallback
className={`bg-gradient-to-b from-[${value.from}] to-[${value.to}]`}
className={cn(
`bg-gradient-to-b from-[${value.from}] to-[${value.to}]`,
{ 'rounded-md': !isPerson },
)}
> >
{name?.slice(0, 1)} {name?.slice(0, 1)}
</AvatarFallback> </AvatarFallback>

+ 2
- 2
web/src/components/ui/switch.tsx 查看文件

>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SwitchPrimitives.Root <SwitchPrimitives.Root
className={cn( className={cn(
'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-colors-background-core-standard data-[state=unchecked]:bg-colors-background-inverse-standard',
'peer inline-flex h-3.5 w-6 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-background-checked data-[state=unchecked]:bg-text-sub-title',
className, className,
)} )}
{...props} {...props}
> >
<SwitchPrimitives.Thumb <SwitchPrimitives.Thumb
className={cn( className={cn(
'pointer-events-none block h-5 w-5 rounded-full bg-colors-text-neutral-strong shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0',
'pointer-events-none block size-3 rounded-full bg-white shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-2 data-[state=unchecked]:translate-x-0',
)} )}
/> />
</SwitchPrimitives.Root> </SwitchPrimitives.Root>

+ 7
- 4
web/src/components/ui/table.tsx 查看文件

HTMLTableElement, HTMLTableElement,
React.HTMLAttributes<HTMLTableElement> React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<div className="relative w-full overflow-auto rounded-2xl bg-background-card">
<table <table
ref={ref} ref={ref}
className={cn('w-full caption-bottom text-sm', className)}
className={cn('w-full caption-bottom text-sm ', className)}
{...props} {...props}
/> />
</div> </div>
<th <th
ref={ref} ref={ref}
className={cn( className={cn(
'h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0',
'h-12 px-4 text-left align-middle font-normal text-text-sub-title [&:has([role=checkbox])]:pr-0',
className, className,
)} )}
{...props} {...props}
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<td <td
ref={ref} ref={ref}
className={cn('p-4 align-middle [&:has([role=checkbox])]:pr-0', className)}
className={cn(
'p-4 align-middle [&:has([role=checkbox])]:pr-0 text-text-title font-normal',
className,
)}
{...props} {...props}
/> />
)); ));

+ 15
- 0
web/src/constants/file.ts 查看文件

export const FileIconMap = {
doc: 'doc',
docx: 'doc',
pdf: 'pdf',
xls: 'excel',
xlsx: 'excel',
ppt: 'ppt',
pptx: 'ppt',
jpg: 'jpg',
jpeg: 'jpg',
png: 'png',
txt: 'text',
csv: 'pdf',
md: 'md',
};

+ 1
- 0
web/src/global.less 查看文件



.ant-app { .ant-app {
height: 100%; height: 100%;
width: 100%;
} }


/* Scroll bar stylings */ /* Scroll bar stylings */

+ 1
- 0
web/src/interfaces/database/knowledge.ts 查看文件

embd_id: string; embd_id: string;
nickname: string; nickname: string;
operator_permission: number; operator_permission: number;
size: number;
} }


export interface IKnowledgeResult { export interface IKnowledgeResult {

+ 44
- 52
web/src/pages/dataset/dataset/dataset-table.tsx 查看文件



return ( return (
<div className="w-full"> <div className="w-full">
<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>
);
})}
<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 className="relative">
{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}
className={cell.column.columnDef.meta?.cellClassName}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow> </TableRow>
))}
</TableHeader>
<TableBody className="relative">
{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}
className={cell.column.columnDef.meta?.cellClassName}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
<div className="flex items-center justify-end space-x-2 py-4"> <div className="flex items-center justify-end space-x-2 py-4">
<div className="flex-1 text-sm text-muted-foreground"> <div className="flex-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of{' '} {table.getFilteredSelectedRowModel().rows.length} of{' '}

+ 1
- 1
web/src/pages/dataset/dataset/index.tsx 查看文件

}); });


return ( return (
<section className="p-8">
<section className="p-5">
<ListFilterBar <ListFilterBar
title="Dataset" title="Dataset"
onSearchChange={handleInputChange} onSearchChange={handleInputChange}

+ 2
- 6
web/src/pages/dataset/dataset/use-dataset-table-columns.tsx 查看文件

import SvgIcon from '@/components/svg-icon';
import { FileIcon } from '@/components/icon-font';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { Switch } from '@/components/ui/switch'; import { Switch } from '@/components/ui/switch';
import { IDocumentInfo } from '@/interfaces/database/document'; import { IDocumentInfo } from '@/interfaces/database/document';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { formatDate } from '@/utils/date'; import { formatDate } from '@/utils/date';
import { getExtension } from '@/utils/document-util';
import { ColumnDef } from '@tanstack/table-core'; import { ColumnDef } from '@tanstack/table-core';
import { ArrowUpDown } from 'lucide-react'; import { ArrowUpDown } from 'lucide-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
row.original.kb_id, row.original.kb_id,
)} )}
> >
<SvgIcon
name={`file-icon/${getExtension(name)}`}
width={24}
></SvgIcon>
<FileIcon name={name}></FileIcon>
<span className={cn('truncate')}>{name}</span> <span className={cn('truncate')}>{name}</span>
</div> </div>
</TooltipTrigger> </TooltipTrigger>

+ 53
- 30
web/src/pages/dataset/sidebar/index.tsx 查看文件

import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useSecondPathName } from '@/hooks/route-hook'; import { useSecondPathName } from '@/hooks/route-hook';
import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-request'; import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-request';
import { cn } from '@/lib/utils';
import { cn, formatBytes } from '@/lib/utils';
import { Routes } from '@/routes'; import { Routes } from '@/routes';
import { formatDate } from '@/utils/date';
import { Banknote, LayoutGrid, User } from 'lucide-react';
import { formatPureDate } from '@/utils/date';
import { Banknote, Database, FileSearch2 } from 'lucide-react';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useHandleMenuClick } from './hooks'; import { useHandleMenuClick } from './hooks';


const items = [
{ icon: User, label: 'Dataset', key: Routes.DatasetBase },
{
icon: LayoutGrid,
label: 'Retrieval testing',
key: Routes.DatasetTesting,
},
{ icon: Banknote, label: 'Settings', key: Routes.DatasetSetting },
];

export function SideBar() { export function SideBar() {
const pathName = useSecondPathName(); const pathName = useSecondPathName();
const { handleMenuClick } = useHandleMenuClick(); const { handleMenuClick } = useHandleMenuClick();
const { data } = useFetchKnowledgeBaseConfiguration(); const { data } = useFetchKnowledgeBaseConfiguration();
const { t } = useTranslation();


return (
<aside className="w-60 relative border-r ">
<div className="p-6 space-y-2 border-b">
<Avatar className="size-20 rounded-lg">
<AvatarImage src={data.avatar} />
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
</Avatar>
const items = useMemo(() => {
return [
{
icon: Database,
label: t(`knowledgeDetails.dataset`),
key: Routes.DatasetBase,
},
{
icon: FileSearch2,
label: t(`knowledgeDetails.testing`),
key: Routes.DatasetTesting,
},
{
icon: Banknote,
label: t(`knowledgeDetails.configuration`),
key: Routes.DatasetSetting,
},
];
}, [t]);


<h3 className="text-lg font-semibold mb-2 line-clamp-1">{data.name}</h3>
<div className="text-sm opacity-80">
{data.doc_num} files | {data.chunk_num} chunks
</div>
<div className="text-sm opacity-80">
Created {formatDate(data.create_time)}
return (
<aside className="relative p-5 space-y-8">
<div className="flex gap-2.5 max-w-[200px] items-center">
<RAGFlowAvatar
avatar={data.avatar}
name={data.name}
className="size-16"
></RAGFlowAvatar>
<div className=" text-text-sub-title text-xs space-y-1">
<h3 className="text-lg font-semibold line-clamp-1 text-text-title">
{data.name}
</h3>
<div className="flex justify-between">
<span>{data.doc_num} files</span>
<span>{formatBytes(data.size)}</span>
</div>
<div>Created {formatPureDate(data.create_time)}</div>
</div> </div>
</div> </div>
<div className="mt-4">

<div className="w-[200px] flex flex-col gap-5">
{items.map((item, itemIdx) => { {items.map((item, itemIdx) => {
const active = '/' + pathName === item.key; const active = '/' + pathName === item.key;
return ( return (
<Button <Button
key={itemIdx} key={itemIdx}
variant={active ? 'secondary' : 'ghost'} variant={active ? 'secondary' : 'ghost'}
className={cn('w-full justify-start gap-2.5 p-6 relative')}
className={cn(
'w-full justify-start gap-2.5 px-3 relative h-10 text-text-sub-title-invert',
{
'bg-background-card': active,
'text-text-title': active,
},
)}
onClick={handleMenuClick(item.key)} onClick={handleMenuClick(item.key)}
> >
<item.icon className="w-6 h-6" />
<item.icon className="size-4" />
<span>{item.label}</span> <span>{item.label}</span>
</Button> </Button>
); );

+ 47
- 53
web/src/pages/files/files-table.tsx 查看文件

import { ArrowUpDown } from 'lucide-react'; import { ArrowUpDown } from 'lucide-react';
import * as React from 'react'; import * as React from 'react';


import { FileIcon } from '@/components/icon-font';
import { RenameDialog } from '@/components/rename-dialog'; import { RenameDialog } from '@/components/rename-dialog';
import SvgIcon from '@/components/svg-icon';
import { TableEmpty, TableSkeleton } from '@/components/table-skeleton'; import { TableEmpty, TableSkeleton } from '@/components/table-skeleton';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { formatFileSize } from '@/utils/common-util'; import { formatFileSize } from '@/utils/common-util';
import { formatDate } from '@/utils/date'; import { formatDate } from '@/utils/date';
import { getExtension } from '@/utils/document-util';
import { pick } from 'lodash'; import { pick } from 'lodash';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div className="flex gap-2"> <div className="flex gap-2">
<SvgIcon
name={`file-icon/${isFolder ? 'folder' : getExtension(name)}`}
width={24}
></SvgIcon>
<span className="size-4">
<FileIcon name={name} type={type}></FileIcon>
</span>
<span <span
className={cn('truncate', { ['cursor-pointer']: isFolder })} className={cn('truncate', { ['cursor-pointer']: isFolder })}
onClick={handleNameClick} onClick={handleNameClick}


return ( return (
<div className="w-full"> <div className="w-full">
<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>
);
})}
<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>
{loading ? (
<TableSkeleton columnsLength={columns.length}></TableSkeleton>
) : 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}
className={cell.column.columnDef.meta?.cellClassName}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow> </TableRow>
))}
</TableHeader>
<TableBody>
{loading ? (
<TableSkeleton columnsLength={columns.length}></TableSkeleton>
) : 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}
className={cell.column.columnDef.meta?.cellClassName}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableEmpty columnsLength={columns.length}></TableEmpty>
)}
</TableBody>
</Table>
</div>
))
) : (
<TableEmpty columnsLength={columns.length}></TableEmpty>
)}
</TableBody>
</Table>

<div className="flex items-center justify-end space-x-2 py-4"> <div className="flex items-center justify-end space-x-2 py-4">
<div className="flex-1 text-sm text-muted-foreground"> <div className="flex-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of {total} row(s) {table.getFilteredSelectedRowModel().rows.length} of {total} row(s)

+ 1
- 1
web/src/pages/files/index.tsx 查看文件

> >
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant={'tertiary'} size={'sm'}>
<Button>
<Upload /> <Upload />
{t('knowledgeDetails.addFile')} {t('knowledgeDetails.addFile')}
</Button> </Button>

+ 1
- 0
web/tailwind.config.js 查看文件

'text-title-invert': 'var(--text-title-invert)', 'text-title-invert': 'var(--text-title-invert)',
'background-header-bar': 'var(--background-header-bar)', 'background-header-bar': 'var(--background-header-bar)',
'background-card': 'var(--background-card)', 'background-card': 'var(--background-card)',
'background-checked': 'var(--background-checked)',


primary: { primary: {
DEFAULT: 'hsl(var(--primary))', DEFAULT: 'hsl(var(--primary))',

+ 3
- 0
web/tailwind.css 查看文件

--background-header-bar: rgba(11, 11, 12, 1); --background-header-bar: rgba(11, 11, 12, 1);
--text-title-invert: rgba(255, 255, 255, 1); --text-title-invert: rgba(255, 255, 255, 1);
--background-card: rgba(22, 22, 24, 0.05); --background-card: rgba(22, 22, 24, 0.05);

--background-checked: rgba(76, 164, 231, 1);
} }


.dark { .dark {
--text-title-invert: rgba(22, 22, 24, 1); --text-title-invert: rgba(22, 22, 24, 1);


--background-card: rgba(255, 255, 255, 0.05); --background-card: rgba(255, 255, 255, 0.05);
--background-checked: rgba(76, 164, 231, 1);
} }
} }



正在加载...
取消
保存