mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-05-03 00:37:48 +08:00
refactor(ui): adjust dataset page styles (#13452)
### What problem does this PR solve? - Adjust UI styles in **Dataset** pages. - Adjust several shared components styles - Modify files and directory structure in `src/layouts` ### Type of change - [x] Refactoring
This commit is contained in:
@ -1,8 +1,7 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { BrushCleaning } from 'lucide-react';
|
||||
import { ReactNode, useCallback } from 'react';
|
||||
import { ReactNode, useId } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ConfirmDeleteDialog,
|
||||
@ -30,58 +29,69 @@ export function BulkOperateBar({
|
||||
className,
|
||||
unit,
|
||||
}: BulkOperateBarProps) {
|
||||
const isDeleteItem = useCallback((id: string) => {
|
||||
return id === 'delete';
|
||||
}, []);
|
||||
const { t } = useTranslation();
|
||||
const ariaDescriptionId = useId();
|
||||
|
||||
return (
|
||||
<Card className={cn('mb-4', className)}>
|
||||
<CardContent className="p-1 pl-5 flex items-center gap-6">
|
||||
<section className="text-text-sub-title-invert flex items-center gap-2">
|
||||
<span>
|
||||
{t('common.selected')}: {count}{' '}
|
||||
{unit ?? t('knowledgeDetails.files')}
|
||||
</span>
|
||||
<BrushCleaning className="size-3" />
|
||||
</section>
|
||||
<Separator orientation={'vertical'} className="h-3"></Separator>
|
||||
<Card
|
||||
className={className}
|
||||
role="menu"
|
||||
aria-label={t('common.bulkOperate')}
|
||||
aria-describedby={ariaDescriptionId}
|
||||
>
|
||||
<CardContent className="ps-5 pe-1 py-1 flex items-center gap-6">
|
||||
<p
|
||||
id={ariaDescriptionId}
|
||||
className="text-sm text-text-secondary flex items-center gap-2"
|
||||
>
|
||||
{t('common.selected')}: {count} {unit ?? t('knowledgeDetails.files')}
|
||||
<BrushCleaning className="size-[1em]" />
|
||||
</p>
|
||||
|
||||
<Separator orientation={'vertical'} className="h-[1em]"></Separator>
|
||||
|
||||
<ul className="flex gap-2">
|
||||
{list.map((x) => (
|
||||
<li
|
||||
key={x.id}
|
||||
className={cn({ ['text-state-error']: isDeleteItem(x.id) })}
|
||||
>
|
||||
<ConfirmDeleteDialog
|
||||
hidden={!isDeleteItem(x.id)}
|
||||
onOk={x.onClick}
|
||||
title={
|
||||
unit
|
||||
? t('common.delete') + ' ' + unit
|
||||
: t('deleteModal.delFiles')
|
||||
}
|
||||
content={{
|
||||
title: t('common.deleteThem'),
|
||||
node: (
|
||||
<ConfirmDeleteDialogNode
|
||||
name={`${unit ? t('common.selected') + ' ' + count + ' ' + unit : t('deleteModal.delFilesContent', { count })}`}
|
||||
></ConfirmDeleteDialogNode>
|
||||
),
|
||||
}}
|
||||
{list.map((x) => {
|
||||
const isDeleteItem = x.id === 'delete';
|
||||
|
||||
const buttonEl = (
|
||||
<Button
|
||||
variant={isDeleteItem ? 'danger' : 'outline'}
|
||||
onClick={isDeleteItem ? () => {} : x.onClick}
|
||||
role="menuitem"
|
||||
>
|
||||
<Button
|
||||
variant={!isDeleteItem(x.id) ? 'ghost' : 'delete'}
|
||||
onClick={isDeleteItem(x.id) ? () => {} : x.onClick}
|
||||
className={cn({
|
||||
['text-state-error border border-state-error bg-state-error/5']:
|
||||
isDeleteItem(x.id),
|
||||
})}
|
||||
>
|
||||
{x.icon} {x.label}
|
||||
</Button>
|
||||
</ConfirmDeleteDialog>
|
||||
</li>
|
||||
))}
|
||||
{x.icon} {x.label}
|
||||
</Button>
|
||||
);
|
||||
|
||||
return (
|
||||
<li key={x.id}>
|
||||
{isDeleteItem ? (
|
||||
<ConfirmDeleteDialog
|
||||
key="deleteModal"
|
||||
onOk={x.onClick}
|
||||
title={
|
||||
unit
|
||||
? t('common.delete') + ' ' + unit
|
||||
: t('deleteModal.delFiles')
|
||||
}
|
||||
content={{
|
||||
title: t('common.deleteThem'),
|
||||
node: (
|
||||
<ConfirmDeleteDialogNode
|
||||
name={`${unit ? t('common.selected') + ' ' + count + ' ' + unit : t('deleteModal.delFilesContent', { count })}`}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{buttonEl}
|
||||
</ConfirmDeleteDialog>
|
||||
) : (
|
||||
buttonEl
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Plus, X } from 'lucide-react';
|
||||
import { LucidePlus, LucideTrash2 } from 'lucide-react';
|
||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Separator } from '../ui/separator';
|
||||
@ -51,7 +51,9 @@ export function DynamicPageRange() {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Separator className="w-3 "></Separator>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`parser_config.pages.${index}.to`}
|
||||
@ -70,19 +72,27 @@ export function DynamicPageRange() {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button variant={'ghost'} onClick={() => remove(index)}>
|
||||
<X />
|
||||
|
||||
<Button
|
||||
className="ml-4"
|
||||
size="icon"
|
||||
variant="outline"
|
||||
onClick={() => remove(index)}
|
||||
>
|
||||
<LucideTrash2 />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
<Button
|
||||
onClick={() => append({ from: 1, to: 100 })}
|
||||
className="mt-4 border-dashed w-full"
|
||||
variant={'outline'}
|
||||
block
|
||||
className="mt-4"
|
||||
variant="dashed"
|
||||
type="button"
|
||||
>
|
||||
<Plus />
|
||||
<LucidePlus />
|
||||
{t('knowledgeDetails.addPage')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -42,7 +42,6 @@ import { DataFlowSelect } from '../data-pipeline-select';
|
||||
import { DelimiterFormField } from '../delimiter-form-field';
|
||||
import { EntityTypesFormField } from '../entity-types-form-field';
|
||||
import { ExcelToHtmlFormField } from '../excel-to-html-form-field';
|
||||
import { FormContainer } from '../form-container';
|
||||
import { LayoutRecognizeFormField } from '../layout-recognize-form-field';
|
||||
import { MaxTokenNumberFormField } from '../max-token-number-from-field';
|
||||
import { MinerUOptionsFormField } from '../mineru-options-form-field';
|
||||
@ -293,22 +292,16 @@ export function ChunkMethodDialog({
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('knowledgeDetails.chunkMethod')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6 max-h-[70vh] overflow-auto"
|
||||
className="space-y-6 max-h-[70vh] overflow-auto -mx-6 px-10 py-5"
|
||||
id={FormId}
|
||||
>
|
||||
<FormContainer>
|
||||
<div className="space-y-6">
|
||||
<ParseTypeItem />
|
||||
{parseType === 1 && <ChunkMethodItem></ChunkMethodItem>}
|
||||
{parseType === 2 && (
|
||||
<DataFlowSelect
|
||||
isMult={false}
|
||||
// toDataPipeline={navigateToAgents}
|
||||
formFieldName="pipeline_id"
|
||||
/>
|
||||
)}
|
||||
{parseType === 1 && <ChunkMethodItem />}
|
||||
|
||||
{/* <FormField
|
||||
control={form.control}
|
||||
@ -326,9 +319,9 @@ export function ChunkMethodDialog({
|
||||
</FormItem>
|
||||
)}
|
||||
/> */}
|
||||
{showPages && parseType === 1 && (
|
||||
<DynamicPageRange></DynamicPageRange>
|
||||
)}
|
||||
|
||||
{showPages && parseType === 1 && <DynamicPageRange />}
|
||||
|
||||
{showPages && parseType === 1 && layoutRecognize && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
@ -341,31 +334,25 @@ export function ChunkMethodDialog({
|
||||
{t('knowledgeDetails.taskPageSize')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
type={'number'}
|
||||
min={1}
|
||||
max={128}
|
||||
></Input>
|
||||
<Input {...field} type={'number'} min={1} max={128} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</FormContainer>
|
||||
</div>
|
||||
|
||||
{parseType === 1 && (
|
||||
<>
|
||||
<FormContainer
|
||||
show={showOne || showMaxTokenNumber}
|
||||
className="space-y-3"
|
||||
>
|
||||
<div className="space-y-6 border-t-0.5 border-border-button pt-6 empty:hidden">
|
||||
{showOne && (
|
||||
<>
|
||||
<LayoutRecognizeFormField showMineruOptions={false} />
|
||||
{isMineruSelected && <MinerUOptionsFormField />}
|
||||
</>
|
||||
)}
|
||||
|
||||
{showMaxTokenNumber && (
|
||||
<>
|
||||
<MaxTokenNumberFormField
|
||||
@ -374,26 +361,21 @@ export function ChunkMethodDialog({
|
||||
? 8192 * 2
|
||||
: 2048
|
||||
}
|
||||
></MaxTokenNumberFormField>
|
||||
<DelimiterFormField></DelimiterFormField>
|
||||
/>
|
||||
<DelimiterFormField />
|
||||
<ChildrenDelimiterForm />
|
||||
</>
|
||||
)}
|
||||
</FormContainer>
|
||||
<FormContainer
|
||||
show={
|
||||
isMineruSelected ||
|
||||
showAutoKeywords(selectedTag) ||
|
||||
showExcelToHtml
|
||||
}
|
||||
className="space-y-3"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6 border-t-0.5 border-border-button pt-6 empty:hidden">
|
||||
{selectedTag === DocumentParserType.Naive && (
|
||||
<>
|
||||
<EnableTocToggle />
|
||||
<ImageContextWindow />
|
||||
</>
|
||||
)}
|
||||
|
||||
{showAutoKeywords(selectedTag) && (
|
||||
<>
|
||||
<AutoMetadata
|
||||
@ -404,28 +386,39 @@ export function ChunkMethodDialog({
|
||||
<AutoQuestionsFormField></AutoQuestionsFormField>
|
||||
</>
|
||||
)}
|
||||
|
||||
{showExcelToHtml && (
|
||||
<ExcelToHtmlFormField></ExcelToHtmlFormField>
|
||||
)}
|
||||
</FormContainer>
|
||||
</div>
|
||||
{/* {showRaptorParseConfiguration(
|
||||
selectedTag as DocumentParserType,
|
||||
) && (
|
||||
<FormContainer>
|
||||
<RaptorFormFields></RaptorFormFields>
|
||||
</FormContainer>
|
||||
)} */}
|
||||
{/* {showGraphRagItems(selectedTag as DocumentParserType) &&
|
||||
useGraphRag && (
|
||||
selectedTag as DocumentParserType,
|
||||
) && (
|
||||
<FormContainer>
|
||||
<UseGraphRagFormField></UseGraphRagFormField>
|
||||
<RaptorFormFields></RaptorFormFields>
|
||||
</FormContainer>
|
||||
)} */}
|
||||
{showEntityTypes && (
|
||||
<EntityTypesFormField></EntityTypesFormField>
|
||||
)}
|
||||
{/* {showGraphRagItems(selectedTag as DocumentParserType) &&
|
||||
useGraphRag && (
|
||||
<FormContainer>
|
||||
<UseGraphRagFormField></UseGraphRagFormField>
|
||||
</FormContainer>
|
||||
)} */}
|
||||
<div className="space-y-6 border-t-0.5 border-border-button pt-6 empty:hidden">
|
||||
{showEntityTypes && <EntityTypesFormField />}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="space-y-6 empty:hidden">
|
||||
{parseType === 2 && (
|
||||
<DataFlowSelect
|
||||
isMult={false}
|
||||
// toDataPipeline={navigateToAgents}
|
||||
formFieldName="pipeline_id"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
<DialogFooter>
|
||||
|
||||
@ -53,22 +53,24 @@ const Empty = (props: EmptyProps) => {
|
||||
export default Empty;
|
||||
|
||||
export const EmptyCard = (props: EmptyCardProps) => {
|
||||
const { icon, className, children, title, description, style } = props;
|
||||
const { icon, className, children, title, description, style, ...restProps } =
|
||||
props;
|
||||
return (
|
||||
<div
|
||||
<article
|
||||
className={cn(
|
||||
'flex flex-col gap-3 items-start justify-start border border-dashed border-border-button rounded-md p-5 w-fit',
|
||||
className,
|
||||
)}
|
||||
style={style}
|
||||
{...restProps}
|
||||
>
|
||||
{icon}
|
||||
{title && <div className="text-text-primary text-base">{title}</div>}
|
||||
{title && <div className="text-text-primary text-sm">{title}</div>}
|
||||
{description && (
|
||||
<div className="text-text-secondary text-sm">{description}</div>
|
||||
<p className="text-text-secondary text-sm">{description}</p>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
};
|
||||
|
||||
@ -104,11 +106,14 @@ export const EmptyAppCard = (props: {
|
||||
break;
|
||||
}
|
||||
return (
|
||||
<div onClick={isSearch ? undefined : props.onClick} data-testid={testId}>
|
||||
<div>
|
||||
<EmptyCard
|
||||
onClick={isSearch ? undefined : props.onClick}
|
||||
data-testid={testId}
|
||||
tabIndex={isSearch ? undefined : 0}
|
||||
icon={showIcon ? cardData.icon : undefined}
|
||||
title={isSearch ? notFound : title}
|
||||
className={cn('cursor-pointer ', className)}
|
||||
className={cn('cursor-pointer', className)}
|
||||
style={style}
|
||||
// description={EmptyCardData[type].description}
|
||||
>
|
||||
|
||||
@ -15,4 +15,4 @@ export type EmptyCardProps = {
|
||||
title?: string;
|
||||
description?: string;
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
} & Omit<React.HTMLAttributes<HTMLDivElement>, 'title'>;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
@ -26,48 +26,61 @@ export function HomeCard({
|
||||
}: IProps) {
|
||||
return (
|
||||
<Card
|
||||
as="article"
|
||||
data-testid={testId}
|
||||
data-agent-name={data.name}
|
||||
onClick={() => {
|
||||
// navigateToSearch(data?.id);
|
||||
onClick?.();
|
||||
}}
|
||||
tabIndex={0}
|
||||
className="px-2.5 py-4 flex gap-2 items-start group h-full w-full hover:shadow-md"
|
||||
>
|
||||
<CardContent className="p-4 flex gap-2 items-start group h-full w-full hover:shadow-md">
|
||||
<div className="flex justify-between mb-4">
|
||||
<RAGFlowAvatar
|
||||
className="w-[32px] h-[32px]"
|
||||
avatar={data.avatar}
|
||||
name={data.name}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col justify-between gap-1 flex-1 h-full w-[calc(100%-50px)]">
|
||||
<section className="flex justify-between">
|
||||
<section className="flex flex-1 min-w-0 gap-1 items-center">
|
||||
<div
|
||||
className="text-base font-bold leading-snug truncate"
|
||||
data-testid="agent-name"
|
||||
>
|
||||
{data.name}
|
||||
</div>
|
||||
{icon}
|
||||
</section>
|
||||
{moreDropdown}
|
||||
</section>
|
||||
<div>
|
||||
<RAGFlowAvatar
|
||||
className="w-[32px] h-[32px]"
|
||||
avatar={data.avatar}
|
||||
name={data.name}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<section className="flex flex-col gap-1 mt-1">
|
||||
<div className="whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
{data.description}
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<p className="text-sm opacity-80 whitespace-nowrap">
|
||||
{formatDate(data.update_time)}
|
||||
</p>
|
||||
{sharedBadge}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</CardContent>
|
||||
<div className="flex-1 w-0">
|
||||
<CardHeader
|
||||
as="header"
|
||||
className="p-0 flex-1 flex flex-row items-center gap-2 space-y-0"
|
||||
>
|
||||
<CardTitle className="flex-1 inline-flex w-0 me-auto">
|
||||
<h3
|
||||
className="flex-1 truncate text-base font-bold leading-snug"
|
||||
data-testid="agent-name"
|
||||
>
|
||||
{data.name}
|
||||
</h3>
|
||||
|
||||
{icon}
|
||||
</CardTitle>
|
||||
|
||||
<div>{moreDropdown}</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="p-0">
|
||||
<div className="flex flex-col justify-between gap-1 flex-1 h-full w-[calc(100%-50px)]">
|
||||
<section className="flex justify-between"></section>
|
||||
|
||||
<section className="flex flex-col gap-1 mt-1">
|
||||
<div className="whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
{data.description}
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<p className="text-sm opacity-80 whitespace-nowrap">
|
||||
{formatDate(data.update_time)}
|
||||
</p>
|
||||
{sharedBadge}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</CardContent>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@ -91,7 +91,7 @@ export default function ListFilterBar({
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<div className={cn('flex justify-between mb-5 items-center', className)}>
|
||||
<div className={cn('flex justify-between items-center', className)}>
|
||||
<div className="text-2xl font-semibold flex items-center gap-2.5">
|
||||
{typeof icon === 'string' ? (
|
||||
// <IconFont name={icon} className="size-6"></IconFont>
|
||||
@ -101,7 +101,8 @@ export default function ListFilterBar({
|
||||
)}
|
||||
{leftPanel || title}
|
||||
</div>
|
||||
<div className="flex gap-5 items-center">
|
||||
|
||||
<div className="flex gap-4 items-center" role="toolbar">
|
||||
{preChildren}
|
||||
{showFilter && (
|
||||
<FilterPopover
|
||||
@ -119,6 +120,7 @@ export default function ListFilterBar({
|
||||
value={searchString}
|
||||
onChange={onSearchChange}
|
||||
className="w-32"
|
||||
role="searchbox"
|
||||
></SearchInput>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@ -9,10 +9,10 @@ export const MoreButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
<Button
|
||||
ref={ref}
|
||||
variant="ghost"
|
||||
size={size || 'icon'}
|
||||
size={size || 'icon-xs'}
|
||||
className={cn(
|
||||
'invisible size-3.5 bg-transparent group-hover:bg-transparent',
|
||||
'group-focus-within:visible group-hover:visible aria-expanded:visible',
|
||||
'opacity-0 size-3.5 transition-all bg-transparent group-hover:bg-transparent',
|
||||
'group-focus-within:opacity-100 group-hover:opacity-100 aria-expanded:opacity-100',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
||||
import { forwardRef, memo, useEffect, useRef, useState } from 'react';
|
||||
import { forwardRef, memo, useMemo } from 'react';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar';
|
||||
|
||||
const PREDEFINED_COLORS = [
|
||||
@ -26,6 +26,15 @@ const getStringHash = (str: string): number => {
|
||||
return Math.abs(hash);
|
||||
};
|
||||
|
||||
const getInitials = (name?: string) => {
|
||||
if (typeof name !== 'string' || !name) return '';
|
||||
const parts = name?.trim().split(/\s+/);
|
||||
if (parts.length === 1) {
|
||||
return parts[0][0].toUpperCase();
|
||||
}
|
||||
return parts[0][0].toUpperCase();
|
||||
};
|
||||
|
||||
const getColorForName = (name: string): { from: string; to: string } => {
|
||||
const hash = getStringHash(name);
|
||||
const index = hash % PREDEFINED_COLORS.length;
|
||||
@ -42,50 +51,15 @@ export const RAGFlowAvatar = memo(
|
||||
}
|
||||
>(({ name, avatar, isPerson = false, className, ...props }, ref) => {
|
||||
// Generate initial letter logic
|
||||
const getInitials = (name?: string) => {
|
||||
if (typeof name !== 'string' || !name) return '';
|
||||
const parts = name?.trim().split(/\s+/);
|
||||
if (parts.length === 1) {
|
||||
return parts[0][0].toUpperCase();
|
||||
}
|
||||
return parts[0][0].toUpperCase();
|
||||
};
|
||||
|
||||
const initials = getInitials(name);
|
||||
const { from, to } = name
|
||||
? getColorForName(name)
|
||||
: { from: 'hsl(0, 0%, 30%)', to: 'hsl(0, 0%, 80%)' };
|
||||
|
||||
const fallbackRef = useRef<HTMLElement>(null);
|
||||
const [fontSize, setFontSize] = useState('0.875rem');
|
||||
|
||||
// Calculate font size
|
||||
const calculateFontSize = () => {
|
||||
if (fallbackRef.current) {
|
||||
const containerWidth = fallbackRef.current.offsetWidth;
|
||||
const newSize = containerWidth * 0.6;
|
||||
setFontSize(`${newSize}px`);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
calculateFontSize();
|
||||
|
||||
if (fallbackRef.current) {
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
calculateFontSize();
|
||||
});
|
||||
|
||||
resizeObserver.observe(fallbackRef.current);
|
||||
|
||||
return () => {
|
||||
if (fallbackRef.current) {
|
||||
resizeObserver.unobserve(fallbackRef.current);
|
||||
}
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
const { initials, from, to } = useMemo(
|
||||
() => ({
|
||||
initials: getInitials(name),
|
||||
from: 'hsl(0, 0%, 30%)',
|
||||
to: 'hsl(0, 0%, 80%)',
|
||||
...(name ? getColorForName(name) : {}),
|
||||
}),
|
||||
[name],
|
||||
);
|
||||
|
||||
return (
|
||||
<Avatar
|
||||
@ -95,22 +69,27 @@ export const RAGFlowAvatar = memo(
|
||||
>
|
||||
<AvatarImage src={avatar} />
|
||||
<AvatarFallback
|
||||
ref={(node) => {
|
||||
fallbackRef.current = node;
|
||||
calculateFontSize();
|
||||
}}
|
||||
className={cn(
|
||||
'bg-gradient-to-b',
|
||||
'flex items-center justify-center',
|
||||
'text-white ',
|
||||
{ 'rounded-md': !isPerson },
|
||||
)}
|
||||
className="flex items-center justify-center bg-gradient-to-b text-white"
|
||||
style={{
|
||||
backgroundImage: `linear-gradient(to bottom, ${from}, ${to})`,
|
||||
fontSize: fontSize,
|
||||
}}
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{initials}
|
||||
<svg
|
||||
className="size-full block text-current select-none"
|
||||
viewBox={`${-(50 + 22.5 * (initials.length - 1))} -50 ${100 + 45 * (initials.length - 1)} 100`}
|
||||
preserveAspectRatio="xMinYMid meet"
|
||||
>
|
||||
<text
|
||||
fontSize={55}
|
||||
fill="currentColor"
|
||||
textAnchor="middle"
|
||||
dominantBaseline="central"
|
||||
>
|
||||
{initials}
|
||||
</text>
|
||||
</svg>
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
);
|
||||
|
||||
@ -143,8 +143,8 @@ export const HomeIcon = ({
|
||||
imgClass,
|
||||
}: {
|
||||
name: string;
|
||||
height?: string;
|
||||
width?: string;
|
||||
height?: string | number;
|
||||
width?: string | number;
|
||||
imgClass?: string;
|
||||
}) => {
|
||||
const isDark = useIsDarkTheme();
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
import { ThemeEnum } from '@/constants/common';
|
||||
import { Moon, Sun } from 'lucide-react';
|
||||
import { FC, useCallback } from 'react';
|
||||
import { useIsDarkTheme, useTheme } from './theme-provider';
|
||||
import { Button } from './ui/button';
|
||||
|
||||
const ThemeToggle: FC = () => {
|
||||
const { setTheme } = useTheme();
|
||||
const isDarkTheme = useIsDarkTheme();
|
||||
const handleThemeChange = useCallback(
|
||||
(checked: boolean) => {
|
||||
setTheme(checked ? ThemeEnum.Dark : ThemeEnum.Light);
|
||||
},
|
||||
[setTheme],
|
||||
);
|
||||
return (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => handleThemeChange(!isDarkTheme)}
|
||||
className="relative inline-flex h-6 w-14 items-center rounded-full transition-colors p-0.5 border-none focus:border-none bg-bg-card hover:bg-bg-card"
|
||||
// aria-label={isDarkTheme ? 'Switch to light mode' : 'Switch to dark mode'}
|
||||
>
|
||||
<div className="inline-flex h-full w-full items-center">
|
||||
<div
|
||||
className={`inline-flex transform items-center justify-center rounded-full transition-transform ${
|
||||
isDarkTheme
|
||||
? ' text-text-disabled h-4 w-5'
|
||||
: ' text-text-primary bg-bg-base h-full w-8 flex-1'
|
||||
}`}
|
||||
>
|
||||
<Sun />
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`inline-flex transform items-center justify-center rounded-full transition-transform ${
|
||||
isDarkTheme
|
||||
? ' text-text-primary bg-bg-base h-full w-8 flex-1'
|
||||
: 'text-text-disabled h-4 w-5'
|
||||
}`}
|
||||
>
|
||||
<Moon />
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeToggle;
|
||||
@ -39,7 +39,7 @@ const AvatarFallback = React.forwardRef<
|
||||
<AvatarPrimitive.Fallback
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex h-full w-full items-center justify-center rounded-full bg-bg-member',
|
||||
'flex h-full w-full items-center justify-center bg-bg-member',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@ -8,7 +8,7 @@ import { Link, LinkProps } from 'react-router';
|
||||
|
||||
const buttonVariants = cva(
|
||||
cn(
|
||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-0',
|
||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm transition-colors outline-0',
|
||||
'disabled:pointer-events-none disabled:opacity-50 rounded border-0.5 border-transparent',
|
||||
'[&_svg]:pointer-events-none [&_svg:not([class*="size-"])]:size-4 shrink-0 [&_svg]:shrink-0',
|
||||
),
|
||||
@ -83,9 +83,13 @@ const buttonVariants = cva(
|
||||
`,
|
||||
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
|
||||
// Static
|
||||
// Button has no interaction transitions
|
||||
static: '',
|
||||
},
|
||||
size: {
|
||||
auto: 'h-full px-1',
|
||||
auto: '',
|
||||
|
||||
xl: 'h-12 rounded-xl px-5',
|
||||
lg: 'h-10 rounded-lg px-4',
|
||||
@ -107,6 +111,8 @@ const buttonVariants = cva(
|
||||
},
|
||||
);
|
||||
|
||||
export type ButtonVariants = VariantProps<typeof buttonVariants>;
|
||||
|
||||
export type ButtonProps<IsAnchor extends boolean = false> = {
|
||||
asChild?: boolean;
|
||||
asLink?: boolean;
|
||||
@ -114,7 +120,7 @@ export type ButtonProps<IsAnchor extends boolean = false> = {
|
||||
block?: boolean;
|
||||
disabled?: boolean;
|
||||
dot?: boolean;
|
||||
} & VariantProps<typeof buttonVariants> &
|
||||
} & ButtonVariants &
|
||||
(IsAnchor extends true
|
||||
? LinkProps
|
||||
: React.ButtonHTMLAttributes<HTMLButtonElement>);
|
||||
@ -144,7 +150,7 @@ const Button = React.forwardRef(
|
||||
<Comp
|
||||
className={cn(
|
||||
buttonVariants({ variant, size, className }),
|
||||
{ 'block w-full': block },
|
||||
{ 'w-full': block },
|
||||
{ relative: dot },
|
||||
)}
|
||||
// @ts-ignore
|
||||
|
||||
@ -3,10 +3,10 @@ import * as React from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const Card = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
HTMLElement,
|
||||
React.HTMLAttributes<HTMLElement> & { as?: React.ElementType }
|
||||
>(({ as: As = 'div', className, ...props }, ref) => (
|
||||
<As
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'rounded-lg border-border-button border-0.5 shadow-sm bg-bg-input transition-shadow',
|
||||
@ -19,9 +19,9 @@ Card.displayName = 'Card';
|
||||
|
||||
const CardHeader = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
React.HTMLAttributes<HTMLDivElement> & { as?: React.ElementType }
|
||||
>(({ as: As = 'div', className, ...props }, ref) => (
|
||||
<As
|
||||
ref={ref}
|
||||
className={cn('flex flex-col space-y-1.5 p-6', className)}
|
||||
{...props}
|
||||
@ -32,7 +32,7 @@ CardHeader.displayName = 'CardHeader';
|
||||
const CardTitle = React.forwardRef<
|
||||
HTMLElement,
|
||||
React.HTMLAttributes<HTMLElement> & { as?: React.ElementType }
|
||||
>(({ className, as: As = 'div', ...props }, ref) => (
|
||||
>(({ as: As = 'div', className, ...props }, ref) => (
|
||||
<As
|
||||
ref={ref}
|
||||
className={cn('text-2xl leading-normal font-medium', className)}
|
||||
@ -43,9 +43,9 @@ CardTitle.displayName = 'CardTitle';
|
||||
|
||||
const CardDescription = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
React.HTMLAttributes<HTMLDivElement> & { as?: React.ElementType }
|
||||
>(({ as: As = 'div', className, ...props }, ref) => (
|
||||
<As
|
||||
ref={ref}
|
||||
className={cn('text-sm text-text-secondary', className)}
|
||||
{...props}
|
||||
@ -55,9 +55,9 @@ CardDescription.displayName = 'CardDescription';
|
||||
|
||||
const CardContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
React.HTMLAttributes<HTMLDivElement> & { as?: React.ElementType }
|
||||
>(({ as: As = 'div', className, ...props }, ref) => (
|
||||
<As
|
||||
ref={ref}
|
||||
className={cn('p-6 pt-0 transition-shadow', className)}
|
||||
{...props}
|
||||
@ -67,9 +67,9 @@ CardContent.displayName = 'CardContent';
|
||||
|
||||
const CardFooter = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
React.HTMLAttributes<HTMLDivElement> & { as?: React.ElementType }
|
||||
>(({ as: As = 'div', className, ...props }, ref) => (
|
||||
<As
|
||||
ref={ref}
|
||||
className={cn('flex items-center p-6 pt-0', className)}
|
||||
{...props}
|
||||
|
||||
@ -38,7 +38,10 @@ const DialogContent = React.forwardRef<
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'outline-0 fixed left-[50%] top-[50%] rounded-lg z-50 grid w-full max-w-xl translate-x-[-50%] translate-y-[-50%] gap-4',
|
||||
'outline-none outline-0 fixed left-[50%] top-[50%] rounded-lg z-50 grid w-full max-w-xl translate-x-[-50%] translate-y-[-50%]',
|
||||
// TODO: to keep scrollbar perfectly aligned to header bottom and/or footer top,
|
||||
// 'gap-4' should be removed, then bring your own body container with padding-y instead.
|
||||
'gap-4',
|
||||
'border-0.5 border-border-button bg-bg-base p-6 shadow-lg duration-200 sm:rounded-lg',
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out',
|
||||
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||
@ -88,7 +91,7 @@ const DialogFooter = ({
|
||||
<div
|
||||
className={cn(
|
||||
// '-mx-6 -mb-6 px-12 pt-4 pb-8',
|
||||
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-4',
|
||||
'-mx-6 -mb-6 p-6 flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-4',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@ -18,7 +18,13 @@ const HoverCardContent = React.forwardRef<
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'z-50 w-fit max-w-96 overflow-auto break-words whitespace-pre-wrap rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-hover-card-content-transform-origin]',
|
||||
'z-50 w-fit max-w-96 overflow-auto break-words whitespace-pre-wrap',
|
||||
'rounded-md border bg-bg-base p-4 text-text-primary shadow-md outline-none',
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0',
|
||||
'data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
||||
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2',
|
||||
'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
'origin-[--radix-hover-card-content-transform-origin]',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@ -2,6 +2,7 @@ import { cn } from '@/lib/utils';
|
||||
import React, { useContext, useState } from 'react';
|
||||
|
||||
const RadioGroupContext = React.createContext<{
|
||||
name?: string;
|
||||
value: string | number;
|
||||
onChange: (value: string | number) => void;
|
||||
disabled?: boolean;
|
||||
@ -50,30 +51,46 @@ function Radio({
|
||||
return (
|
||||
<label
|
||||
className={cn(
|
||||
'flex items-center cursor-pointer gap-2 text-sm',
|
||||
'group/radio relative flex items-center cursor-pointer gap-2 text-sm',
|
||||
mergedDisabled && 'cursor-not-allowed opacity-50',
|
||||
)}
|
||||
>
|
||||
<span
|
||||
<input
|
||||
type="radio"
|
||||
name={groupContext?.name}
|
||||
value={value}
|
||||
checked={isChecked}
|
||||
onClick={handleClick}
|
||||
disabled={mergedDisabled}
|
||||
data-testid={testId}
|
||||
className="peer absolute size-[1px] opacity-0"
|
||||
/>
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-4 w-4 items-center justify-center rounded-full border border-border transition-colors',
|
||||
'peer outline-none focus-visible:border-border-button',
|
||||
'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',
|
||||
isChecked && 'border-primary bg-primary/10',
|
||||
mergedDisabled && 'border-muted',
|
||||
)}
|
||||
onClick={handleClick}
|
||||
data-testid={testId}
|
||||
>
|
||||
{isChecked && (
|
||||
<div className="h-3 w-3 fill-primary text-primary bg-text-primary rounded-full" />
|
||||
)}
|
||||
</span>
|
||||
<div
|
||||
className={cn(
|
||||
'h-2 w-2 fill-primary text-primary bg-text-primary rounded-full opacity-0 scale-0 transition-all',
|
||||
isChecked && 'opacity-100 scale-100',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{children && <span className="text-foreground">{children}</span>}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
type RadioGroupProps = {
|
||||
name?: string;
|
||||
value?: string | number;
|
||||
defaultValue?: string | number;
|
||||
onChange?: (value: string | number) => void;
|
||||
@ -86,6 +103,7 @@ type RadioGroupProps = {
|
||||
const Group = React.forwardRef<HTMLDivElement, RadioGroupProps>(
|
||||
(
|
||||
{
|
||||
name,
|
||||
value,
|
||||
defaultValue,
|
||||
onChange,
|
||||
@ -116,6 +134,7 @@ const Group = React.forwardRef<HTMLDivElement, RadioGroupProps>(
|
||||
return (
|
||||
<RadioGroupContext.Provider
|
||||
value={{
|
||||
name,
|
||||
value: mergedValue,
|
||||
onChange: handleChange,
|
||||
disabled,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
import * as React from 'react';
|
||||
import { Button, ButtonVariants } from './button';
|
||||
export declare type SegmentedValue = string | number;
|
||||
export declare type SegmentedRawOption = SegmentedValue;
|
||||
export interface SegmentedLabeledOption {
|
||||
@ -56,7 +57,7 @@ export interface SegmentedProps extends Omit<
|
||||
itemClassName?: string;
|
||||
rounded?: keyof typeof segmentedVariants.round;
|
||||
sizeType?: keyof typeof segmentedVariants.size;
|
||||
buttonSize?: keyof typeof segmentedVariants.buttonSize;
|
||||
buttonSize?: ButtonVariants['size'];
|
||||
}
|
||||
|
||||
export const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
|
||||
@ -101,12 +102,12 @@ export const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
|
||||
const actualValue = isObject ? option.value : option;
|
||||
|
||||
return (
|
||||
<div
|
||||
<Button
|
||||
key={actualValue}
|
||||
type="button"
|
||||
size={buttonSize}
|
||||
variant="static"
|
||||
className={cn(
|
||||
'inline-flex items-center text-base font-normal cursor-pointer',
|
||||
segmentedVariants.round[rounded],
|
||||
segmentedVariants.buttonSize[buttonSize],
|
||||
{
|
||||
'text-text-primary bg-bg-base': selectedValue === actualValue,
|
||||
},
|
||||
@ -118,7 +119,7 @@ export const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
|
||||
onClick={() => handleOnChange(actualValue)}
|
||||
>
|
||||
{isObject ? option.label : option}
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@ -12,7 +12,7 @@ export function PageContainer({
|
||||
}: React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>) {
|
||||
return (
|
||||
<div
|
||||
className={cn('size-full px-10 py-3 overflow-auto', className)}
|
||||
className={cn('size-full px-5 py-3 overflow-auto', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@ -1,7 +1,7 @@
|
||||
import { Outlet } from 'react-router';
|
||||
import { Header } from './next-header';
|
||||
import { Header } from './components/header';
|
||||
|
||||
export function NextLayoutContainer({ children }: React.PropsWithChildren) {
|
||||
export function RootLayoutContainer({ children }: React.PropsWithChildren) {
|
||||
return (
|
||||
<div className="size-full grid grid-rows-[auto_1fr] grid-cols-1 grid-flow-col">
|
||||
<Header className="px-5 py-4" />
|
||||
@ -11,10 +11,10 @@ export function NextLayoutContainer({ children }: React.PropsWithChildren) {
|
||||
);
|
||||
}
|
||||
|
||||
export default function NextLayout() {
|
||||
export default function RootLayout() {
|
||||
return (
|
||||
<NextLayoutContainer>
|
||||
<RootLayoutContainer>
|
||||
<Outlet />
|
||||
</NextLayoutContainer>
|
||||
</RootLayoutContainer>
|
||||
);
|
||||
}
|
||||
@ -73,6 +73,7 @@ export default {
|
||||
},
|
||||
selected: 'Selected',
|
||||
seeAll: 'See all',
|
||||
bulkOperate: 'Bulk operate',
|
||||
},
|
||||
login: {
|
||||
loginTitle: 'Sign in to your account',
|
||||
@ -514,7 +515,7 @@ Example: A 1 KB message with 1024-dim embedding uses ~9 KB. The 5 MB default lim
|
||||
buildItFromScratch: 'Build it from scratch',
|
||||
dataFlow: 'Pipeline',
|
||||
parseType: 'Parse type',
|
||||
manualSetup: 'Choose pipeline',
|
||||
manualSetup: 'Pipeline',
|
||||
builtIn: 'Built-in',
|
||||
titleDescription:
|
||||
'Update your memory configuration here, particularly the LLM and prompts.',
|
||||
|
||||
@ -20,7 +20,7 @@ import { getSystemVersion, logout } from '@/services/admin-service';
|
||||
|
||||
import authorizationUtil from '@/utils/authorization-util';
|
||||
|
||||
import ThemeSwitch from '../components/theme-switch';
|
||||
import ThemeSwitch from '../../../components/theme-switch';
|
||||
import { IS_ENTERPRISE } from '../utils';
|
||||
import { CurrentUserInfoContext } from './root-layout';
|
||||
|
||||
|
||||
@ -33,8 +33,8 @@ import authorizationUtil from '@/utils/authorization-util';
|
||||
|
||||
import { login } from '@/services/admin-service';
|
||||
|
||||
import ThemeSwitch from '../../components/theme-switch';
|
||||
import { BgSvg } from '../login-next/bg';
|
||||
import ThemeSwitch from './components/theme-switch';
|
||||
|
||||
import { CurrentUserInfoContext } from './layouts/root-layout';
|
||||
|
||||
|
||||
@ -50,25 +50,23 @@ export function DatasetActionCell({
|
||||
}, [record, showRenameModal]);
|
||||
|
||||
return (
|
||||
<section className="flex gap-4 items-center text-text-sub-title-invert opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<div
|
||||
className="
|
||||
flex gap-2 items-center opacity-0
|
||||
transition-opacity group-hover:opacity-100 group-focus-within:opacity-100"
|
||||
>
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="border-none hover:bg-bg-card text-text-primary"
|
||||
size={'sm'}
|
||||
size="icon-xs"
|
||||
variant="ghost"
|
||||
disabled={isRunning}
|
||||
onClick={handleRename}
|
||||
>
|
||||
<PenLine />
|
||||
<PenLine className="size-[1em]" />
|
||||
</Button>
|
||||
<HoverCard>
|
||||
<HoverCardTrigger>
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="border-none hover:bg-bg-card text-text-primary"
|
||||
disabled={isRunning}
|
||||
size={'sm'}
|
||||
>
|
||||
<Eye />
|
||||
<Button size="icon-xs" variant="ghost" disabled={isRunning}>
|
||||
<Eye className="size-[1em]" />
|
||||
</Button>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent className="w-[40vw] max-h-[40vh] overflow-auto">
|
||||
@ -94,26 +92,24 @@ export function DatasetActionCell({
|
||||
|
||||
{isVirtualDocument || (
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="border-none hover:bg-bg-card text-text-primary"
|
||||
size="icon-xs"
|
||||
variant="ghost"
|
||||
onClick={onDownloadDocument}
|
||||
disabled={isRunning}
|
||||
size={'sm'}
|
||||
>
|
||||
<Download />
|
||||
<Download className="size-[1em]" />
|
||||
</Button>
|
||||
)}
|
||||
<ConfirmDeleteDialog onOk={handleRemove}>
|
||||
<Button
|
||||
data-testid="document-delete"
|
||||
variant="transparent"
|
||||
className="border-none hover:bg-bg-card text-text-primary"
|
||||
size={'sm'}
|
||||
size="icon-xs"
|
||||
variant="ghost"
|
||||
disabled={isRunning}
|
||||
>
|
||||
<Trash2 />
|
||||
<Trash2 className="size-[1em]" />
|
||||
</Button>
|
||||
</ConfirmDeleteDialog>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -199,25 +199,27 @@ const Generate: React.FC<GenerateProps> = (props) => {
|
||||
return (
|
||||
<DropdownMenu open={open} onOpenChange={handleOpenChange}>
|
||||
<DropdownMenuTrigger asChild disabled={disabled}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
className={cn(disabled && '!cursor-not-allowed')}
|
||||
variant="transparent"
|
||||
size="icon"
|
||||
onClick={() => {
|
||||
if (!disabled) {
|
||||
handleOpenChange(!open);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<WandSparkles />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
className={cn(disabled && '!cursor-not-allowed')}
|
||||
variant="transparent"
|
||||
size="icon"
|
||||
onClick={() => {
|
||||
if (!disabled) {
|
||||
handleOpenChange(!open);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<WandSparkles />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent>{t('knowledgeDetails.generate')}</TooltipContent>
|
||||
</Tooltip>
|
||||
<TooltipContent>{t('knowledgeDetails.generate')}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-[380px] p-5 flex flex-col gap-2 ">
|
||||
{Object.values(GenerateType).map((name) => {
|
||||
|
||||
@ -6,6 +6,7 @@ import { FileUploadDialog } from '@/components/file-upload-dialog';
|
||||
import ListFilterBar from '@/components/list-filter-bar';
|
||||
import { RenameDialog } from '@/components/rename-dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@ -16,7 +17,7 @@ import {
|
||||
import { useRowSelection } from '@/hooks/logic-hooks/use-row-selection';
|
||||
import { useFetchDocumentList } from '@/hooks/use-document-request';
|
||||
import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-request';
|
||||
import { Upload } from 'lucide-react';
|
||||
import { LucidePlus } from 'lucide-react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { MetadataType } from '../components/metedata/constant';
|
||||
@ -136,10 +137,12 @@ export default function Dataset() {
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="p-5 min-w-[880px]">
|
||||
<Card
|
||||
as="article"
|
||||
className="mb-5 mr-5 min-w-[880px] bg-transparent shadow-none"
|
||||
>
|
||||
<CardHeader as="header" className="p-5 space-y-0">
|
||||
<ListFilterBar
|
||||
title={t('header.dataset')}
|
||||
onSearchChange={handleInputChange}
|
||||
searchString={searchString}
|
||||
value={filterValue}
|
||||
@ -147,12 +150,15 @@ export default function Dataset() {
|
||||
onChange={handleFilterSubmit}
|
||||
onOpenChange={onOpenChange}
|
||||
filters={filters}
|
||||
className="items-end"
|
||||
leftPanel={
|
||||
<div className="items-start">
|
||||
<div className="pb-1">{t('knowledgeDetails.subbarFiles')}</div>
|
||||
<div className="text-text-secondary text-sm">
|
||||
<div>
|
||||
<h1 className="leading-normal font-medium">
|
||||
{t('knowledgeDetails.subbarFiles')}
|
||||
</h1>
|
||||
<p className="text-text-secondary text-sm font-normal">
|
||||
{t('knowledgeDetails.datasetDescription')}
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
preChildren={<Generate disabled={!(dataSetData.chunk_num > 0)} />}
|
||||
@ -190,12 +196,12 @@ export default function Dataset() {
|
||||
>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button size={'sm'}>
|
||||
<Upload />
|
||||
<Button size="default">
|
||||
<LucidePlus />
|
||||
{t('knowledgeDetails.addFile')}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56">
|
||||
<DropdownMenuContent className="w-auto min-w-40" align="end">
|
||||
<DropdownMenuItem onClick={showDocumentUploadModal}>
|
||||
{t('fileManager.uploadFile')}
|
||||
</DropdownMenuItem>
|
||||
@ -206,12 +212,17 @@ export default function Dataset() {
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</ListFilterBar>
|
||||
|
||||
{rowSelectionIsEmpty || (
|
||||
<BulkOperateBar
|
||||
className="!mt-2.5 !-mb-2.5"
|
||||
list={updatedList as BulkOperateItemType[]}
|
||||
count={selectedCount}
|
||||
></BulkOperateBar>
|
||||
/>
|
||||
)}
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="px-5 py-0">
|
||||
<DatasetTable
|
||||
documents={documents}
|
||||
pagination={pagination}
|
||||
@ -220,7 +231,8 @@ export default function Dataset() {
|
||||
setRowSelection={setRowSelection}
|
||||
showManageMetadataModal={showManageMetadataModal}
|
||||
loading={loading}
|
||||
></DatasetTable>
|
||||
/>
|
||||
|
||||
{documentUploadVisible && (
|
||||
<FileUploadDialog
|
||||
hideModal={hideDocumentUploadModal}
|
||||
@ -282,7 +294,7 @@ export default function Dataset() {
|
||||
hideModal={hideReparseDialogModal}
|
||||
></ReparseDialog>
|
||||
)}
|
||||
</section>
|
||||
</>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@ -84,9 +84,8 @@ export const PopoverContent = ({ record }: IProps) => {
|
||||
export function ParsingCard({ record, handleShowLog }: IProps) {
|
||||
return (
|
||||
<Button
|
||||
variant={'transparent'}
|
||||
className="border-none"
|
||||
size={'sm'}
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={() => handleShowLog?.(record)}
|
||||
>
|
||||
<Dot run={record.run}></Dot>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { IconFontFill } from '@/components/icon-font';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@ -24,10 +25,10 @@ import { useHandleRunDocumentByIds } from './use-run-document';
|
||||
import { isParserRunning } from './utils';
|
||||
const IconMap = {
|
||||
[RunningStatus.UNSTART]: (
|
||||
<IconFontFill name="play" className="text-accent-primary" />
|
||||
<IconFontFill name="play" className="text-accent-primary size-[1em]" />
|
||||
),
|
||||
[RunningStatus.RUNNING]: (
|
||||
<CircleX size={14} color="rgba(var(--state-error))" />
|
||||
<CircleX color="rgba(var(--state-error))" className="size-[1em]" />
|
||||
),
|
||||
[RunningStatus.CANCEL]: (
|
||||
<IconFontFill name="reparse" className="text-accent-primary" />
|
||||
@ -49,25 +50,63 @@ const ParseStatusStateMap = {
|
||||
[RunningStatus.SCHEDULE]: 'running',
|
||||
} as const;
|
||||
|
||||
export function ParsingStatusCell({
|
||||
export function ParseDropdownButton({
|
||||
record,
|
||||
showChangeParserModal,
|
||||
// showSetMetaModal,
|
||||
}: {
|
||||
record: IDocumentInfo;
|
||||
} & UseChangeDocumentParserShowType) {
|
||||
const { t } = useTranslation();
|
||||
const { pipeline_id, pipeline_name, parser_id } = record;
|
||||
|
||||
const handleShowChangeParserModal = useCallback(() => {
|
||||
showChangeParserModal(record);
|
||||
}, [record, showChangeParserModal]);
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="static" size="auto" className="capitalize">
|
||||
{pipeline_id
|
||||
? pipeline_name || pipeline_id
|
||||
: parser_id === 'naive'
|
||||
? 'general'
|
||||
: parser_id}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p className="capitalize">
|
||||
{pipeline_id
|
||||
? pipeline_name || pipeline_id
|
||||
: parser_id === 'naive'
|
||||
? 'general'
|
||||
: parser_id}
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem onClick={handleShowChangeParserModal}>
|
||||
{t('knowledgeDetails.dataPipeline')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
export function ParsingStatusCell({
|
||||
record,
|
||||
showLog,
|
||||
}: {
|
||||
record: IDocumentInfo;
|
||||
showLog: (record: IDocumentInfo) => void;
|
||||
} & UseChangeDocumentParserShowType) {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
run,
|
||||
parser_id,
|
||||
pipeline_id,
|
||||
pipeline_name,
|
||||
progress,
|
||||
chunk_num,
|
||||
id,
|
||||
} = record;
|
||||
const { run, progress, chunk_num, id } = record;
|
||||
const operationIcon = IconMap[run];
|
||||
const p = Number((progress * 100).toFixed(2));
|
||||
const {
|
||||
@ -86,10 +125,6 @@ export function ParsingStatusCell({
|
||||
handleRunDocumentByIds(record.id, isRunning, option);
|
||||
};
|
||||
|
||||
const handleShowChangeParserModal = useCallback(() => {
|
||||
showChangeParserModal(record);
|
||||
}, [record, showChangeParserModal]);
|
||||
|
||||
const showParse = useMemo(() => {
|
||||
return record.type !== DocumentType.Virtual;
|
||||
}, [record]);
|
||||
@ -103,71 +138,25 @@ export function ParsingStatusCell({
|
||||
data-testid="document-parse-status"
|
||||
data-state={ParseStatusStateMap[run] ?? 'unknown'}
|
||||
>
|
||||
<div className="text-ellipsis w-[100px] flex items-center justify-between">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="border-none truncate max-w-32 cursor-pointer px-2 py-1 rounded-sm hover:bg-bg-card">
|
||||
{pipeline_id
|
||||
? pipeline_name || pipeline_id
|
||||
: parser_id === 'naive'
|
||||
? 'general'
|
||||
: parser_id}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>
|
||||
{pipeline_id
|
||||
? pipeline_name || pipeline_id
|
||||
: parser_id === 'naive'
|
||||
? 'general'
|
||||
: parser_id}
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem onClick={handleShowChangeParserModal}>
|
||||
{t('knowledgeDetails.dataPipeline')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
{showParse && (
|
||||
<div className="flex items-center gap-3">
|
||||
<Separator orientation="vertical" className="h-2.5" />
|
||||
{!isParserRunning(run) && (
|
||||
// <ReparseDialog
|
||||
// hidden={isZeroChunk || isRunning}
|
||||
// handleOperationIconClick={handleOperationIconClick}
|
||||
// chunk_num={chunk_num}
|
||||
// >
|
||||
<div
|
||||
className="cursor-pointer flex items-center gap-3"
|
||||
onClick={() => {
|
||||
showReparseDialogModal();
|
||||
}}
|
||||
>
|
||||
{!isParserRunning(run) && operationIcon}
|
||||
</div>
|
||||
// {/* </ReparseDialog> */}
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
<Separator orientation="vertical" className="h-[1em]" />
|
||||
|
||||
{isParserRunning(run) ? (
|
||||
<>
|
||||
<div
|
||||
className="flex items-center gap-1 cursor-pointer"
|
||||
<Button
|
||||
size="auto"
|
||||
variant="static"
|
||||
onClick={() => handleShowLog(record)}
|
||||
>
|
||||
<Progress value={p} className="h-1 flex-1 min-w-10" />
|
||||
{p}%
|
||||
</div>
|
||||
<div
|
||||
className="cursor-pointer flex items-center gap-3"
|
||||
onClick={() => {
|
||||
showReparseDialogModal();
|
||||
}}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={() => showReparseDialogModal()}
|
||||
// onClick={
|
||||
// isZeroChunk || isRunning
|
||||
// ? handleOperationIconClick(false)
|
||||
@ -175,13 +164,22 @@ export function ParsingStatusCell({
|
||||
// }
|
||||
>
|
||||
{operationIcon}
|
||||
</div>
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<ParsingCard
|
||||
record={record}
|
||||
handleShowLog={handleShowLog}
|
||||
></ParsingCard>
|
||||
<>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={() => {
|
||||
showReparseDialogModal();
|
||||
}}
|
||||
>
|
||||
{operationIcon}
|
||||
</Button>
|
||||
|
||||
<ParsingCard record={record} handleShowLog={handleShowLog} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -10,12 +10,12 @@ import {
|
||||
} from '@/hooks/use-document-request';
|
||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||
import {
|
||||
Ban,
|
||||
CircleCheck,
|
||||
CircleX,
|
||||
Cylinder,
|
||||
Play,
|
||||
Trash2,
|
||||
LucideCircleX,
|
||||
LucideCylinder,
|
||||
LucidePlayCircle,
|
||||
LucideToggleLeft,
|
||||
LucideToggleRight,
|
||||
LucideTrash2,
|
||||
} from 'lucide-react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -117,36 +117,36 @@ export function useBulkOperateDataset({
|
||||
{
|
||||
id: 'enabled',
|
||||
label: t('knowledgeDetails.enabled'),
|
||||
icon: <CircleCheck />,
|
||||
icon: <LucideToggleRight />,
|
||||
onClick: handleEnableClick,
|
||||
},
|
||||
{
|
||||
id: 'disabled',
|
||||
label: t('knowledgeDetails.disabled'),
|
||||
icon: <Ban />,
|
||||
icon: <LucideToggleLeft />,
|
||||
onClick: handleDisableClick,
|
||||
},
|
||||
{
|
||||
id: 'run',
|
||||
label: t('knowledgeDetails.run'),
|
||||
icon: <Play />,
|
||||
icon: <LucidePlayCircle />,
|
||||
onClick: () => showModal(),
|
||||
},
|
||||
{
|
||||
id: 'cancel',
|
||||
label: t('knowledgeDetails.cancel'),
|
||||
icon: <CircleX />,
|
||||
icon: <LucideCircleX />,
|
||||
onClick: handleCancelClick,
|
||||
},
|
||||
{
|
||||
id: 'batch-metadata',
|
||||
label: t('knowledgeDetails.metadata.metadata'),
|
||||
icon: <Cylinder />,
|
||||
icon: <LucideCylinder />,
|
||||
},
|
||||
{
|
||||
id: 'delete',
|
||||
label: t('common.delete'),
|
||||
icon: <Trash2 />,
|
||||
icon: <LucideTrash2 />,
|
||||
onClick: async () => {
|
||||
const code = await handleDelete();
|
||||
if (code === 0) {
|
||||
|
||||
@ -11,15 +11,14 @@ import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { useSetDocumentStatus } from '@/hooks/use-document-request';
|
||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useDataSourceInfo } from '@/pages/user-setting/data-source/constant';
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { ColumnDef } from '@tanstack/table-core';
|
||||
import { ArrowUpDown, MonitorUp } from 'lucide-react';
|
||||
import { ArrowUpDown } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { MetadataType } from '../components/metedata/constant';
|
||||
import { ShowManageMetadataModalProps } from '../components/metedata/interface';
|
||||
import { DatasetActionCell } from './dataset-action-cell';
|
||||
import { ParsingStatusCell } from './parsing-status-cell';
|
||||
import { ParseDropdownButton, ParsingStatusCell } from './parsing-status-cell';
|
||||
import { UseChangeDocumentParserShowType } from './use-change-document-parser';
|
||||
import { UseRenameDocumentShowType } from './use-rename-document';
|
||||
|
||||
@ -38,7 +37,7 @@ export function useDatasetTableColumns({
|
||||
const { t } = useTranslation('translation', {
|
||||
keyPrefix: 'knowledgeDetails',
|
||||
});
|
||||
const { dataSourceInfo } = useDataSourceInfo();
|
||||
// const { dataSourceInfo } = useDataSourceInfo();
|
||||
const { navigateToChunkParsedResult } = useNavigatePage();
|
||||
const { setDocumentStatus } = useSetDocumentStatus();
|
||||
|
||||
@ -47,6 +46,7 @@ export function useDatasetTableColumns({
|
||||
id: 'select',
|
||||
header: ({ table }) => (
|
||||
<Checkbox
|
||||
className="size-3"
|
||||
checked={
|
||||
table.getIsAllPageRowsSelected() ||
|
||||
(table.getIsSomePageRowsSelected() && 'indeterminate')
|
||||
@ -57,6 +57,7 @@ export function useDatasetTableColumns({
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<Checkbox
|
||||
className="size-3"
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
aria-label="Select row"
|
||||
@ -69,14 +70,19 @@ export function useDatasetTableColumns({
|
||||
accessorKey: 'name',
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="border-none"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
{t('name')}
|
||||
<ArrowUpDown />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={() =>
|
||||
column.toggleSorting(column.getIsSorted() === 'asc')
|
||||
}
|
||||
>
|
||||
<ArrowUpDown />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
meta: { cellClassName: 'max-w-[20vw]' },
|
||||
@ -87,7 +93,7 @@ export function useDatasetTableColumns({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
className="flex gap-2 cursor-pointer"
|
||||
className="flex items-center gap-2 cursor-pointer"
|
||||
onClick={navigateToChunkParsedResult(
|
||||
row.original.id,
|
||||
row.original.kb_id,
|
||||
@ -108,22 +114,31 @@ export function useDatasetTableColumns({
|
||||
accessorKey: 'create_time',
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="border-none"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
{t('uploadDate')}
|
||||
<ArrowUpDown />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={() =>
|
||||
column.toggleSorting(column.getIsSorted() === 'asc')
|
||||
}
|
||||
>
|
||||
<ArrowUpDown />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => (
|
||||
<div className="lowercase">
|
||||
<time
|
||||
className="lowercase"
|
||||
dateTime={new Date(row.getValue('create_time')).toISOString()}
|
||||
>
|
||||
{formatDate(row.getValue('create_time'))}
|
||||
</div>
|
||||
</time>
|
||||
),
|
||||
},
|
||||
/*
|
||||
{
|
||||
accessorKey: 'source_from',
|
||||
header: t('source'),
|
||||
@ -146,6 +161,7 @@ export function useDatasetTableColumns({
|
||||
</div>
|
||||
),
|
||||
},
|
||||
*/
|
||||
{
|
||||
accessorKey: 'status',
|
||||
header: t('enabled'),
|
||||
@ -174,8 +190,9 @@ export function useDatasetTableColumns({
|
||||
cell: ({ row }) => {
|
||||
const length = Object.keys(row.getValue('meta_fields') || {}).length;
|
||||
return (
|
||||
<div
|
||||
className="capitalize cursor-pointer"
|
||||
<Button
|
||||
variant="static"
|
||||
size="auto"
|
||||
onClick={() => {
|
||||
showManageMetadataModal({
|
||||
// metadata: util.JSONToMetaDataTableData(
|
||||
@ -209,7 +226,7 @@ export function useDatasetTableColumns({
|
||||
}}
|
||||
>
|
||||
{length + ' fields'}
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
},
|
||||
@ -217,13 +234,25 @@ export function useDatasetTableColumns({
|
||||
accessorKey: 'run',
|
||||
header: t('Parse'),
|
||||
// meta: { cellClassName: 'min-w-[20vw]' },
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<ParseDropdownButton
|
||||
record={row.original}
|
||||
showChangeParserModal={showChangeParserModal}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'run-status',
|
||||
header: '',
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<ParsingStatusCell
|
||||
record={row.original}
|
||||
showChangeParserModal={showChangeParserModal}
|
||||
showLog={showLog}
|
||||
></ParsingStatusCell>
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
@ -238,7 +267,7 @@ export function useDatasetTableColumns({
|
||||
<DatasetActionCell
|
||||
record={record}
|
||||
showRenameModal={showRenameModal}
|
||||
></DatasetActionCell>
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
@ -45,7 +45,7 @@ export function SeeAllCard() {
|
||||
return (
|
||||
<Card
|
||||
className="w-full flex-none h-full cursor-pointer"
|
||||
onClick={navigateToDatasetList}
|
||||
onClick={() => navigateToDatasetList({ isCreate: false })}
|
||||
>
|
||||
<CardContent className="p-2.5 pt-1 w-full h-full flex items-center justify-center gap-1.5 text-text-secondary">
|
||||
{t('common.seeAll')} <ChevronRight className="size-4" />
|
||||
|
||||
@ -20,14 +20,16 @@ export function ApplicationCard({
|
||||
moreDropdown,
|
||||
}: ApplicationCardProps) {
|
||||
return (
|
||||
<Card className="w-[264px]" onClick={onClick}>
|
||||
<CardContent className="p-2.5 group flex justify-between w-full">
|
||||
<Card className="w-[264px]" onClick={onClick} as="article">
|
||||
<CardContent className="p-2.5 group flex justify-between w-full">
|
||||
<div className="flex items-center gap-2.5 w-full">
|
||||
<RAGFlowAvatar
|
||||
className="size-14 rounded-lg"
|
||||
avatar={app.avatar}
|
||||
name={app.title || 'CN'}
|
||||
></RAGFlowAvatar>
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
<div className="flex-1">
|
||||
<h3 className="text-sm font-normal line-clamp-1 mb-1 text-ellipsis w-[160px] overflow-hidden">
|
||||
{app.title}
|
||||
@ -37,6 +39,7 @@ export function ApplicationCard({
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{moreDropdown}
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -49,7 +52,11 @@ export type SeeAllAppCardProps = {
|
||||
|
||||
export function SeeAllAppCard({ click }: SeeAllAppCardProps) {
|
||||
return (
|
||||
<Card className="w-full min-h-[76px] cursor-pointer" onClick={click}>
|
||||
<Card
|
||||
className="w-full min-h-[76px] cursor-pointer"
|
||||
onClick={click}
|
||||
tabIndex={0}
|
||||
>
|
||||
<CardContent className="p-2.5 pt-1 w-full h-full flex items-center justify-center gap-1.5 text-text-secondary">
|
||||
{t('common.seeAll')} <ChevronRight className="size-4" />
|
||||
</CardContent>
|
||||
|
||||
@ -47,10 +47,10 @@ export function Applications() {
|
||||
|
||||
const options = useMemo(
|
||||
() => [
|
||||
{ value: Routes.Chats, label: t('chat.chatApps') },
|
||||
{ value: Routes.Searches, label: t('search.searchApps') },
|
||||
{ value: Routes.Chats, label: t('header.chat') },
|
||||
{ value: Routes.Searches, label: t('header.search') },
|
||||
{ value: Routes.Agents, label: t('header.flow') },
|
||||
{ value: Routes.Memories, label: t('memories.memory') },
|
||||
{ value: Routes.Memories, label: t('header.memories') },
|
||||
],
|
||||
[t],
|
||||
);
|
||||
@ -63,55 +63,57 @@ export function Applications() {
|
||||
|
||||
return (
|
||||
<section className="mt-12">
|
||||
<div className="flex justify-between items-center mb-5">
|
||||
<h2 className="text-2xl font-semibold flex gap-2.5">
|
||||
<header className="flex justify-between items-center mb-2.5">
|
||||
<h2 className="text-2xl font-semibold">
|
||||
<HomeIcon
|
||||
imgClass="me-2.5"
|
||||
name={`${IconMap[val as keyof typeof IconMap]}`}
|
||||
width={'32'}
|
||||
width={24}
|
||||
/>
|
||||
{options.find((x) => x.value === val)?.label}
|
||||
</h2>
|
||||
|
||||
<Segmented
|
||||
buttonSize="sm"
|
||||
options={options}
|
||||
value={val}
|
||||
onChange={handleChange}
|
||||
buttonSize="xl"
|
||||
// className="bg-bg-card border border-border-button rounded-lg"
|
||||
// activeClassName="bg-text-primary border-none rounded-lg"
|
||||
></Segmented>
|
||||
</div>
|
||||
/>
|
||||
</header>
|
||||
|
||||
{/* <div className="flex flex-wrap gap-4"> */}
|
||||
<CardSineLineContainer>
|
||||
{val === Routes.Agents && (
|
||||
<Agents
|
||||
setListLength={(length: number) => setListLength(length)}
|
||||
setLoading={(loading: boolean) => setLoading(loading)}
|
||||
></Agents>
|
||||
/>
|
||||
)}
|
||||
{val === Routes.Chats && (
|
||||
<ChatList
|
||||
setListLength={(length: number) => setListLength(length)}
|
||||
setLoading={(loading: boolean) => setLoading(loading)}
|
||||
></ChatList>
|
||||
/>
|
||||
)}
|
||||
{val === Routes.Searches && (
|
||||
<SearchList
|
||||
setListLength={(length: number) => setListLength(length)}
|
||||
setLoading={(loading: boolean) => setLoading(loading)}
|
||||
></SearchList>
|
||||
/>
|
||||
)}
|
||||
{val === Routes.Memories && (
|
||||
<MemoryList
|
||||
setListLength={(length: number) => setListLength(length)}
|
||||
setLoading={(loading: boolean) => setLoading(loading)}
|
||||
></MemoryList>
|
||||
/>
|
||||
)}
|
||||
{listLength > 0 && (
|
||||
<SeeAllAppCard
|
||||
click={() => handleNavigate({ isCreate: false })}
|
||||
></SeeAllAppCard>
|
||||
<SeeAllAppCard click={() => handleNavigate({ isCreate: false })} />
|
||||
)}
|
||||
</CardSineLineContainer>
|
||||
|
||||
{listLength <= 0 && !loading && (
|
||||
<EmptyAppCard
|
||||
type={EmptyTypeMap[val as keyof typeof EmptyTypeMap]}
|
||||
|
||||
@ -42,9 +42,11 @@ export function Banner() {
|
||||
export function NextBanner() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<h1 className="text-5xl font-bold leading-normal">
|
||||
<span className="text-text-primary">{t('header.welcome')} </span>
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-l from-[#40EBE3] to-[#4A51FF]">
|
||||
<h1 className="text-5xl leading-normal">
|
||||
<span className="font-semibold text-text-primary">
|
||||
{t('header.welcome')}{' '}
|
||||
</span>
|
||||
<span className="font-bold text-transparent bg-clip-text bg-gradient-to-l from-[#40EBE3] to-[#4A51FF]">
|
||||
RAGFlow
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
@ -26,12 +26,15 @@ export function Datasets() {
|
||||
|
||||
return (
|
||||
<section>
|
||||
<h2 className="text-2xl font-semibold mb-6 flex gap-2.5 items-center">
|
||||
{/* <IconFont name="data" className="size-8"></IconFont> */}
|
||||
<HomeIcon name="datasets" width={'32'} />
|
||||
{t('header.dataset')}
|
||||
</h2>
|
||||
<div className="">
|
||||
<header>
|
||||
<h2 className="leading-8 text-2xl font-semibold mb-2.5">
|
||||
{/* <IconFont name="data" className="size-8"></IconFont> */}
|
||||
<HomeIcon imgClass="me-2.5" name="datasets" width={24} />
|
||||
{t('header.dataset')}
|
||||
</h2>
|
||||
</header>
|
||||
|
||||
<div>
|
||||
{loading ? (
|
||||
<div className="flex-1">
|
||||
<CardSkeleton />
|
||||
@ -40,15 +43,13 @@ export function Datasets() {
|
||||
<>
|
||||
{kbs?.length > 0 && (
|
||||
<CardSineLineContainer>
|
||||
{kbs
|
||||
?.slice(0, 6)
|
||||
.map((dataset) => (
|
||||
<DatasetCard
|
||||
key={dataset.id}
|
||||
dataset={dataset}
|
||||
showDatasetRenameModal={showDatasetRenameModal}
|
||||
></DatasetCard>
|
||||
))}
|
||||
{kbs?.slice(0, 6).map((dataset) => (
|
||||
<DatasetCard
|
||||
key={dataset.id}
|
||||
dataset={dataset}
|
||||
showDatasetRenameModal={showDatasetRenameModal}
|
||||
></DatasetCard>
|
||||
))}
|
||||
{
|
||||
<SeeAllAppCard
|
||||
click={() => navigateToDatasetList({ isCreate: false })}
|
||||
@ -66,13 +67,14 @@ export function Datasets() {
|
||||
// </div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{datasetRenameVisible && (
|
||||
<RenameDialog
|
||||
hideModal={hideDatasetRenameModal}
|
||||
onOk={onDatasetRenameOk}
|
||||
initialName={initialDatasetName}
|
||||
loading={datasetRenameLoading}
|
||||
></RenameDialog>
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { PageContainer } from '@/layouts/page-container';
|
||||
import { PageContainer } from '@/layouts/components/page-container';
|
||||
import { Applications } from './applications';
|
||||
import { NextBanner } from './banner';
|
||||
import { Datasets } from './datasets';
|
||||
@ -6,12 +6,14 @@ import { Datasets } from './datasets';
|
||||
const Home = () => {
|
||||
return (
|
||||
<PageContainer>
|
||||
<header className="mb-8">
|
||||
<NextBanner />
|
||||
</header>
|
||||
<article>
|
||||
<header className="mb-8">
|
||||
<NextBanner />
|
||||
</header>
|
||||
|
||||
<Datasets />
|
||||
<Applications />
|
||||
<Datasets />
|
||||
<Applications />
|
||||
</article>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
useGetChatSearchParams,
|
||||
} from '@/hooks/use-chat-request';
|
||||
import { IClientConversation } from '@/interfaces/database/chat';
|
||||
import { NextLayoutContainer } from '@/layouts/next';
|
||||
import { RootLayoutContainer } from '@/layouts/root-layout';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useMount } from 'ahooks';
|
||||
import { isEmpty } from 'lodash';
|
||||
@ -106,7 +106,7 @@ export default function Chat() {
|
||||
}
|
||||
|
||||
return (
|
||||
<NextLayoutContainer>
|
||||
<RootLayoutContainer>
|
||||
<section className="h-full flex flex-col" data-testid="chat-detail">
|
||||
<article className="flex flex-1 min-h-0 pb-9">
|
||||
<Sessions handleConversationCardClick={handleSessionClick}></Sessions>
|
||||
@ -146,6 +146,6 @@ export default function Chat() {
|
||||
</Card>
|
||||
</article>
|
||||
</section>
|
||||
</NextLayoutContainer>
|
||||
</RootLayoutContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { IconFontFill } from '@/components/icon-font';
|
||||
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
||||
import ThemeToggle from '@/components/theme-toggle';
|
||||
import ThemeSwitch from '@/components/theme-switch';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Domain } from '@/constants/common';
|
||||
import { useSecondPathName } from '@/hooks/route-hook';
|
||||
@ -103,15 +103,10 @@ export function SideBar() {
|
||||
<div className="mr-2 px-2 text-accent-primary rounded-md">
|
||||
{version}
|
||||
</div>
|
||||
<ThemeToggle />
|
||||
<ThemeSwitch />
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="w-full gap-3 bg-bg-base border border-border-button"
|
||||
onClick={() => {
|
||||
logout();
|
||||
}}
|
||||
>
|
||||
|
||||
<Button block size="lg" variant="transparent" onClick={() => logout()}>
|
||||
{t('setting.logout')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -146,7 +146,7 @@ const routeConfigOptions = [
|
||||
{
|
||||
path: Routes.Root,
|
||||
layout: false,
|
||||
Component: () => import('@/layouts/next'),
|
||||
Component: () => import('@/layouts/root-layout'),
|
||||
loader: ({ request }) => {
|
||||
const url = new URL(request.url);
|
||||
const auth = url.searchParams.get('auth');
|
||||
@ -170,7 +170,7 @@ const routeConfigOptions = [
|
||||
},
|
||||
{
|
||||
path: Routes.Root,
|
||||
Component: () => import('@/layouts/next'),
|
||||
Component: () => import('@/layouts/root-layout'),
|
||||
children: [
|
||||
{
|
||||
path: Routes.Datasets,
|
||||
|
||||
Reference in New Issue
Block a user