feat: memory status (#12959)

### What problem does this PR solve?

Add memory status indicator and detail message dialog 

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
Jimmy Ben Klieve
2026-02-03 11:16:18 +08:00
committed by GitHub
parent ad06c042c4
commit fafaaa26c3
4 changed files with 140 additions and 2 deletions

View File

@ -91,6 +91,8 @@ function Root({ children }: React.PropsWithChildren) {
i18n.on('languageChanged', function (lng: string) {
storage.setLanguage(lng);
setLocal(getLocale(lng));
// Should reflect to <html lang="...">
document.documentElement.lang = lng;
});
return (

View File

@ -129,6 +129,17 @@ Procedural Memory: Learned skills, habits, and automated procedures.`,
delMemoryWarn: `After deletion, all messages in this memory will be deleted and cannot be retrieved by agents.`,
},
memory: {
taskLogDialog: {
title: 'Memory',
startTime: 'Start time',
status: 'Status',
details: 'Details',
success: 'Success',
running: 'Running',
failed: 'Failed',
},
messages: {
forget: 'Forget',
forgetMessageTip: 'Are you sure you want to forget?',

View File

@ -10,7 +10,17 @@ export interface IMessageInfo {
invalid_at: string;
forget_at: string;
status: boolean;
extract?: IMessageInfo[];
extract?: Omit<IMessageInfo, 'task'>[];
task: {
chunk_ids: string;
create_time: number;
digest: string;
doc_id: string;
from_page: number;
id: string;
progress: number;
progress_msg: string;
};
}
export interface IMessageTableProps {

View File

@ -5,6 +5,15 @@ import {
import { EmptyType } from '@/components/empty/constant';
import Empty from '@/components/empty/empty';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
import { Modal } from '@/components/ui/modal/modal';
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
import { Switch } from '@/components/ui/switch';
@ -27,6 +36,7 @@ import {
Row,
SortingState,
VisibilityState,
createColumnHelper,
flexRender,
getCoreRowModel,
getExpandedRowModel,
@ -35,6 +45,7 @@ import {
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
import dayjs from 'dayjs';
import { t } from 'i18next';
import { pick } from 'lodash';
import {
@ -57,6 +68,18 @@ export type MemoryTableProps = {
setPagination: (params: { page: number; pageSize: number }) => void;
};
const columnHelper = createColumnHelper<IMessageInfo>();
function getTaskStatus(progress: number) {
if (progress >= 1) {
return 'success';
} else if (progress > 0 && progress < 1) {
return 'running';
} else {
return 'failed';
}
}
export function MemoryTable({
messages,
total,
@ -198,6 +221,98 @@ export function MemoryTable({
);
},
},
columnHelper.display({
id: 'task_progress',
cell: ({ row }) => {
const { task } = row.original;
if (!task) {
return null;
}
const taskStatus = getTaskStatus(task.progress);
return (
<Dialog>
<DialogTrigger asChild>
<Button
variant="transparent"
size="icon"
className="border-0 size-8"
>
<div
className={cn('size-1 rounded-full', {
'bg-state-success': taskStatus === 'success',
'bg-state-error': taskStatus === 'failed',
'bg-state-warning': taskStatus === 'running',
})}
/>
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>{t('memory.taskLogDialog.title')}</DialogTitle>
</DialogHeader>
<dl className="space-y-4">
<div className="grid grid-rows-2 grid-cols-2 grid-flow-col items-center gap-x-4 gap-y-1">
<dt className="text-text-secondary">
{t('memory.taskLogDialog.startTime')}
</dt>
<dd className="text-sm">
{dayjs(task.create_time)
.locale(document.documentElement.lang)
.format('MM/DD/YYYY HH:mm:ss')}
</dd>
<dt className="text-text-secondary">
{t('memory.taskLogDialog.status')}
</dt>
<dd className="text-sm">
<div
className={cn(
'inline-flex items-center gap-1 text-xs rounded-full px-2 py-1',
{
'text-state-success bg-state-success-5':
taskStatus === 'success',
'text-state-error bg-state-error-5':
taskStatus === 'failed',
'text-state-warning bg-state-warning-5':
taskStatus === 'running',
},
)}
>
<div className="size-1 rounded-full bg-current" />
{t(`memory.taskLogDialog.${taskStatus}`)}
</div>
</dd>
</div>
<div className="space-y-1">
<dt className="text-text-secondary">
{t('memory.taskLogDialog.details')}
</dt>
<dd className="text-sm">
<div className="bg-bg-card rounded-lg p-2 max-h-64 overflow-auto">
<pre>
<code>{task.progress_msg}</code>
</pre>
</div>
</dd>
</div>
</dl>
<DialogFooter>
<DialogClose asChild>
<Button variant="ghost">{t('common.close')}</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
);
},
}),
{
accessorKey: 'action',
header: () => <span>{t('memory.messages.action')}</span>,
@ -244,7 +359,7 @@ export function MemoryTable({
data: messages,
columns,
onExpandedChange: setExpanded,
getSubRows: (row) => row.extract || undefined,
getSubRows: (row) => (row.extract as IMessageInfo[]) || undefined,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),