From d62b484d71d052e1c873ffd23af8e53057c5c651 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Tue, 24 Mar 2026 16:31:32 +0800 Subject: [PATCH] feat(web): app logs --- web/src/api/application.ts | 9 +- web/src/components/Table/index.tsx | 4 +- web/src/i18n/en.ts | 2 +- web/src/i18n/zh.ts | 2 +- web/src/views/ApplicationConfig/Logs.tsx | 79 ++++++++++++++ .../components/ConfigHeader.tsx | 6 +- .../components/LogDetailModal.tsx | 103 ++++++++++++++++++ .../views/ApplicationConfig/index.module.css | 2 +- web/src/views/ApplicationConfig/index.tsx | 4 +- web/src/views/ApplicationConfig/types.ts | 16 ++- 10 files changed, 215 insertions(+), 12 deletions(-) create mode 100644 web/src/views/ApplicationConfig/Logs.tsx create mode 100644 web/src/views/ApplicationConfig/components/LogDetailModal.tsx diff --git a/web/src/api/application.ts b/web/src/api/application.ts index 935f786a..a5730289 100644 --- a/web/src/api/application.ts +++ b/web/src/api/application.ts @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 13:59:45 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-19 20:42:23 + * @Last Modified time: 2026-03-24 15:48:30 */ import { request } from '@/utils/request' import type { ApplicationModalData } from '@/views/ApplicationManagement/types' @@ -169,4 +169,9 @@ export const cancelShare = (app_id: string, target_workspace_id?: string) => { export const cancelSpaceShare = (target_workspace_id?: string) => { return request.delete(`/apps/share/${target_workspace_id}`) } - +// Application conversation logs +export const getAppLogsUrl = (app_id: string) => `/apps/${app_id}/logs` +// Get full conversation message history +export const getAppLogDetail = (app_id: string, conversation_id: string) => { + return request.get(`/apps/${app_id}/logs/${conversation_id}`) +} \ No newline at end of file diff --git a/web/src/components/Table/index.tsx b/web/src/components/Table/index.tsx index 719d0dee..578bbd1b 100644 --- a/web/src/components/Table/index.tsx +++ b/web/src/components/Table/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-02 15:29:46 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-23 12:11:18 + * @Last Modified time: 2026-03-24 16:12:56 */ /** * RbTable Component @@ -102,7 +102,7 @@ const RbTable = forwardRef(({ const [loading, setLoading] = useState(false) const [currentPagination, setCurrentPagination] = useState({ page: 1, - pagesize: 10, + pagesize: 20, }); const [total, setTotal] = useState(0); diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 1dad256e..53d6c4af 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -1309,7 +1309,7 @@ export const en = { apiKeyRequestTotal: 'Total Requests', qps: 'Average QPS', qpsLimit: 'QPS Limit', - qpsLimitTip: '(Requests per second)', + qpsLimitTip: 'Requests per second', apiLimitConfig: 'Rate Limiting Configuration', qpsLimitDesc: 'Limit the maximum number of requests this Key can make per second', dailyUsageLimit: 'Daily Usage Limit', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 0a0efb67..0132df5b 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -687,7 +687,7 @@ export const zh = { apiKeyRequestTotal: '总请求数', qps: '平均 QPS', qpsLimit: 'QPS 限制', - qpsLimitTip: '(每秒请求数)', + qpsLimitTip: '每秒请求数', apiLimitConfig: '限流配置', qpsLimitDesc: '限制此 Key 每秒最多可以发起的请求数', dailyUsageLimit: '日调用量限制', diff --git a/web/src/views/ApplicationConfig/Logs.tsx b/web/src/views/ApplicationConfig/Logs.tsx new file mode 100644 index 00000000..5dc75baf --- /dev/null +++ b/web/src/views/ApplicationConfig/Logs.tsx @@ -0,0 +1,79 @@ +/* + * @Author: ZhaoYing + * @Date: 2026-03-24 15:41:20 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-24 16:06:44 + */ +import { type FC, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useParams } from 'react-router-dom'; +import { Flex, Button } from 'antd'; +import type { ColumnsType } from 'antd/es/table'; + +import { getAppLogsUrl } from '@/api/application'; +import Table from '@/components/Table' +import { formatDateTime } from '@/utils/format'; +import type { LogItem, LogDetailModalRef } from './types' +import LogDetailModal from './components/LogDetailModal' + +const Statistics: FC = () => { + const { t } = useTranslation(); + const { id } = useParams(); + const logDetailRef = useRef(null); + + const handleViewDetail = (item: LogItem) => { + logDetailRef.current?.handleOpen(item); + } + + /** Table column configuration */ + const columns: ColumnsType = [ + { + title: t('application.logTitle'), + dataIndex: 'title', + key: 'title', + className: 'rb:text-[#212332]' + }, + { + title: t('user.createTime'), + dataIndex: 'created_at', + key: 'created_at', + render: (createdAt: string) => formatDateTime(createdAt, 'YYYY-MM-DD HH:mm:ss'), + }, + { + title: t('user.lastLoginTime'), + dataIndex: 'last_login_at', + key: 'last_login_at', + render: (lastLoginAt: string) => lastLoginAt ? formatDateTime(lastLoginAt, 'YYYY-MM-DD HH:mm:ss') : '-', + }, + { + title: t('common.operation'), + key: 'action', + render: (_, record) => ( + + + + ), + }, + ]; + return ( +
+ + + + ); +} +export default Statistics; \ No newline at end of file diff --git a/web/src/views/ApplicationConfig/components/ConfigHeader.tsx b/web/src/views/ApplicationConfig/components/ConfigHeader.tsx index 415df2f8..5cee841a 100644 --- a/web/src/views/ApplicationConfig/components/ConfigHeader.tsx +++ b/web/src/views/ApplicationConfig/components/ConfigHeader.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:27:52 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-19 21:21:28 + * @Last Modified time: 2026-03-24 15:58:23 */ import { type FC, useRef, useMemo, useCallback } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; @@ -27,10 +27,10 @@ import FeaturesConfig from './FeaturesConfig' /** * Tab keys for application configuration */ -const tabKeys = ['arrangement', 'api', 'release', 'statistics'] +const tabKeys = ['arrangement', 'api', 'release', 'log', 'statistics'] const sharingTabKeys = [ 'test', - // 'log', + 'log', 'api' ] diff --git a/web/src/views/ApplicationConfig/components/LogDetailModal.tsx b/web/src/views/ApplicationConfig/components/LogDetailModal.tsx new file mode 100644 index 00000000..26d8741b --- /dev/null +++ b/web/src/views/ApplicationConfig/components/LogDetailModal.tsx @@ -0,0 +1,103 @@ +/* + * @Author: ZhaoYing + * @Date: 2026-03-24 16:31:24 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-24 16:31:24 + */ +import { forwardRef, useImperativeHandle, useState, useEffect } from 'react'; +import { Flex, Button, Empty, Skeleton } from 'antd'; +import { useTranslation } from 'react-i18next'; + +import type { LogDetailModalRef, LogItem } from '../types' +import RbModal from '@/components/RbModal' +import { getAppLogDetail } from '@/api/application' +import ChatContent from '@/components/Chat/ChatContent' +import { formatDateTime } from '@/utils/format' +import type { ChatItem } from '@/components/Chat/types' + +/** Log detail data with conversation messages */ +type Data = LogItem & { + messages: ChatItem[]; +} + +/** Modal component for displaying conversation log details */ +const LogDetailModal = forwardRef((_props, ref) => { + const { t } = useTranslation(); + const [visible, setVisible] = useState(false); + const [loading, setLoading] = useState(false) + const [vo, setVo] = useState(null) + const [data, setData] = useState({} as Data) + + /** Close modal and reset form */ + const handleClose = () => { + setVisible(false); + setLoading(false) + setVo(null) + setData({} as Data) + }; + + /** Open modal */ + const handleOpen = (item: LogItem) => { + setVisible(true); + setVo(item) + }; + + /** Fetch detail when modal opens */ + useEffect(() => { + if (visible && vo) { + getDetail() + } + }, [visible, vo]) + + /** Fetch conversation log detail from API */ + const getDetail = () => { + if (!vo) return + setLoading(true) + getAppLogDetail(vo.app_id, vo.id).then(res => { + setData(res as Data) + }) + .finally(() => { + setLoading(false) + }) + } + /** Expose methods to parent component */ + useImperativeHandle(ref, () => ({ + handleOpen, + handleClose + })); + + return ( + + {data.title} +
{formatDateTime(data.created_at, 'YYYY.MM')} - {formatDateTime(data.updated_at, 'YYYY.MM')}
+ } + open={visible} + onCancel={handleClose} + footer={null} + width={1000} + > + + {t('workingDetail.conversationStream')} + + +
+ {loading + ? + : data.messages?.length === 0 + ? + : ( + formatDateTime(item.created_at)} + /> + ) + } +
+
+ ); +}); + +export default LogDetailModal; \ No newline at end of file diff --git a/web/src/views/ApplicationConfig/index.module.css b/web/src/views/ApplicationConfig/index.module.css index 3b3a5316..8c50929c 100644 --- a/web/src/views/ApplicationConfig/index.module.css +++ b/web/src/views/ApplicationConfig/index.module.css @@ -14,7 +14,7 @@ font-weight: 500; } .tabs :global(.ant-tabs-tab+.ant-tabs-tab) { - margin-left: 78px; + margin-left: 48px; } .tabs:global(.ant-tabs-top>.ant-tabs-nav .ant-tabs-ink-bar), .tabs:global(.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-ink-bar) { diff --git a/web/src/views/ApplicationConfig/index.tsx b/web/src/views/ApplicationConfig/index.tsx index 114bdd89..1630be49 100644 --- a/web/src/views/ApplicationConfig/index.tsx +++ b/web/src/views/ApplicationConfig/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:29:37 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-19 21:09:32 + * @Last Modified time: 2026-03-24 15:59:47 */ import React, { useEffect, useState, useRef } from 'react'; import { useParams } from 'react-router-dom'; @@ -19,6 +19,7 @@ import Workflow from '@/views/Workflow'; import Statistics from './Statistics' import TestChat from './TestChat' import type { WorkflowConfig } from '@/views/Workflow/types'; +import Logs from './Logs'; /** * Application configuration page component @@ -126,6 +127,7 @@ const ApplicationConfig: React.FC = () => { {activeTab === 'release' && } {activeTab === 'statistics' && } {activeTab === 'test' && } + {activeTab === 'log' && } ); diff --git a/web/src/views/ApplicationConfig/types.ts b/web/src/views/ApplicationConfig/types.ts index 5dea5940..e125e73a 100644 --- a/web/src/views/ApplicationConfig/types.ts +++ b/web/src/views/ApplicationConfig/types.ts @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:29:49 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-24 10:59:18 + * @Last Modified time: 2026-03-24 15:44:33 */ import type { KnowledgeConfig } from './components/Knowledge/types' import type { Variable } from './components/VariableList/types' @@ -481,4 +481,18 @@ export interface AppSharingModalRef { export interface AppSharingForm { target_workspace_ids: string[]; permission: 'readonly' | 'editable' +} + +export interface LogItem { + id: string; + app_id: string; + user_id: string; + title: string; + message_count: number; + is_draft: boolean; + created_at: number; + updated_at: number; +} +export interface LogDetailModalRef { + handleOpen: (vo: LogItem) => void; } \ No newline at end of file