refactor(ui): update knowledge graph, chunk, metadata, agent log styles (#13518)

### What problem does this PR solve?

Update UI styles:
- **Dataset** > **Knowledge graph** tooltip
- **Dataset** > **Files** > **Manage metadata** modal
- **Dataset** > **Files** > **Modify Chunking Method** > **Auto
metadata** > **Manage generation settings** modal
- **Agent** > **Canvas (Ingestion pipeline)** > **Dataflow result**

### Type of change

- [x] Refactoring
This commit is contained in:
Jimmy Ben Klieve
2026-03-11 11:27:20 +08:00
committed by GitHub
parent 2133fd76a8
commit 507ba4ea20
32 changed files with 613 additions and 485 deletions

View File

@@ -73,10 +73,11 @@ const ChunkCard = ({
return (
<Card
className={classNames('relative flex-none', styles.chunkCard, {
[`${theme === 'dark' ? styles.cardSelectedDark : styles.cardSelected}`]:
selected,
})}
as="article"
className={classNames(
'relative flex-none p-3 pt-6 shadow-none transition-colors',
selected && 'bg-text-primary/15',
)}
>
<span
className="
@@ -88,8 +89,12 @@ const ChunkCard = ({
{t(`chunk.docType.${chunkType}`)}
</span>
<div className="flex items-start justify-between gap-2">
<Checkbox onCheckedChange={handleCheck} checked={checked}></Checkbox>
<div className="flex items-start justify-between gap-2.5">
<Checkbox
className="mt-1"
onCheckedChange={handleCheck}
checked={checked}
/>
{/* Using <Tooltip> instead of <Popover> to avoid flickering when hovering over the image */}
{item.image_id && (
@@ -98,41 +103,44 @@ const ChunkCard = ({
<Image
t={imageCacheKey}
id={item.image_id}
className={styles.image}
className="mt-1 rounded !w-28 object-contain"
/>
</TooltipTrigger>
<TooltipContent
className="p-0"
align={'start'}
side={'left'}
align="start"
side="left"
sideOffset={-20}
tabIndex={-1}
>
<Image
t={imageCacheKey}
id={item.image_id}
className={styles.imagePreview}
className="size-full max-w-[50vw] max-h-[50vh] object-contain"
/>
</TooltipContent>
</Tooltip>
)}
<section
className={cn(styles.content, 'flex-1')}
onDoubleClick={handleContentDoubleClick}
onClick={handleContentClick}
className={cn(styles.content, 'mt-2')}
>
<div
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(item.content_with_weight),
__html: DOMPurify.sanitize(item.content_with_weight).trim(),
}}
className={classNames(styles.contentText, {
[styles.contentEllipsis]: textMode === ChunkTextMode.Ellipse,
})}
></div>
className={classNames(
// Keep whitespaces?
'text-wrap break-words whitespace-pre',
textMode === ChunkTextMode.Ellipse && 'line-clamp-3',
)}
/>
</section>
<div className="mt-2">
<div>
<Switch
checked={enabled}
onCheckedChange={onChange}

View File

@@ -1,10 +1,17 @@
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Label } from '@/components/ui/label';
import { Ban, CircleCheck, Trash2 } from 'lucide-react';
import { cn } from '@/lib/utils';
import {
LucideToggleLeft,
LucideToggleRight,
LucideTrash2,
} from 'lucide-react';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
type ICheckboxSetProps = {
className?: string;
selectAllChunk: (e: any) => void;
removeChunk: (e?: any) => void;
switchChunk: (available: number) => void;
@@ -13,6 +20,7 @@ type ICheckboxSetProps = {
};
export default (props: ICheckboxSetProps) => {
const {
className,
selectAllChunk,
removeChunk,
switchChunk,
@@ -45,39 +53,28 @@ export default (props: ICheckboxSetProps) => {
}, [selectedChunkIds]);
return (
<div className="flex gap-[40px] py-4 px-2 h-14">
<div className="flex items-center gap-3 cursor-pointer text-muted-foreground hover:text-text-primary">
<Checkbox
id="all_chunks_checkbox"
onCheckedChange={handleSelectAllCheck}
checked={checked}
className=" data-[state=checked]:bg-text-primary data-[state=checked]:border-text-primary data-[state=checked]:text-bg-base border-muted-foreground text-muted-foreground hover:text-bg-base hover:border-text-primary "
/>
<Label htmlFor="all_chunks_checkbox">{t('chunk.selectAll')}</Label>
</div>
<div className={cn('flex gap-4 text-sm text-text-secondary', className)}>
<Label className="flex items-center gap-2">
<Checkbox onCheckedChange={handleSelectAllCheck} checked={checked} />
<span>{t('chunk.selectAll')}</span>
</Label>
{isSelected && (
<>
<div
className="flex items-center cursor-pointer text-muted-foreground hover:text-text-primary"
onClick={handleEnabledClick}
>
<CircleCheck size={16} />
<span className="block ml-1">{t('chunk.enable')}</span>
</div>
<div
className="flex items-center cursor-pointer text-muted-foreground hover:text-text-primary"
onClick={handleDisabledClick}
>
<Ban size={16} />
<Button variant="outline" onClick={handleEnabledClick}>
<LucideToggleRight size={16} />
{t('chunk.enable')}
</Button>
<Button variant="outline" onClick={handleDisabledClick}>
<LucideToggleLeft size={16} />
<span className="block ml-1">{t('chunk.disable')}</span>
</div>
<div
className="flex items-center cursor-pointer text-red-400 hover:text-red-500"
onClick={handleDeleteClick}
>
<Trash2 size={16} />
<span className="block ml-1">{t('chunk.delete')}</span>
</div>
</Button>
<Button variant="danger" onClick={handleDeleteClick}>
<LucideTrash2 className="size-[1em]" />
{t('chunk.delete')}
</Button>
</>
)}
</div>

View File

@@ -8,7 +8,8 @@ import {
import { Radio } from '@/components/ui/radio';
import { Segmented } from '@/components/ui/segmented';
import { useTranslate } from '@/hooks/common-hooks';
import { ListFilter, Plus } from 'lucide-react';
import { cn } from '@/lib/utils';
import { LucideFilter, Plus } from 'lucide-react';
import { useState } from 'react';
import { ChunkTextMode } from '../../constant';
interface ChunkResultBarProps {
@@ -21,6 +22,7 @@ interface ChunkResultBarProps {
searchString: string;
}
export default function ChunkResultBar({
className,
changeChunkTextMode,
available,
selectAllChunk,
@@ -59,42 +61,46 @@ export default function ChunkResultBar({
changeChunkTextMode(value);
};
return (
<div className="flex pr-[25px]">
<div className={cn('flex justify-end gap-4', className)}>
<Segmented
className="gap-0 me-auto"
buttonSize="xs"
itemClassName="px-2"
options={textSelectOptions}
value={textSelectValue}
onChange={changeTextSelectValue}
/>
<div className="ml-auto"></div>
<div className="h-8 flex items-center gap-5">
<SearchInput
// style={{ width: 200 }}
placeholder={t('search')}
// icon={<SearchOutlined />}
onChange={handleInputChange}
value={searchString}
/>
<Popover>
<PopoverTrigger asChild>
<Button
variant={'ghost'}
// className="bg-bg-card text-text-secondary hover:bg-card"
>
<ListFilter />
</Button>
</PopoverTrigger>
<PopoverContent className="p-0 w-[200px]">
{filterContent}
</PopoverContent>
</Popover>
<Button
variant={'ghost'}
onClick={() => createChunk()}
// className="bg-bg-card text-primary hover:bg-card"
>
<Plus size={44} />
</Button>
</div>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
size="icon"
// className="bg-bg-card text-text-secondary hover:bg-card"
>
<LucideFilter />
</Button>
</PopoverTrigger>
<PopoverContent className="p-0 w-[200px]">
{filterContent}
</PopoverContent>
</Popover>
<SearchInput
className="w-28"
placeholder={t('search')}
onChange={handleInputChange}
value={searchString}
/>
<Button
variant="outline"
size="icon"
onClick={() => createChunk()}
// className="bg-bg-card text-primary hover:bg-card"
>
<Plus size={44} />
</Button>
{/* <div className="w-[20px]"></div>
<div className="w-[20px]"></div> */}
</div>

View File

@@ -24,6 +24,7 @@ import DocumentHeader from '@/components/document-preview/document-header';
import { useGetDocumentUrl } from '@/components/document-preview/hooks';
import { PageHeader } from '@/components/page-header';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import message from '@/components/ui/message';
import {
RAGFlowPagination,
@@ -174,7 +175,7 @@ const Chunk = () => {
}, [documentInfo]);
return (
<>
<main className="h-dvh flex flex-col">
<PageHeader>
<Button
variant="outline"
@@ -186,49 +187,58 @@ const Chunk = () => {
{t('common.back')}
</Button>
</PageHeader>
<div className={styles.chunkPage}>
<div className="flex flex-1 gap-8">
<div className="w-2/5">
<div className="h-[100px] flex flex-col justify-end pb-[5px]">
<DocumentHeader {...documentInfo} />
</div>
<section className={styles.documentPreview}>
<Card className="mx-5 mb-5 flex-1 h-0 p-0 bg-transparent shadow-none">
<CardContent className="p-0 h-full flex flex-row divide-x-0.5 rtl:divide-x-reverse">
<article className="w-2/5 flex flex-col">
<DocumentHeader className="flex-0 p-5 pb-0" {...documentInfo} />
<div className="flex-1 h-0 p-5 pt-2.5">
<DocumentPreview
className={styles.documentPreview}
fileType={fileType}
highlights={highlights}
setWidthAndHeight={setWidthAndHeight}
url={fileUrl}
></DocumentPreview>
</section>
</div>
<div
/>
</div>
</article>
<article
className={classNames(
{ [styles.pagePdfWrapper]: isPdf },
'flex flex-col w-3/5',
)}
>
<Spin spinning={loading} className={styles.spin} size="large">
<div className="h-[100px] flex flex-col justify-end pb-[5px]">
<div>
<h2 className="text-[24px]">{t('chunk.chunkResult')}</h2>
<div className="text-[14px] text-text-secondary">
{t('chunk.chunkResultTip')}
</div>
</div>
<header className="flex-0 p-5 pb-2.5 border-b-0.5 border-b-border-button">
<h2 className="text-[24px]">{t('chunk.chunkResult')}</h2>
<div className="text-[14px] text-text-secondary">
{t('chunk.chunkResultTip')}
</div>
<div className=" rounded-[16px] bg-[#FFF]/10 pl-[20px] pb-[20px] pt-[20px] box-border mb-2">
<ChunkResultBar
handleInputChange={handleInputChange}
searchString={searchString}
changeChunkTextMode={changeChunkTextMode}
createChunk={showChunkUpdatingModal}
available={available}
selectAllChunk={selectAllChunk}
handleSetAvailable={handleSetAvailable}
/>
<div className="pt-[5px] pb-[5px]">
</header>
<Spin spinning={loading} className="flex-1 h-0" size="large">
<div className="relative @container h-full px-5 pb-5 overflow-x-hidden overflow-y-auto">
<div
className="
sticky top-0 z-[1] bg-bg-base space-y-4 py-5
@4xl:flex @4xl:justify-between @4xl:items-center
@4xl:space-y-0 @4xl:gap-4
"
role="toolbar"
>
<ChunkResultBar
className="@4xl:order-2"
handleInputChange={handleInputChange}
searchString={searchString}
changeChunkTextMode={changeChunkTextMode}
createChunk={showChunkUpdatingModal}
available={available}
selectAllChunk={selectAllChunk}
handleSetAvailable={handleSetAvailable}
/>
<CheckboxSets
className="h-8"
selectAllChunk={selectAllChunk}
switchChunk={handleSwitchChunk}
removeChunk={handleRemoveChunk}
@@ -236,35 +246,27 @@ const Chunk = () => {
selectedChunkIds={selectedChunkIds}
/>
</div>
<div className={styles.pageContent}>
<div
className={classNames(
styles.chunkContainer,
{
[styles.chunkOtherContainer]: !isPdf,
},
'flex flex-col gap-4',
)}
>
{chunkList.map((item) => (
<ChunkCard
item={item}
key={item.chunk_id}
editChunk={showChunkUpdatingModal}
checked={selectedChunkIds.some(
(x) => x === item.chunk_id,
)}
handleCheckboxClick={handleSingleCheckboxClick}
switchChunk={handleSwitchChunk}
clickChunkCard={handleChunkCardClick}
selected={item.chunk_id === selectedChunkId}
textMode={textMode}
t={dataUpdatedAt}
></ChunkCard>
))}
</div>
<div className="space-y-4">
{chunkList.map((item) => (
<ChunkCard
item={item}
key={item.chunk_id}
editChunk={showChunkUpdatingModal}
checked={selectedChunkIds.some(
(x) => x === item.chunk_id,
)}
handleCheckboxClick={handleSingleCheckboxClick}
switchChunk={handleSwitchChunk}
clickChunkCard={handleChunkCardClick}
selected={item.chunk_id === selectedChunkId}
textMode={textMode}
t={dataUpdatedAt}
/>
))}
</div>
<div className={styles.pageFooter}>
<footer className="mt-5">
<RAGFlowPagination
pageSize={pagination.pageSize}
current={pagination.current}
@@ -272,13 +274,14 @@ const Chunk = () => {
onChange={(page, pageSize) => {
onPaginationChange(page, pageSize);
}}
></RAGFlowPagination>
</div>
/>
</footer>
</div>
</Spin>
</div>
</div>
</div>
</article>
</CardContent>
</Card>
{chunkUpdatingVisible && (
<CreatingModal
doc_id={documentId}
@@ -290,7 +293,7 @@ const Chunk = () => {
parserId={documentInfo.parser_id}
/>
)}
</>
</main>
);
};

View File

@@ -1,11 +1,11 @@
import { CustomTimeline, TimelineNode } from '@/components/originui/timeline';
import {
Blocks,
File,
FilePlay,
FileStack,
Heading,
ListPlus,
LucideBlocks,
LucideFile,
LucideFilePlay,
LucideFileStack,
LucideHeading,
LucideListPlus,
} from 'lucide-react';
import { useMemo } from 'react';
import { TimelineNodeType } from '../../constant';
@@ -21,28 +21,28 @@ export type ITimelineNodeObj = {
export const TimelineNodeObj = {
[TimelineNodeType.begin]: {
title: 'File',
icon: <File size={13} />,
icon: <LucideFile className="size-[1em]" />,
clickable: false,
},
[TimelineNodeType.parser]: {
title: 'Parser',
icon: <FilePlay size={13} />,
icon: <LucideFilePlay className="size-[1em]" />,
},
[TimelineNodeType.contextGenerator]: {
title: 'Context Generator',
icon: <FileStack size={13} />,
icon: <LucideFileStack className="size-[1em]" />,
},
[TimelineNodeType.titleSplitter]: {
title: 'Title Splitter',
icon: <Heading size={13} />,
icon: <LucideHeading className="size-[1em]" />,
},
[TimelineNodeType.characterSplitter]: {
title: 'Character Splitter',
icon: <Blocks size={13} />,
icon: <LucideBlocks className="size-[1em]" />,
},
[TimelineNodeType.tokenizer]: {
title: 'Tokenizer',
icon: <ListPlus size={13} />,
icon: <LucideListPlus className="size-[1em]" />,
clickable: false,
},
};
@@ -80,6 +80,7 @@ const TimelineDataFlow = ({
onStepChange={handleStepChange}
orientation="horizontal"
lineStyle="solid"
lineColor="rgb(var(--))"
nodeSize={24}
activeStyle={{
nodeSize: 30,

View File

@@ -19,27 +19,21 @@ import { useGetDocumentUrl } from '@/components/document-preview/hooks';
import { TimelineNode } from '@/components/originui/timeline';
import { PageHeader } from '@/components/page-header';
import Spotlight from '@/components/spotlight';
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from '@/components/ui/breadcrumb';
import { Button } from '@/components/ui/button';
import { Modal } from '@/components/ui/modal/modal';
import { AgentCategory } from '@/constants/agent';
import { AgentCategory, AgentQuery } from '@/constants/agent';
import { Images } from '@/constants/common';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
import { Routes } from '@/routes';
import { LucideArrowBigLeft } from 'lucide-react';
import TimelineDataFlow from './components/time-line';
import { TimelineNodeType } from './constant';
import styles from './index.module.less';
import { IDslComponent, IPipelineFileLogDetail } from './interface';
import ParserContainer from './parser';
const Chunk = () => {
const DataflowResult = () => {
const { isReadOnly, knowledgeId, agentId, agentTitle, documentExtension } =
useGetPipelineResultSearchParams();
@@ -158,46 +152,22 @@ const Chunk = () => {
return (
<>
<PageHeader>
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink
onClick={() => {
if (knowledgeId) {
navigateToDatasetList();
}
if (agentId) {
navigateToAgents();
}
}}
>
{knowledgeId ? t('knowledgeDetails.dataset') : t('header.flow')}
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink
onClick={() => {
if (knowledgeId) {
navigateToDatasetOverview(knowledgeId)();
}
if (isAgent) {
navigateToAgent(agentId, AgentCategory.DataflowCanvas)();
}
}}
>
{knowledgeId ? t('knowledgeDetails.overview') : agentTitle}
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>
{knowledgeId ? documentInfo?.name : t('flow.viewResult')}
</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
<Button
asLink
variant="outline"
to={
knowledgeId
? `${Routes.DatasetBase}${Routes.DataSetOverview}/${knowledgeId}`
: isAgent
? `${Routes.Agent}/${agentId}?${AgentQuery.Category}=${AgentCategory.DataflowCanvas}`
: '#'
}
>
<LucideArrowBigLeft />
{t('common.back')}
</Button>
</PageHeader>
{type === 'dataflow' && (
<div className=" absolute ml-[50%] translate-x-[-50%] top-4 flex justify-center">
<TimelineDataFlow
@@ -264,4 +234,4 @@ const Chunk = () => {
);
};
export default Chunk;
export default DataflowResult;

View File

@@ -4,7 +4,7 @@ import { DatePicker } from '@/components/ui/date-picker';
import { Input } from '@/components/ui/input';
import { formatDate } from '@/utils/date';
import { ColumnDef, Row, Table } from '@tanstack/react-table';
import { ListChevronsDownUp, Settings, Trash2 } from 'lucide-react';
import { ListChevronsDownUp, LucidePencil, Trash2 } from 'lucide-react';
import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
@@ -147,7 +147,7 @@ export const useMetadataColumns = ({
header: () => <span>{t('knowledgeDetails.metadata.description')}</span>,
cell: ({ row }) => (
<div className="text-sm truncate max-w-32">
{row.getValue('description')}
{row.getValue('description') || '-'}
</div>
),
},
@@ -347,17 +347,17 @@ export const useMetadataColumns = ({
cell: ({ row }) => (
<div className=" flex opacity-0 group-hover:opacity-100 gap-2">
<Button
variant={'ghost'}
className="bg-transparent px-1 py-0"
variant="ghost"
size="icon-sm"
onClick={() => {
handleEditValueRow(row.original, row.index);
}}
>
<Settings />
<LucidePencil />
</Button>
<Button
variant={'delete'}
className="p-0 bg-transparent"
variant="delete"
size="icon-sm"
onClick={() => {
setDeleteDialogContent({
visible: true,

View File

@@ -321,36 +321,8 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
>
<>
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<div className="w-1/2">
{secondTitle || t('knowledgeDetails.metadata.metadata')}
</div>
<div>
{/* {metadataType === MetadataType.Manage && (
<Button
variant={'ghost'}
className="border border-border-button"
type="button"
onClick={handleMenuClick(Routes.DataSetSetting, {
openMetadata: true,
})}
>
{t('knowledgeDetails.metadata.toMetadataSetting')}
</Button>
)} */}
{isCanAdd && activeTab !== 'built-in' && (
<Button
variant={'ghost'}
className="border border-border-button"
type="button"
onClick={handAddValueRow}
data-testid={addButtonTestId}
>
<Plus />
{t('common.add')}
</Button>
)}
</div>
<div className="flex gap-4 items-center justify-between text-truncate">
{secondTitle || t('knowledgeDetails.metadata.metadata')}
</div>
{rowSelectionIsEmpty || (
@@ -366,14 +338,43 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
value={activeTab}
onValueChange={(v) => setActiveTab(v as MetadataSettingsTab)}
>
<TabsList className="w-fit">
<TabsTrigger value="generation">
{t('knowledgeDetails.metadata.generation')}
</TabsTrigger>
<TabsTrigger value="built-in">
{t('knowledgeDetails.metadata.builtIn')}
</TabsTrigger>
</TabsList>
<div className="flex gap-4 items-center justify-between">
<TabsList className="w-fit">
<TabsTrigger value="generation">
{t('knowledgeDetails.metadata.generation')}
</TabsTrigger>
<TabsTrigger value="built-in">
{t('knowledgeDetails.metadata.builtIn')}
</TabsTrigger>
</TabsList>
<div>
{/* {metadataType === MetadataType.Manage && (
<Button
variant={'ghost'}
className="border border-border-button"
type="button"
onClick={handleMenuClick(Routes.DataSetSetting, {
openMetadata: true,
})}
>
{t('knowledgeDetails.metadata.toMetadataSetting')}
</Button>
)} */}
{isCanAdd && activeTab !== 'built-in' && (
<Button
variant="outline"
type="button"
onClick={handAddValueRow}
data-testid={addButtonTestId}
>
<Plus />
{t('common.add')}
</Button>
)}
</div>
</div>
<TabsContent value="generation">
<Table rootClassName="max-h-[800px]">
<TableHeader>

View File

@@ -283,8 +283,8 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
metaData.valueType === metadataValueTypeEnum['list'] && (
<div>
<Button
variant={'ghost'}
className="border border-border-button"
variant="outline"
size="icon"
onClick={handleAddValue}
data-testid={addValueButtonTestId}
>

View File

@@ -31,13 +31,14 @@ const CategoryPanel = ({ chunkMethod }: { chunkMethod: string }) => {
}, [chunkMethod]);
return (
<section>
<div>
{imageList.length > 0 ? (
<>
<h5 className="font-semibold text-base mt-0 mb-1">
{`"${item.title}" ${t('methodTitle')}`}
</h5>
<p
className="[&_ul]:list-disc [&_ol]:list-decimal [&_:is(ul,ol)]:pl-8"
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(item.description),
}}
@@ -68,7 +69,7 @@ const CategoryPanel = ({ chunkMethod }: { chunkMethod: string }) => {
</div>
)}
{chunkMethod === 'tag' && <TagTabs></TagTabs>}
</section>
</div>
);
};

View File

@@ -1,7 +1,8 @@
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { cn } from '@/lib/utils';
import { t } from 'i18next';
import { X } from 'lucide-react';
import { LucideX } from 'lucide-react';
import { useState } from 'react';
import CategoryPanel from './category-panel';
@@ -20,20 +21,25 @@ const ChunkMethodLearnMore = ({ parserId }: { parserId: string }) => {
{t('knowledgeDetails.learnMore')}
</Button>
</div>
<div
className="bg-[#FFF]/10 p-[20px] rounded-[12px] mt-[10px] relative flex-1 overflow-auto"
<Card
as="article"
className="relative flex-1 overflow-auto mt-4"
style={{ display: visible ? 'block' : 'none' }}
>
<CategoryPanel chunkMethod={parserId}></CategoryPanel>
<div
className="absolute right-1 top-1 cursor-pointer hover:text-[#FFF]/30"
onClick={() => {
setVisible(false);
}}
<Button
className="absolute right-2 top-2"
variant="ghost"
size="icon-xs"
onClick={() => setVisible(false)}
>
<X />
</div>
</div>
<LucideX />
</Button>
<CardContent className="p-5">
<CategoryPanel chunkMethod={parserId}></CategoryPanel>
</CardContent>
</Card>
</div>
);
};

View File

@@ -415,8 +415,8 @@ export function AutoMetadata({
avatar={knowledgeBase.avatar}
name={knowledgeBase.name}
className="size-8"
></RAGFlowAvatar>
<div className=" text-text-primary text-base space-y-1 overflow-hidden">
/>
<div className="text-text-primary text-base space-y-1 truncate overflow-hidden">
{knowledgeBase.name}
</div>
</div>

View File

@@ -46,7 +46,6 @@ export function useDatasetTableColumns({
id: 'select',
header: ({ table }) => (
<Checkbox
className="size-3"
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && 'indeterminate')
@@ -57,7 +56,6 @@ export function useDatasetTableColumns({
),
cell: ({ row }) => (
<Checkbox
className="size-3"
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"

View File

@@ -1,15 +1,16 @@
import { ElementDatum, Graph, IElementEvent } from '@antv/g6';
import isEmpty from 'lodash/isEmpty';
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useCallback, useEffect, useId, useMemo, useRef } from 'react';
import { buildNodesAndCombos, defaultComboLabel } from './util';
import { useIsDarkTheme } from '@/components/theme-provider';
import { cn } from '@/lib/utils';
import styles from './index.module.less';
const TooltipColorMap = {
combo: 'red',
node: 'black',
edge: 'blue',
combo: 'text-red-600',
node: 'text-black',
edge: 'text-blue-600',
};
interface IProps {
@@ -18,6 +19,7 @@ interface IProps {
}
const ForceGraph = ({ data, show }: IProps) => {
const tooltipId = useId();
const containerRef = useRef<HTMLDivElement>(null);
const graphRef = useRef<Graph | null>(null);
const isDark = useIsDarkTheme();
@@ -52,23 +54,46 @@ const ForceGraph = ({ data, show }: IProps) => {
getContent: (e: IElementEvent, items: ElementDatum) => {
if (Array.isArray(items)) {
if (items.some((x) => x?.isCombo)) {
return `<p style="font-weight:600;color:red">${items?.[0]?.data?.label}</p>`;
return `<p class="font-semibold text-red-600">${items?.[0]?.data?.label}</p>`;
}
let result = ``;
items.forEach((item) => {
result += `<section style="color:${TooltipColorMap[e['targetType'] as keyof typeof TooltipColorMap]};"><h3>${item?.id}</h3>`;
if (item?.entity_type) {
result += `<div style="padding-bottom: 6px;"><b>Entity type: </b>${item?.entity_type}</div>`;
}
if (item?.weight) {
result += `<div><b>Weight: </b>${item?.weight}</div>`;
}
if (item?.description) {
result += `<p>${item?.description}</p>`;
}
});
return result + '</section>';
return items
.flatMap((item) => {
return [
'<div ',
`id="${tooltipId}"`,
`aria-label="${item?.id}"`,
`role="tooltip"`,
`class="${TooltipColorMap[e['targetType'] as keyof typeof TooltipColorMap]}"`,
'>',
`<h3>${item?.id}</h3>`,
'<dl class="mb-1 empty:hidden">',
...(item?.entity_type
? [
'<div class="flex items-center gap-[.5ch]">',
'<dt><b>Entity type: </b></dt>',
`<dd>${item.entity_type}</dd>`,
'</div>',
]
: []),
...(item?.weight
? [
'<div class="flex items-center gap-[.5ch]">',
'<dt><b>Weight: </b></dt>',
`<dd>${item.weight}</dd>`,
'</div>',
]
: []),
'</dl>',
item.description
? `<p class="text-xs">${item.description}</p>`
: '',
'</div>',
];
})
.join('');
}
return undefined;
},
},
@@ -82,34 +107,32 @@ const ForceGraph = ({ data, show }: IProps) => {
node: {
style: {
size: (d) => {
let size = 100 + ((d.rank as number) || 0) * 5;
size = size > 300 ? 300 : size;
return size;
const size = 100 + ((d.rank as number) || 0) * 5;
return Math.min(size, 300);
},
labelText: (d) => d.id,
labelFill: isDark ? 'rgba(255,255,255,1)' : 'rgba(0,0,0,1)',
// labelPadding: 30,
labelFontSize: 40,
// labelOffsetX: 20,
// labelOffsetX: 20,
labelOffsetY: 20,
labelPlacement: 'center',
labelWordWrap: true,
},
palette: {
type: 'group',
field: (d) => {
return d?.entity_type as string;
},
field: (d) => d?.entity_type as string,
},
},
edge: {
style: (model) => {
const weight: number = Number(model?.weight) || 2;
const lineWeight = weight * 4;
return {
stroke: isDark ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.5)',
lineDash: [10, 10],
lineWidth: lineWeight > 8 ? 8 : lineWeight,
lineWidth: Math.min(weight * 4, 8),
};
},
},
@@ -149,12 +172,9 @@ const ForceGraph = ({ data, show }: IProps) => {
return (
<div
ref={containerRef}
className={styles.forceContainer}
style={{
width: '100%',
height: '100%',
display: show ? 'block' : 'none',
}}
className={cn(styles.forceContainer, 'size-full', !show && 'hidden')}
aria-haspopup="true"
aria-describedby={tooltipId}
/>
);
};

View File

@@ -1,5 +1,7 @@
.forceContainer {
:global(.tooltip) {
border-radius: 10px !important;
padding: 0.5rem 0.75rem !important;
border-radius: 0.5rem !important;
font-family: var(--font-sans) !important;
}
}

View File

@@ -1,7 +1,8 @@
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
import { useFetchKnowledgeGraph } from '@/hooks/use-knowledge-request';
import { Trash2 } from 'lucide-react';
import { LucideTrash2 } from 'lucide-react';
import React from 'react';
import { useTranslation } from 'react-i18next';
import ForceGraph from './force-graph';
@@ -13,18 +14,23 @@ const KnowledgeGraph: React.FC = () => {
const { handleDeleteKnowledgeGraph } = useDeleteKnowledgeGraph();
return (
<section className={'w-full h-[90dvh] relative p-6'}>
<Card
as="article"
className="relative me-5 mb-5 p-0 bg-transparent shadow-none overflow-hidden"
>
<ConfirmDeleteDialog onOk={handleDeleteKnowledgeGraph}>
<Button
variant="outline"
size={'sm'}
className="absolute right-0 top-0 z-50"
size="sm"
className="absolute right-5 top-5 z-50"
>
<Trash2 /> {t('common.delete')}
<LucideTrash2 />
{t('common.delete')}
</Button>
</ConfirmDeleteDialog>
<ForceGraph data={data?.graph} show></ForceGraph>
</section>
<ForceGraph data={data?.graph} show />
</Card>
);
};

View File

@@ -91,7 +91,7 @@ export default function Datasets() {
value={filterValue}
filters={owners}
onChange={handleFilterSubmit}
className="px-8"
className="px-8 mb-4"
icon={'datasets'}
>
<Button onClick={showModal}>