mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-23 15:06:50 +08:00
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:
3
web/src/pages/memory/constant.tsx
Normal file
3
web/src/pages/memory/constant.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export enum MemoryApiAction {
|
||||
FetchMemoryDetail = 'fetchMemoryDetail',
|
||||
}
|
||||
59
web/src/pages/memory/hooks/use-memory-messages.ts
Normal file
59
web/src/pages/memory/hooks/use-memory-messages.ts
Normal 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,
|
||||
};
|
||||
};
|
||||
59
web/src/pages/memory/hooks/use-memory-setting.ts
Normal file
59
web/src/pages/memory/hooks/use-memory-setting.ts
Normal 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,
|
||||
};
|
||||
};
|
||||
17
web/src/pages/memory/index.tsx
Normal file
17
web/src/pages/memory/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
51
web/src/pages/memory/memory-message/index.tsx
Normal file
51
web/src/pages/memory/memory-message/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
19
web/src/pages/memory/memory-message/interface.ts
Normal file
19
web/src/pages/memory/memory-message/interface.ts
Normal 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;
|
||||
}
|
||||
225
web/src/pages/memory/memory-message/message-table.tsx
Normal file
225
web/src/pages/memory/memory-message/message-table.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
13
web/src/pages/memory/memory-setting/index.tsx
Normal file
13
web/src/pages/memory/memory-setting/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
17
web/src/pages/memory/sidebar/hooks.tsx
Normal file
17
web/src/pages/memory/sidebar/hooks.tsx
Normal 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 };
|
||||
};
|
||||
88
web/src/pages/memory/sidebar/index.tsx
Normal file
88
web/src/pages/memory/sidebar/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user