#
zhou zhou
2 天以前 c65d967bee45b38f0077cfd8f7971787b7042e98
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import React, { useEffect, useState } from "react";
import {
    List,
    SearchInput,
    TopToolbar,
    SelectColumnsButton,
    EditButton,
    FilterButton,
    TextInput,
    DateInput,
    SelectInput,
    useListContext,
    Pagination,
} from 'react-admin';
import { Box, Chip, Grid, Typography } from '@mui/material';
import EmptyData from "@/page/components/EmptyData";
import { AiConsoleLayout, AiConsolePanel, aiCardSx } from "@/page/components/AiConsoleLayout";
import MyExportButton from '@/page/components/MyExportButton';
import { DEFAULT_PAGE_SIZE } from '@/config/setting';
import request from "@/utils/request";
 
const filters = [
    <SearchInput source="condition" alwaysOn />,
    <DateInput label='common.time.after' source="timeStart" alwaysOn />,
    <DateInput label='common.time.before' source="timeEnd" alwaysOn />,
    <TextInput source="modelCode" label="模型编码" />,
    <TextInput source="routeCode" label="路由编码" />,
    <SelectInput source="result" label="结果" choices={[
        { id: '1', name: '成功' },
        { id: '0', name: '失败' },
    ]} />,
];
 
const resultColor = (result) => Number(result) === 1 ? 'success' : 'error';
 
const CallLogBoard = () => {
    const { data, isLoading } = useListContext();
    const records = data || [];
    const [stats, setStats] = useState({});
 
    useEffect(() => {
        let mounted = true;
        const fetchStats = async () => {
            try {
                const { data: res } = await request.get('/ai/call-log/stats');
                if (mounted && res?.code === 200) {
                    setStats(res.data || {});
                }
            } catch (error) {
            }
        };
        fetchStats();
        return () => {
            mounted = false;
        };
    }, []);
 
    if (!isLoading && !records.length) {
        return <EmptyData />;
    }
 
    return (
        <AiConsoleLayout
            title="AI调用日志"
            subtitle="按当前系统后台风格展示模型调用观测,突出成功率、最近 24 小时调用量和平均耗时,便于快速排查路由与上游模型问题。"
            stats={[
                { label: '调用总数', value: stats.total || 0 },
                { label: '成功率', value: `${Number(stats.successRate || 0).toFixed(1)}%` },
                { label: '平均耗时', value: `${stats.avgSpendTime || 0} ms` },
                { label: '近24小时', value: stats.last24hCount || 0 },
            ]}
        >
            <AiConsolePanel
                title="调用明细"
                subtitle={`已观测模型 ${stats.modelCount || 0} 个,路由 ${stats.routeCount || 0} 条。`}
                minHeight={420}
            >
                <Grid container spacing={2}>
                    {records.map((record) => (
                        <Grid item xs={12} md={6} xl={4} key={record.id}>
                            <Box sx={aiCardSx(record.result === 1)}>
                                <Box display="flex" justifyContent="space-between" gap={1}>
                                    <Box>
                                        <Typography variant="subtitle1" sx={{ fontWeight: 700, color: '#284059' }}>
                                            {record.modelCode || '未记录模型'}
                                        </Typography>
                                        <Typography variant="caption" sx={{ color: '#8093a8' }}>
                                            {record.routeCode || '未记录路由'} · 第 {record.attemptNo || 1} 次
                                        </Typography>
                                    </Box>
                                    <Chip size="small" color={resultColor(record.result)} label={record.result$ || '未知'} />
                                </Box>
                                <Box sx={{ mt: 1.25, display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 1.5 }}>
                                    <Box>
                                        <Typography variant="caption" sx={{ color: '#70839a' }}>耗时</Typography>
                                        <Typography variant="body2" sx={{ color: '#31465d' }}>{record.spendTime || 0} ms</Typography>
                                    </Box>
                                    <Box>
                                        <Typography variant="caption" sx={{ color: '#70839a' }}>请求时间</Typography>
                                        <Typography variant="body2" sx={{ color: '#31465d' }}>{record.requestTime || '-'}</Typography>
                                    </Box>
                                </Box>
                                <Box sx={{ mt: 1.25 }}>
                                    <Typography variant="caption" sx={{ color: '#70839a' }}>错误信息</Typography>
                                    <Typography variant="body2" sx={{ mt: 0.5, minHeight: 72, color: '#31465d' }}>
                                        {record.err || '无'}
                                    </Typography>
                                </Box>
                                <Box sx={{ mt: 1.5 }}>
                                    <EditButton record={record} sx={{ px: 1.25, py: 0.5, minWidth: 64 }} />
                                </Box>
                            </Box>
                        </Grid>
                    ))}
                </Grid>
                <Box sx={{ mt: 2 }}>
                    <Pagination rowsPerPageOptions={[DEFAULT_PAGE_SIZE, 25, 50]} />
                </Box>
            </AiConsolePanel>
        </AiConsoleLayout>
    );
};
 
const AiCallLogList = () => (
    <List
        sx={{ width: '100%', flexGrow: 1 }}
        title={"menu.aiCallLog"}
        filters={filters}
        sort={{ field: "createTime", order: "desc" }}
        actions={(
            <TopToolbar>
                <FilterButton />
                <SelectColumnsButton preferenceKey='aiCallLog' />
                <MyExportButton />
            </TopToolbar>
        )}
        perPage={DEFAULT_PAGE_SIZE}
        pagination={false}
    >
        <CallLogBoard />
    </List>
);
 
export default AiCallLogList;