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)
This commit is contained in:
balibabu 2025-05-02 21:27:21 +08:00 committed by GitHub
parent fc379e90d1
commit cb37f00a8f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 211 additions and 153 deletions

View File

@ -19,6 +19,7 @@ import weekYear from 'dayjs/plugin/weekYear';
import weekday from 'dayjs/plugin/weekday';
import React, { ReactNode, useEffect, useState } from 'react';
import { ThemeProvider, useTheme } from './components/theme-provider';
import { SidebarProvider } from './components/ui/sidebar';
import { TooltipProvider } from './components/ui/tooltip';
import storage from './utils/authorization-util';
@ -68,7 +69,9 @@ function Root({ children }: React.PropsWithChildren) {
}}
locale={locale}
>
<App>{children}</App>
<SidebarProvider>
<App>{children}</App>
</SidebarProvider>
<Sonner position={'top-right'} expand richColors closeButton></Sonner>
<Toaster />
</ConfigProvider>

View File

@ -1,4 +1,6 @@
import { FileIconMap } from '@/constants/file';
import { cn } from '@/lib/utils';
import { getExtension } from '@/utils/document-util';
type IconFontType = {
name: string;
@ -10,3 +12,18 @@ export const IconFont = ({ name, className }: IconFontType) => (
<use xlinkHref={`#icon-${name}`} />
</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>
);
}

View File

@ -1,3 +1,4 @@
import { cn } from '@/lib/utils';
import * as AvatarPrimitive from '@radix-ui/react-avatar';
import { random } from 'lodash';
import { forwardRef } from 'react';
@ -15,16 +16,24 @@ export const RAGFlowAvatar = forwardRef<
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> & {
name?: string;
avatar?: string;
isPerson?: boolean;
}
>(({ name, avatar, ...props }, ref) => {
>(({ name, avatar, isPerson = false, className, ...props }, ref) => {
const index = random(0, 3);
console.log('🚀 ~ index:', index);
const value = Colors[index];
return (
<Avatar ref={ref} {...props}>
<Avatar
ref={ref}
{...props}
className={cn(className, { 'rounded-md': !isPerson })}
>
<AvatarImage src={avatar} />
<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)}
</AvatarFallback>

View File

@ -11,7 +11,7 @@ const Switch = React.forwardRef<
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
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,
)}
{...props}
@ -19,7 +19,7 @@ const Switch = React.forwardRef<
>
<SwitchPrimitives.Thumb
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>

View File

@ -6,10 +6,10 @@ const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<div className="relative w-full overflow-auto rounded-2xl bg-background-card">
<table
ref={ref}
className={cn('w-full caption-bottom text-sm', className)}
className={cn('w-full caption-bottom text-sm ', className)}
{...props}
/>
</div>
@ -73,7 +73,7 @@ const TableHead = React.forwardRef<
<th
ref={ref}
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,
)}
{...props}
@ -87,7 +87,10 @@ const TableCell = React.forwardRef<
>(({ className, ...props }, ref) => (
<td
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}
/>
));

15
web/src/constants/file.ts Normal file
View File

@ -0,0 +1,15 @@
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',
};

View File

@ -17,6 +17,7 @@ body {
.ant-app {
height: 100%;
width: 100%;
}
/* Scroll bar stylings */

View File

@ -25,6 +25,7 @@ export interface IKnowledge {
embd_id: string;
nickname: string;
operator_permission: number;
size: number;
}
export interface IKnowledgeResult {

View File

@ -119,59 +119,51 @@ export function DatasetTable({
return (
<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>
))}
</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-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of{' '}

View File

@ -60,7 +60,7 @@ export default function Dataset() {
});
return (
<section className="p-8">
<section className="p-5">
<ListFilterBar
title="Dataset"
onSearchChange={handleInputChange}

View File

@ -1,4 +1,4 @@
import SvgIcon from '@/components/svg-icon';
import { FileIcon } from '@/components/icon-font';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Switch } from '@/components/ui/switch';
@ -12,7 +12,6 @@ import { useSetDocumentStatus } from '@/hooks/use-document-request';
import { IDocumentInfo } from '@/interfaces/database/document';
import { cn } from '@/lib/utils';
import { formatDate } from '@/utils/date';
import { getExtension } from '@/utils/document-util';
import { ColumnDef } from '@tanstack/table-core';
import { ArrowUpDown } from 'lucide-react';
import { useTranslation } from 'react-i18next';
@ -88,10 +87,7 @@ export function useDatasetTableColumns({
row.original.kb_id,
)}
>
<SvgIcon
name={`file-icon/${getExtension(name)}`}
width={24}
></SvgIcon>
<FileIcon name={name}></FileIcon>
<span className={cn('truncate')}>{name}</span>
</div>
</TooltipTrigger>

View File

@ -1,55 +1,78 @@
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
import { Button } from '@/components/ui/button';
import { useSecondPathName } from '@/hooks/route-hook';
import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-request';
import { cn } from '@/lib/utils';
import { cn, formatBytes } from '@/lib/utils';
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';
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() {
const pathName = useSecondPathName();
const { handleMenuClick } = useHandleMenuClick();
const { data } = useFetchKnowledgeBaseConfiguration();
const { t } = useTranslation();
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]);
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>
<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)}
<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 className="mt-4">
<div className="w-[200px] flex flex-col gap-5">
{items.map((item, itemIdx) => {
const active = '/' + pathName === item.key;
return (
<Button
key={itemIdx}
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)}
>
<item.icon className="w-6 h-6" />
<item.icon className="size-4" />
<span>{item.label}</span>
</Button>
);

View File

@ -14,8 +14,8 @@ import {
import { ArrowUpDown } from 'lucide-react';
import * as React from 'react';
import { FileIcon } from '@/components/icon-font';
import { RenameDialog } from '@/components/rename-dialog';
import SvgIcon from '@/components/svg-icon';
import { TableEmpty, TableSkeleton } from '@/components/table-skeleton';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
@ -39,7 +39,6 @@ import { IFile } from '@/interfaces/database/file-manager';
import { cn } from '@/lib/utils';
import { formatFileSize } from '@/utils/common-util';
import { formatDate } from '@/utils/date';
import { getExtension } from '@/utils/document-util';
import { pick } from 'lodash';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@ -148,10 +147,9 @@ export function FilesTable({
<Tooltip>
<TooltipTrigger asChild>
<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
className={cn('truncate', { ['cursor-pointer']: isFolder })}
onClick={handleNameClick}
@ -262,54 +260,50 @@ export function FilesTable({
return (
<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>
))}
</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-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of {total} row(s)

View File

@ -88,7 +88,7 @@ export default function Files() {
>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant={'tertiary'} size={'sm'}>
<Button>
<Upload />
{t('knowledgeDetails.addFile')}
</Button>

View File

@ -50,6 +50,7 @@ module.exports = {
'text-title-invert': 'var(--text-title-invert)',
'background-header-bar': 'var(--background-header-bar)',
'background-card': 'var(--background-card)',
'background-checked': 'var(--background-checked)',
primary: {
DEFAULT: 'hsl(var(--primary))',

View File

@ -84,6 +84,8 @@
--background-header-bar: rgba(11, 11, 12, 1);
--text-title-invert: rgba(255, 255, 255, 1);
--background-card: rgba(22, 22, 24, 0.05);
--background-checked: rgba(76, 164, 231, 1);
}
.dark {
@ -189,6 +191,7 @@
--text-title-invert: rgba(22, 22, 24, 1);
--background-card: rgba(255, 255, 255, 0.05);
--background-checked: rgba(76, 164, 231, 1);
}
}