feat(web): app logs
This commit is contained in:
@@ -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}`)
|
||||
}
|
||||
@@ -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<TableRef, TableComponentProps>(({
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [currentPagination, setCurrentPagination] = useState({
|
||||
page: 1,
|
||||
pagesize: 10,
|
||||
pagesize: 20,
|
||||
});
|
||||
const [total, setTotal] = useState(0);
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -687,7 +687,7 @@ export const zh = {
|
||||
apiKeyRequestTotal: '总请求数',
|
||||
qps: '平均 QPS',
|
||||
qpsLimit: 'QPS 限制',
|
||||
qpsLimitTip: '(每秒请求数)',
|
||||
qpsLimitTip: '每秒请求数',
|
||||
apiLimitConfig: '限流配置',
|
||||
qpsLimitDesc: '限制此 Key 每秒最多可以发起的请求数',
|
||||
dailyUsageLimit: '日调用量限制',
|
||||
|
||||
79
web/src/views/ApplicationConfig/Logs.tsx
Normal file
79
web/src/views/ApplicationConfig/Logs.tsx
Normal file
@@ -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<LogDetailModalRef>(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) => (
|
||||
<Flex wrap>
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() => handleViewDetail(record as LogItem)}
|
||||
>
|
||||
{t('common.view')}
|
||||
</Button>
|
||||
</Flex>
|
||||
),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className="rb:bg-white rb:rounded-lg rb:pt-3 rb:px-3">
|
||||
<Table
|
||||
apiUrl={getAppLogsUrl(id || '')}
|
||||
apiParams={{
|
||||
is_draft: false,
|
||||
}}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
isScroll={true}
|
||||
scrollY="calc(100vh - 214px)"
|
||||
/>
|
||||
<LogDetailModal ref={logDetailRef} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default Statistics;
|
||||
@@ -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'
|
||||
]
|
||||
|
||||
|
||||
103
web/src/views/ApplicationConfig/components/LogDetailModal.tsx
Normal file
103
web/src/views/ApplicationConfig/components/LogDetailModal.tsx
Normal file
@@ -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<LogDetailModalRef>((_props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [vo, setVo] = useState<LogItem | null>(null)
|
||||
const [data, setData] = useState<Data>({} 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 (
|
||||
<RbModal
|
||||
title={<>
|
||||
{data.title}
|
||||
<div className="rb:text-[#5B6167] rb:leading-4.5 rb:text-[12px]">{formatDateTime(data.created_at, 'YYYY.MM')} - {formatDateTime(data.updated_at, 'YYYY.MM')}</div>
|
||||
</>}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
footer={null}
|
||||
width={1000}
|
||||
>
|
||||
<Flex justify="space-between" align="center" className="rb:bg-[#F6F6F6] rb:rounded-lg rb:py-2.5! rb:pr-2.5! rb:pl-3.25!">
|
||||
{t('workingDetail.conversationStream')}
|
||||
<Button className="rb:h-6!" onClick={getDetail}>{t('workingDetail.refresh')}</Button>
|
||||
</Flex>
|
||||
<div className="rb-border rb:p-3 rb:rounded-xl rb:mt-3 rb:h-116.5 rb:overflow-y-auto">
|
||||
{loading
|
||||
? <Skeleton active />
|
||||
: data.messages?.length === 0
|
||||
? <Empty className="rb:my-20" />
|
||||
: (
|
||||
<ChatContent
|
||||
contentClassNames="rb:max-w-110!"
|
||||
data={data.messages || []}
|
||||
streamLoading={false}
|
||||
labelFormat={(item) => formatDateTime(item.created_at)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</RbModal>
|
||||
);
|
||||
});
|
||||
|
||||
export default LogDetailModal;
|
||||
@@ -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) {
|
||||
|
||||
@@ -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' && <ReleasePage data={application as Application} refresh={getApplicationInfo} />}
|
||||
{activeTab === 'statistics' && <Statistics application={application} />}
|
||||
{activeTab === 'test' && <TestChat application={application} config={config} />}
|
||||
{activeTab === 'log' && <Logs />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user