From 4f8e7ef7632d7696576e33392b896bddd58117b2 Mon Sep 17 00:00:00 2001 From: chanx <1243304602@qq.com> Date: Wed, 30 Jul 2025 09:48:51 +0800 Subject: [PATCH] Feat: Add agent-log-list page (#9076) ### What problem does this PR solve? Fix: Add agent-log-list page And RAPTOR:Save directly after enabling, incomplete form submission #3221 ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue) --- .../components/originui/time-range-picker.tsx | 186 ++++++++++ web/src/hooks/logic-hooks/navigate-hooks.ts | 8 + web/src/hooks/use-agent-request.ts | 30 +- web/src/interfaces/database/agent.ts | 35 ++ web/src/pages/agent/index.tsx | 17 +- web/src/pages/agents/agent-log-page.tsx | 319 ++++++++++++++++++ .../dataset/setting/chunk-method-form.tsx | 18 +- web/src/routes.ts | 6 + web/src/services/agent-service.ts | 12 + web/src/utils/api.ts | 2 + web/src/utils/date.ts | 11 + 11 files changed, 627 insertions(+), 17 deletions(-) create mode 100644 web/src/components/originui/time-range-picker.tsx create mode 100644 web/src/pages/agents/agent-log-page.tsx diff --git a/web/src/components/originui/time-range-picker.tsx b/web/src/components/originui/time-range-picker.tsx new file mode 100644 index 000000000..0ec1790ac --- /dev/null +++ b/web/src/components/originui/time-range-picker.tsx @@ -0,0 +1,186 @@ +import { + endOfMonth, + endOfYear, + format, + startOfMonth, + startOfYear, + subDays, + subMonths, + subYears, +} from 'date-fns'; +import { useEffect, useId, useState } from 'react'; + +import { Calendar, DateRange } from '@/components/originui/calendar'; +import { Button } from '@/components/ui/button'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover'; +import { cn } from '@/lib/utils'; +import { CalendarIcon } from 'lucide-react'; + +const CalendarComp = ({ + selectDateRange, + onSelect, + ...props +}: ITimeRangePickerProps) => { + const today = new Date(); + const yesterday = { + from: subDays(today, 1), + to: subDays(today, 1), + }; + const last7Days = { + from: subDays(today, 6), + to: today, + }; + const last30Days = { + from: subDays(today, 29), + to: today, + }; + const monthToDate = { + from: startOfMonth(today), + to: today, + }; + const lastMonth = { + from: startOfMonth(subMonths(today, 1)), + to: endOfMonth(subMonths(today, 1)), + }; + const yearToDate = { + from: startOfYear(today), + to: today, + }; + const lastYear = { + from: startOfYear(subYears(today, 1)), + to: endOfYear(subYears(today, 1)), + }; + const dateRangeList = [ + { key: 'yestday', value: yesterday }, + { key: 'last7Days', value: last7Days }, + { key: 'last30Days', value: last30Days }, + { key: 'monthToDate', value: monthToDate }, + { key: 'lastMonth', value: lastMonth }, + { key: 'yearToDate', value: yearToDate }, + { key: 'lastYear', value: lastYear }, + ]; + const [month, setMonth] = useState(today); + const [date, setDate] = useState(selectDateRange || last7Days); + useEffect(() => { + onSelect?.(date); + }, [date, onSelect]); + return ( +
+
+
+
+
+
+ + {dateRangeList.map((dateRange) => ( + + ))} +
+
+
+ { + if (newDate) { + setDate(newDate as DateRange); + } + }} + month={month} + onMonthChange={setMonth} + className="p-2" + {...props} + // disabled={[ + // { after: today }, // Dates before today + // ]} + /> +
+
+
+ ); +}; + +export type ITimeRangePickerProps = { + onSelect: (e: DateRange) => void; + selectDateRange: DateRange; + className?: string; +}; +const TimeRangePicker = ({ + onSelect, + selectDateRange, + ...props +}: ITimeRangePickerProps) => { + const id = useId(); + const today = new Date(); + const [date, setDate] = useState( + selectDateRange || { from: today, to: today }, + ); + const onChange = (e: DateRange | undefined) => { + if (!e) return; + setDate(e); + onSelect?.(e); + }; + return ( + + + + + + + + + ); +}; +export default TimeRangePicker; diff --git a/web/src/hooks/logic-hooks/navigate-hooks.ts b/web/src/hooks/logic-hooks/navigate-hooks.ts index 4633c3d45..a334bb0ea 100644 --- a/web/src/hooks/logic-hooks/navigate-hooks.ts +++ b/web/src/hooks/logic-hooks/navigate-hooks.ts @@ -54,6 +54,13 @@ export const useNavigatePage = () => { [navigate], ); + const navigateToAgentLogs = useCallback( + (id: string) => () => { + navigate(`${Routes.AgentLogPage}/${id}`); + }, + [navigate], + ); + const navigateToAgentTemplates = useCallback(() => { navigate(Routes.AgentTemplates); }, [navigate]); @@ -120,6 +127,7 @@ export const useNavigatePage = () => { navigateToChunk, navigateToAgents, navigateToAgent, + navigateToAgentLogs, navigateToAgentTemplates, navigateToSearchList, navigateToSearch, diff --git a/web/src/hooks/use-agent-request.ts b/web/src/hooks/use-agent-request.ts index 5f3930cc1..b3b972f27 100644 --- a/web/src/hooks/use-agent-request.ts +++ b/web/src/hooks/use-agent-request.ts @@ -1,13 +1,20 @@ import { FileUploadProps } from '@/components/file-upload'; import message from '@/components/ui/message'; import { AgentGlobals } from '@/constants/agent'; -import { ITraceData } from '@/interfaces/database/agent'; +import { + IAgentLogsRequest, + IAgentLogsResponse, + ITraceData, +} from '@/interfaces/database/agent'; import { DSL, IFlow, IFlowTemplate } from '@/interfaces/database/flow'; import { IDebugSingleRequestBody } from '@/interfaces/request/agent'; import i18n from '@/locales/config'; import { BeginId } from '@/pages/agent/constant'; import { useGetSharedChatSearchParams } from '@/pages/chat/shared-hooks'; -import agentService, { fetchTrace } from '@/services/agent-service'; +import agentService, { + fetchAgentLogsByCanvasId, + fetchTrace, +} from '@/services/agent-service'; import api from '@/utils/api'; import { buildMessageListWithUuid } from '@/utils/chat'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; @@ -558,3 +565,22 @@ export const useFetchAgentAvatar = (): { return { data, loading, refetch }; }; + +export const useFetchAgentLog = (searchParams: IAgentLogsRequest) => { + const { id } = useParams(); + const { data, isFetching: loading } = useQuery({ + queryKey: ['fetchAgentLog', id, searchParams], + initialData: {} as IAgentLogsResponse, + gcTime: 0, + queryFn: async () => { + console.log('useFetchAgentLog', searchParams); + const { data } = await fetchAgentLogsByCanvasId(id as string, { + ...searchParams, + }); + + return data?.data ?? []; + }, + }); + + return { data, loading }; +}; diff --git a/web/src/interfaces/database/agent.ts b/web/src/interfaces/database/agent.ts index eabd2cbd0..772d169ab 100644 --- a/web/src/interfaces/database/agent.ts +++ b/web/src/interfaces/database/agent.ts @@ -229,3 +229,38 @@ export interface ITraceData { component_id: string; trace: Array>; } + +export interface IAgentLogResponse { + id: string; + message: IAgentLogMessage[]; + update_date: string; + create_date: string; + update_time: number; + create_time: number; + round: number; + thumb_up: number; + errors: string; + source: string; + user_id: string; + dsl: string; + reference: IReference; +} +export interface IAgentLogsResponse { + total: number; + sessions: IAgentLogResponse[]; +} +export interface IAgentLogsRequest { + keywords?: string; + to_date?: string | Date; + from_date?: string | Date; + orderby?: string; + desc?: boolean; + page?: number; + page_size?: number; +} + +export interface IAgentLogMessage { + content: string; + role: 'user' | 'assistant'; + id: string; +} diff --git a/web/src/pages/agent/index.tsx b/web/src/pages/agent/index.tsx index f7d0326ae..f306a4ba6 100644 --- a/web/src/pages/agent/index.tsx +++ b/web/src/pages/agent/index.tsx @@ -25,7 +25,6 @@ import { CirclePlay, Download, History, - Key, LaptopMinimalCheck, Logs, ScreenShare, @@ -42,7 +41,6 @@ import { useGetBeginNodeDataInputs, useGetBeginNodeDataQueryIsSafe, } from './hooks/use-get-begin-query'; -import { useOpenDocument } from './hooks/use-open-document'; import { useSaveGraph, useSaveGraphBeforeOpeningDebugDrawer, @@ -73,7 +71,7 @@ export default function Agent() { const { t } = useTranslation(); const { data: userInfo } = useFetchUserInfo(); - const openDocument = useOpenDocument(); + // const openDocument = useOpenDocument(); const { handleExportJson, handleImportJson, @@ -100,7 +98,7 @@ export default function Agent() { const { showEmbedModal, hideEmbedModal, embedVisible, beta } = useShowEmbedModal(); - + const { navigateToAgentLogs } = useNavigatePage(); const isBeginNodeDataQuerySafe = useGetBeginNodeDataQueryIsSafe(); return ( @@ -135,7 +133,10 @@ export default function Agent() { {t('flow.historyversion')} - @@ -147,11 +148,11 @@ export default function Agent() { - + {/* API - - + */} + {/* */} {t('flow.import')} diff --git a/web/src/pages/agents/agent-log-page.tsx b/web/src/pages/agents/agent-log-page.tsx new file mode 100644 index 000000000..02e508e4a --- /dev/null +++ b/web/src/pages/agents/agent-log-page.tsx @@ -0,0 +1,319 @@ +import TimeRangePicker from '@/components/originui/time-range-picker'; +import { PageHeader } from '@/components/page-header'; +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, +} from '@/components/ui/breadcrumb'; +import { SearchInput } from '@/components/ui/input'; +import { RAGFlowPagination } from '@/components/ui/ragflow-pagination'; +import { Spin } from '@/components/ui/spin'; +import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; +import { useFetchAgentLog } from '@/hooks/use-agent-request'; +import { IAgentLogResponse } from '@/interfaces/database/agent'; +import React, { useEffect, useState } from 'react'; +import { useParams } from 'umi'; +import { DateRange } from '../../components/originui/calendar/index'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '../../components/ui/table'; +import { useFetchDataOnMount } from '../agent/hooks/use-fetch-data'; + +const AgentLogPage: React.FC = () => { + const { navigateToAgentList, navigateToAgent } = useNavigatePage(); + const { flowDetail: agentDetail } = useFetchDataOnMount(); + const { id: canvasId } = useParams(); + const today = new Date(); + const init = { + keywords: '', + from_date: today, + to_date: today, + orderby: 'create_time', + desc: false, + page: 1, + page_size: 10, + }; + const [searchParams, setSearchParams] = useState(init); + const columns = [ + { + title: 'ID', + dataIndex: 'id', + key: 'id', + }, + { + title: 'Title', + dataIndex: 'title', + key: 'title', + render: (text, record: IAgentLogResponse) => ( + + {record?.message?.length ? record?.message[0]?.content : ''} + + ), + }, + { + title: 'State', + dataIndex: 'state', + key: 'state', + render: (text, record: IAgentLogResponse) => ( +
+ ), + }, + { + title: 'Number', + dataIndex: 'round', + key: 'round', + }, + { + title: 'Latest Date', + dataIndex: 'update_date', + key: 'update_date', + sortable: true, + }, + { + title: 'Create Date', + dataIndex: 'create_date', + key: 'create_date', + sortable: true, + }, + ]; + + const { data: logData, loading } = useFetchAgentLog(searchParams); + const { sessions: data, total } = logData || {}; + const [currentDate, setCurrentDate] = useState({ + from: searchParams.from_date, + to: searchParams.to_date, + }); + const [keywords, setKeywords] = useState(searchParams.keywords); + const handleDateRangeChange = ({ + from: startDate, + to: endDate, + }: DateRange) => { + setCurrentDate({ from: startDate, to: endDate }); + }; + + const [pagination, setPagination] = useState<{ + current: number; + pageSize: number; + total: number; + }>({ + current: 1, + pageSize: 10, + total: total, + }); + + useEffect(() => { + setPagination((pre) => { + return { + ...pre, + total: total, + }; + }); + }, [total]); + + const [sortConfig, setSortConfig] = useState<{ + orderby: string; + desc: boolean; + } | null>({ orderby: init.orderby, desc: init.desc ? true : false }); + + const handlePageChange = (current?: number, pageSize?: number) => { + console.log('current', current, 'pageSize', pageSize); + setPagination((pre) => { + return { + ...pre, + current: current ?? pre.current, + pageSize: pageSize ?? pre.pageSize, + }; + }); + }; + + const handleSearch = () => { + setSearchParams((pre) => { + return { + ...pre, + from_date: currentDate.from as Date, + to_date: currentDate.to as Date, + page: pagination.current, + page_size: pagination.pageSize, + orderby: sortConfig?.orderby || '', + desc: sortConfig?.desc as boolean, + keywords: keywords, + }; + }); + }; + + useEffect(() => { + handleSearch(); + }, [pagination.current, pagination.pageSize, sortConfig]); + // handle sort + const handleSort = (key: string) => { + let desc = false; + if (sortConfig && sortConfig.orderby === key) { + desc = !sortConfig.desc; + } + setSortConfig({ orderby: key, desc }); + }; + + const handleReset = () => { + setSearchParams(init); + }; + return ( +
+ + + + + + Agent + + + + + + {agentDetail.title} + + + + + Log + + + + +
+
+

Log

+ +
+
+ ID/Title + { + setKeywords(e.target.value); + }} + className="w-32" + > +
+
+ Latest Date + +
+ + +
+
+
+ {/*
*/} + + + + {columns.map((column) => ( + handleSort(column.dataIndex) + : undefined + } + className={ + column.sortable ? 'cursor-pointer hover:bg-muted/50' : '' + } + > +
+ {column.title} + {column.sortable && + sortConfig?.orderby === column.dataIndex && ( + + {sortConfig.desc ? '↓' : '↑'} + + )} +
+
+ ))} +
+
+ + {loading && ( + + + + Loading... + + + + )} + {!loading && + data?.map((item) => ( + + {columns.map((column) => ( + + {column.render + ? column.render(item[column.dataIndex], item) + : item[column.dataIndex]} + + ))} + + ))} + {!loading && (!data || data.length === 0) && ( + + + No data + + + )} + +
+ {/*
*/} +
+
+
+ { + handlePageChange(page, pageSize); + }} + > +
+
+
+
+ ); +}; + +export default AgentLogPage; diff --git a/web/src/pages/dataset/setting/chunk-method-form.tsx b/web/src/pages/dataset/setting/chunk-method-form.tsx index 1e5e59c2d..0beb3b6f4 100644 --- a/web/src/pages/dataset/setting/chunk-method-form.tsx +++ b/web/src/pages/dataset/setting/chunk-method-form.tsx @@ -88,13 +88,17 @@ export function ChunkMethodForm() { let beValid = await form.formControl.trigger(); if (beValid) { // setSubmitLoading(true); - let postData = form.formState.values; - delete postData['avatar']; // has submitted in first form general - - saveKnowledgeConfiguration({ - ...postData, - kb_id, - }); + // let postData = form.formState.values; + // console.log('submit form -->', form); + // delete postData['avatar']; // has submitted in first form general + form.handleSubmit(async (values) => { + console.log('saveKnowledgeConfiguration: ', values); + delete values['avatar']; + await saveKnowledgeConfiguration({ + kb_id, + ...values, + }); + })(); } } catch (e) { console.log(e); diff --git a/web/src/routes.ts b/web/src/routes.ts index 612ac2323..1850940cc 100644 --- a/web/src/routes.ts +++ b/web/src/routes.ts @@ -36,6 +36,7 @@ export enum Routes { Result = '/result', ResultView = `${Chunk}${Result}`, KnowledgeGraph = '/knowledge-graph', + AgentLogPage = '/agent-log-page', } const routes = [ @@ -244,6 +245,11 @@ const routes = [ }, ], }, + { + path: `${Routes.AgentLogPage}/:id`, + layout: false, + component: `@/pages${Routes.Agents}${Routes.AgentLogPage}`, + }, { path: `${Routes.Agent}/:id`, layout: false, diff --git a/web/src/services/agent-service.ts b/web/src/services/agent-service.ts index 66ffe8465..b7684ac68 100644 --- a/web/src/services/agent-service.ts +++ b/web/src/services/agent-service.ts @@ -1,3 +1,4 @@ +import { IAgentLogsRequest } from '@/interfaces/database/agent'; import api from '@/utils/api'; import { registerNextServer } from '@/utils/register-server'; import request from '@/utils/request'; @@ -22,6 +23,7 @@ const { fetchVersion, fetchCanvas, fetchAgentAvatar, + fetchAgentLogs, } = api; const methods = { @@ -101,6 +103,10 @@ const methods = { url: fetchAgentAvatar, method: 'get', }, + fetchAgentLogs: { + url: fetchAgentLogs, + method: 'get', + }, } as const; const agentService = registerNextServer(methods); @@ -108,5 +114,11 @@ const agentService = registerNextServer(methods); export const fetchTrace = (data: { canvas_id: string; message_id: string }) => { return request.get(methods.trace.url, { params: data }); }; +export const fetchAgentLogsByCanvasId = ( + canvasId: string, + params: IAgentLogsRequest, +) => { + return request.get(methods.fetchAgentLogs.url(canvasId), { params: params }); +}; export default agentService; diff --git a/web/src/utils/api.ts b/web/src/utils/api.ts index 2b706260a..679dfeddf 100644 --- a/web/src/utils/api.ts +++ b/web/src/utils/api.ts @@ -153,6 +153,8 @@ export default { fetchCanvas: (id: string) => `${api_host}/canvas/get/${id}`, fetchAgentAvatar: (id: string) => `${api_host}/canvas/getsse/${id}`, uploadAgentFile: (id?: string) => `${api_host}/canvas/upload/${id}`, + fetchAgentLogs: (canvasId: string) => + `${api_host}/canvas/${canvasId}/sessions`, // mcp server listMcpServer: `${api_host}/mcp_server/list`, diff --git a/web/src/utils/date.ts b/web/src/utils/date.ts index 8515cc29c..56feb940c 100644 --- a/web/src/utils/date.ts +++ b/web/src/utils/date.ts @@ -32,3 +32,14 @@ export function formatPureDate(date: any) { } return dayjs(date).format('DD/MM/YYYY'); } + +export function formatStandardDate(date: any) { + if (!date) { + return ''; + } + const parsedDate = dayjs(date); + if (!parsedDate.isValid()) { + return ''; + } + return parsedDate.format('YYYY-MM-DD'); +}