Features: Memory page rendering and other bug fixes (#11784)

### What problem does this PR solve?

Features: Memory page rendering and other bug fixes
- Rendering of the Memory list page
- Rendering of the message list page in Memory
- Fixed an issue where the empty state was incorrectly displayed when
search criteria were applied
- Added a web link for the API-Key
- modifying the index_mode attribute of the Confluence data source.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
chanx
2025-12-08 10:17:56 +08:00
committed by GitHub
parent 3285f09c92
commit 660fa8888b
55 changed files with 2047 additions and 218 deletions

View File

@ -0,0 +1,3 @@
export enum MemoryApiAction {
FetchMemoryDetail = 'fetchMemoryDetail',
}

View File

@ -0,0 +1,59 @@
import { useHandleSearchChange } from '@/hooks/logic-hooks';
import { getMemoryDetailById } from '@/services/memory-service';
import { useQuery } from '@tanstack/react-query';
import { useParams, useSearchParams } from 'umi';
import { MemoryApiAction } from '../constant';
import { IMessageTableProps } from '../memory-message/interface';
export const useFetchMemoryMessageList = (props?: {
refreshCount?: number;
}) => {
const { refreshCount } = props || {};
const { id } = useParams();
const [searchParams] = useSearchParams();
const memoryBaseId = searchParams.get('id') || id;
const { handleInputChange, searchString, pagination, setPagination } =
useHandleSearchChange();
let queryKey: (MemoryApiAction | number)[] = [
MemoryApiAction.FetchMemoryDetail,
];
if (typeof refreshCount === 'number') {
queryKey = [MemoryApiAction.FetchMemoryDetail, refreshCount];
}
const { data, isFetching: loading } = useQuery<IMessageTableProps>({
queryKey: [...queryKey, searchString, pagination],
initialData: {} as IMessageTableProps,
gcTime: 0,
queryFn: async () => {
if (memoryBaseId) {
const { data } = await getMemoryDetailById(memoryBaseId as string, {
// filter: {
// agent_id: '',
// },
keyword: searchString,
page: pagination.current,
page_size: pagination.pageSize,
});
// setPagination({
// page: data?.page ?? 1,
// pageSize: data?.page_size ?? 10,
// total: data?.total ?? 0,
// });
return data?.data ?? {};
} else {
return {};
}
},
});
return {
data,
loading,
handleInputChange,
searchString,
pagination,
setPagination,
};
};

View File

@ -0,0 +1,59 @@
import { useHandleSearchChange } from '@/hooks/logic-hooks';
import { IMemory } from '@/pages/memories/interface';
import { getMemoryDetailById } from '@/services/memory-service';
import { useQuery } from '@tanstack/react-query';
import { useParams, useSearchParams } from 'umi';
import { MemoryApiAction } from '../constant';
export const useFetchMemoryBaseConfiguration = (props?: {
refreshCount?: number;
}) => {
const { refreshCount } = props || {};
const { id } = useParams();
const [searchParams] = useSearchParams();
const memoryBaseId = searchParams.get('id') || id;
const { handleInputChange, searchString, pagination, setPagination } =
useHandleSearchChange();
let queryKey: (MemoryApiAction | number)[] = [
MemoryApiAction.FetchMemoryDetail,
];
if (typeof refreshCount === 'number') {
queryKey = [MemoryApiAction.FetchMemoryDetail, refreshCount];
}
const { data, isFetching: loading } = useQuery<IMemory>({
queryKey: [...queryKey, searchString, pagination],
initialData: {} as IMemory,
gcTime: 0,
queryFn: async () => {
if (memoryBaseId) {
const { data } = await getMemoryDetailById(memoryBaseId as string, {
// filter: {
// agent_id: '',
// },
keyword: searchString,
page: pagination.current,
page_size: pagination.size,
});
// setPagination({
// page: data?.page ?? 1,
// pageSize: data?.page_size ?? 10,
// total: data?.total ?? 0,
// });
return data?.data ?? {};
} else {
return {};
}
},
});
return {
data,
loading,
handleInputChange,
searchString,
pagination,
setPagination,
};
};

View File

@ -0,0 +1,17 @@
import Spotlight from '@/components/spotlight';
import { Outlet } from 'umi';
import { SideBar } from './sidebar';
export default function DatasetWrapper() {
return (
<section className="flex h-full flex-col w-full">
<div className="flex flex-1 min-h-0">
<SideBar></SideBar>
<div className=" relative flex-1 overflow-auto border-[0.5px] border-border-button p-5 rounded-md mr-5 mb-5">
<Spotlight />
<Outlet />
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,51 @@
import ListFilterBar from '@/components/list-filter-bar';
import { t } from 'i18next';
import { useFetchMemoryMessageList } from '../hooks/use-memory-messages';
import { MemoryTable } from './message-table';
export default function MemoryMessage() {
const {
searchString,
// documents,
data,
pagination,
handleInputChange,
setPagination,
// filterValue,
// handleFilterSubmit,
loading,
} = useFetchMemoryMessageList();
return (
<div className="flex flex-col gap-2">
<ListFilterBar
title="Dataset"
onSearchChange={handleInputChange}
searchString={searchString}
// value={filterValue}
// onChange={handleFilterSubmit}
// onOpenChange={onOpenChange}
// filters={filters}
leftPanel={
<div className="items-start">
<div className="pb-1">{t('knowledgeDetails.subbarFiles')}</div>
<div className="text-text-secondary text-sm">
{t('knowledgeDetails.datasetDescription')}
</div>
</div>
}
></ListFilterBar>
<MemoryTable
messages={data?.messages?.message_list ?? []}
pagination={pagination}
setPagination={setPagination}
total={data?.messages?.total ?? 0}
// rowSelection={rowSelection}
// setRowSelection={setRowSelection}
// loading={loading}
></MemoryTable>
<div className="flex items-center gap-2">
<div className="h-4 w-4 rounded-full bg-text ">message</div>
</div>
</div>
);
}

View File

@ -0,0 +1,19 @@
export interface IMessageInfo {
message_id: number;
message_type: 'semantic' | 'raw' | 'procedural';
source_id: string | '-';
id: string;
user_id: string;
agent_id: string;
agent_name: string;
session_id: string;
valid_at: string;
invalid_at: string;
forget_at: string;
status: boolean;
}
export interface IMessageTableProps {
messages: { message_list: Array<IMessageInfo>; total: number };
storage_type: string;
}

View File

@ -0,0 +1,225 @@
import {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
import * as React from 'react';
import { EmptyType } from '@/components/empty/constant';
import Empty from '@/components/empty/empty';
import { Button } from '@/components/ui/button';
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
import { Switch } from '@/components/ui/switch';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { Pagination } from '@/interfaces/common';
import { t } from 'i18next';
import { pick } from 'lodash';
import { Eraser, TextSelect } from 'lucide-react';
import { useMemo } from 'react';
import { IMessageInfo } from './interface';
export type MemoryTableProps = {
messages: Array<IMessageInfo>;
total: number;
pagination: Pagination;
setPagination: (params: { page: number; pageSize: number }) => void;
};
export function MemoryTable({
messages,
total,
pagination,
setPagination,
}: MemoryTableProps) {
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[],
);
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({});
// Define columns for the memory table
const columns: ColumnDef<IMessageInfo>[] = useMemo(
() => [
{
accessorKey: 'session_id',
header: () => <span>{t('memoryDetail.messages.sessionId')}</span>,
cell: ({ row }) => (
<div className="text-sm font-medium ">
{row.getValue('session_id')}
</div>
),
},
{
accessorKey: 'agent_name',
header: () => <span>{t('memoryDetail.messages.agent')}</span>,
cell: ({ row }) => (
<div className="text-sm font-medium ">
{row.getValue('agent_name')}
</div>
),
},
{
accessorKey: 'message_type',
header: () => <span>{t('memoryDetail.messages.type')}</span>,
cell: ({ row }) => (
<div className="text-sm font-medium capitalize">
{row.getValue('message_type')}
</div>
),
},
{
accessorKey: 'valid_at',
header: () => <span>{t('memoryDetail.messages.validDate')}</span>,
cell: ({ row }) => (
<div className="text-sm ">{row.getValue('valid_at')}</div>
),
},
{
accessorKey: 'forget_at',
header: () => <span>{t('memoryDetail.messages.forgetAt')}</span>,
cell: ({ row }) => (
<div className="text-sm ">{row.getValue('forget_at')}</div>
),
},
{
accessorKey: 'source_id',
header: () => <span>{t('memoryDetail.messages.source')}</span>,
cell: ({ row }) => (
<div className="text-sm ">{row.getValue('source_id')}</div>
),
},
{
accessorKey: 'status',
header: () => <span>{t('memoryDetail.messages.enable')}</span>,
cell: ({ row }) => {
const isEnabled = row.getValue('status') as boolean;
return (
<div className="flex items-center">
<Switch defaultChecked={isEnabled} onChange={() => {}} />
</div>
);
},
},
{
accessorKey: 'action',
header: () => <span>{t('memoryDetail.messages.action')}</span>,
meta: {
cellClassName: 'w-12',
},
cell: () => (
<div className=" hidden group-hover:flex">
<Button variant={'ghost'} className="bg-transparent">
<TextSelect />
</Button>
<Button
variant={'delete'}
className="bg-transparent"
aria-label="Edit"
>
<Eraser />
</Button>
</div>
),
},
],
[],
);
const currentPagination = useMemo(() => {
return {
pageIndex: (pagination.current || 1) - 1,
pageSize: pagination.pageSize || 10,
};
}, [pagination]);
const table = useReactTable({
data: messages,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
manualPagination: true,
state: {
sorting,
columnFilters,
columnVisibility,
pagination: currentPagination,
},
rowCount: total,
});
return (
<div className="w-full">
<Table rootClassName="max-h-[calc(100vh-222px)]">
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody className="relative">
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && 'selected'}
className="group"
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
<Empty type={EmptyType.Data} />
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
<div className="flex items-center justify-end py-4 absolute bottom-3 right-3">
<RAGFlowPagination
{...pick(pagination, 'current', 'pageSize')}
total={total}
onChange={(page, pageSize) => {
setPagination({ page, pageSize });
}}
/>
</div>
</div>
);
}

View File

@ -0,0 +1,13 @@
export default function MemoryMessage() {
return (
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<div className="h-4 w-4 rounded-full bg-text-secondary">11</div>
<div className="h-4 w-4 rounded-full bg-text-secondary">11</div>
</div>
<div className="flex items-center gap-2">
<div className="h-4 w-4 rounded-full bg-text ">setting</div>
</div>
</div>
);
}

View File

@ -0,0 +1,17 @@
import { Routes } from '@/routes';
import { useCallback } from 'react';
import { useNavigate, useParams } from 'umi';
export const useHandleMenuClick = () => {
const navigate = useNavigate();
const { id } = useParams();
const handleMenuClick = useCallback(
(key: Routes) => () => {
navigate(`${Routes.Memory}${key}/${id}`);
},
[id, navigate],
);
return { handleMenuClick };
};

View File

@ -0,0 +1,88 @@
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
import { Button } from '@/components/ui/button';
import { useSecondPathName } from '@/hooks/route-hook';
import { cn, formatBytes } from '@/lib/utils';
import { Routes } from '@/routes';
import { formatPureDate } from '@/utils/date';
import { Banknote, Logs } from 'lucide-react';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useFetchMemoryBaseConfiguration } from '../hooks/use-memory-setting';
import { useHandleMenuClick } from './hooks';
type PropType = {
refreshCount?: number;
};
export function SideBar({ refreshCount }: PropType) {
const pathName = useSecondPathName();
const { handleMenuClick } = useHandleMenuClick();
// refreshCount: be for avatar img sync update on top left
const { data } = useFetchMemoryBaseConfiguration({ refreshCount });
const { t } = useTranslation();
const items = useMemo(() => {
const list = [
{
icon: <Logs className="size-4" />,
label: t(`knowledgeDetails.overview`),
key: Routes.MemoryMessage,
},
{
icon: <Banknote className="size-4" />,
label: t(`knowledgeDetails.configuration`),
key: Routes.MemorySetting,
},
];
return list;
}, [t]);
return (
<aside className="relative p-5 space-y-8">
<div className="flex gap-2.5 max-w-[200px] items-center">
<RAGFlowAvatar
avatar={data.avatar}
name={data.name}
className="size-16"
></RAGFlowAvatar>
<div className=" text-text-secondary text-xs space-y-1 overflow-hidden">
<h3 className="text-lg font-semibold line-clamp-1 text-text-primary text-ellipsis overflow-hidden">
{data.name}
</h3>
<div className="flex justify-between">
<span>
{data.doc_num} {t('knowledgeDetails.files')}
</span>
<span>{formatBytes(data.size)}</span>
</div>
<div>
{t('knowledgeDetails.created')} {formatPureDate(data.create_time)}
</div>
</div>
</div>
<div className="w-[200px] flex flex-col gap-5">
{items.map((item, itemIdx) => {
const active = '/' + pathName === item.key;
return (
<Button
key={itemIdx}
variant={active ? 'secondary' : 'ghost'}
className={cn(
'w-full justify-start gap-2.5 px-3 relative h-10 text-text-secondary',
{
'bg-bg-card': active,
'text-text-primary': active,
},
)}
onClick={handleMenuClick(item.key)}
>
{item.icon}
<span>{item.label}</span>
</Button>
);
})}
</div>
</aside>
);
}