zhou zhou
15 小时以前 287a666e1b2bb155e86aa88ebace201d1e8a51f6
rsf-admin/src/page/system/aiCallLog/AiCallLogList.jsx
@@ -8,6 +8,7 @@
    TopToolbar,
    useListContext,
    useNotify,
    useTranslate,
} from "react-admin";
import {
    Alert,
@@ -32,12 +33,12 @@
const filters = [
    <SearchInput source="condition" alwaysOn />,
    <TextInput source="requestId" label="请求ID" />,
    <TextInput source="promptCode" label="Prompt 编码" />,
    <TextInput source="userId" label="用户ID" />,
    <TextInput source="requestId" label="ai.observe.fields.requestId" />,
    <TextInput source="promptCode" label="ai.observe.fields.promptCode" />,
    <TextInput source="userId" label="ai.observe.fields.userId" />,
    <SelectInput
        source="status"
        label="状态"
        label="common.field.status"
        choices={[
            { id: "RUNNING", name: "RUNNING" },
            { id: "COMPLETED", name: "COMPLETED" },
@@ -48,6 +49,7 @@
];
const ObserveSummary = () => {
    const translate = useTranslate();
    const [stats, setStats] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState("");
@@ -64,7 +66,7 @@
            })
            .catch((err) => {
                if (active) {
                    setError(err?.message || "获取 AI 观测统计失败");
                    setError(err?.message || translate("ai.observe.summary.fetchFailed"));
                }
            })
            .finally(() => {
@@ -83,9 +85,9 @@
                <CardContent>
                    <Stack direction="row" justifyContent="space-between" alignItems="center" mb={2}>
                        <Box>
                            <Typography variant="h6">观测总览</Typography>
                            <Typography variant="h6">{translate("ai.observe.summary.title")}</Typography>
                            <Typography variant="body2" color="text.secondary">
                                当前租户下的 AI 对话调用与 MCP 工具调用统计。
                                {translate("ai.observe.summary.description")}
                            </Typography>
                        </Box>
                        {loading && <CircularProgress size={24} />}
@@ -94,31 +96,31 @@
                    {!loading && !error && stats && (
                        <Grid container spacing={2}>
                            <Grid item xs={12} md={3}>
                                <Typography variant="caption" color="text.secondary">AI 调用量</Typography>
                                <Typography variant="caption" color="text.secondary">{translate("ai.observe.summary.callCount")}</Typography>
                                <Typography variant="h5">{stats.callCount ?? 0}</Typography>
                                <Typography variant="body2" color="text.secondary">
                                    成功 {stats.successCount ?? 0} / 失败 {stats.failureCount ?? 0}
                                    {translate("ai.observe.summary.successFailure", { success: stats.successCount ?? 0, failure: stats.failureCount ?? 0 })}
                                </Typography>
                            </Grid>
                            <Grid item xs={12} md={3}>
                                <Typography variant="caption" color="text.secondary">平均耗时</Typography>
                                <Typography variant="caption" color="text.secondary">{translate("ai.observe.summary.avgElapsed")}</Typography>
                                <Typography variant="h5">{stats.avgElapsedMs ?? 0} ms</Typography>
                                <Typography variant="body2" color="text.secondary">
                                    首包 {stats.avgFirstTokenLatencyMs ?? 0} ms
                                    {translate("ai.observe.summary.firstToken", { value: stats.avgFirstTokenLatencyMs ?? 0 })}
                                </Typography>
                            </Grid>
                            <Grid item xs={12} md={3}>
                                <Typography variant="caption" color="text.secondary">Token 使用</Typography>
                                <Typography variant="caption" color="text.secondary">{translate("ai.observe.summary.tokenUsage")}</Typography>
                                <Typography variant="h5">{stats.totalTokens ?? 0}</Typography>
                                <Typography variant="body2" color="text.secondary">
                                    平均 {stats.avgTotalTokens ?? 0}
                                    {translate("ai.observe.summary.avgToken", { value: stats.avgTotalTokens ?? 0 })}
                                </Typography>
                            </Grid>
                            <Grid item xs={12} md={3}>
                                <Typography variant="caption" color="text.secondary">工具成功率</Typography>
                                <Typography variant="caption" color="text.secondary">{translate("ai.observe.summary.toolSuccessRate")}</Typography>
                                <Typography variant="h5">{Number(stats.toolSuccessRate || 0).toFixed(2)}%</Typography>
                                <Typography variant="body2" color="text.secondary">
                                    调用 {stats.toolCallCount ?? 0} / 失败 {stats.toolFailureCount ?? 0}
                                    {translate("ai.observe.summary.toolCallFailure", { call: stats.toolCallCount ?? 0, failure: stats.toolFailureCount ?? 0 })}
                                </Typography>
                            </Grid>
                        </Grid>
@@ -129,21 +131,22 @@
    );
};
const resolveStatusChip = (status) => {
const resolveStatusChip = (status, translate) => {
    if (status === "COMPLETED") {
        return { color: "success", label: "成功" };
        return { color: "success", label: translate("ai.observe.status.completed") };
    }
    if (status === "FAILED") {
        return { color: "error", label: "失败" };
        return { color: "error", label: translate("ai.observe.status.failed") };
    }
    if (status === "ABORTED") {
        return { color: "warning", label: "中断" };
        return { color: "warning", label: translate("ai.observe.status.aborted") };
    }
    return { color: "default", label: status || "--" };
};
const AiCallLogDetailDialog = ({ record, open, onClose }) => {
    const notify = useNotify();
    const translate = useTranslate();
    const [logs, setLogs] = useState([]);
    const [loading, setLoading] = useState(false);
@@ -161,7 +164,7 @@
            })
            .catch((error) => {
                if (active) {
                    notify(error?.message || "获取 MCP 调用日志失败", { type: "error" });
                    notify(error?.message || translate("ai.observe.detail.mcpLogsFailed"), { type: "error" });
                }
            })
            .finally(() => {
@@ -180,35 +183,35 @@
    return (
        <Dialog open={open} onClose={onClose} fullWidth maxWidth="lg">
            <DialogTitle>AI 调用详情</DialogTitle>
            <DialogTitle>{translate("ai.observe.detail.title")}</DialogTitle>
            <DialogContent dividers>
                <Grid container spacing={2}>
                    <Grid item xs={12} md={6}>
                        <Typography variant="caption" color="text.secondary">请求ID</Typography>
                        <Typography variant="caption" color="text.secondary">{translate("ai.observe.fields.requestId")}</Typography>
                        <Typography variant="body2">{record.requestId || "--"}</Typography>
                    </Grid>
                    <Grid item xs={12} md={3}>
                        <Typography variant="caption" color="text.secondary">用户ID</Typography>
                        <Typography variant="caption" color="text.secondary">{translate("ai.observe.fields.userId")}</Typography>
                        <Typography variant="body2">{record.userId || "--"}</Typography>
                    </Grid>
                    <Grid item xs={12} md={3}>
                        <Typography variant="caption" color="text.secondary">会话ID</Typography>
                        <Typography variant="caption" color="text.secondary">{translate("ai.observe.fields.sessionId")}</Typography>
                        <Typography variant="body2">{record.sessionId || "--"}</Typography>
                    </Grid>
                    <Grid item xs={12} md={4}>
                        <Typography variant="caption" color="text.secondary">Prompt</Typography>
                        <Typography variant="caption" color="text.secondary">{translate("ai.common.prompt")}</Typography>
                        <Typography variant="body2">{record.promptName || "--"} / {record.promptCode || "--"}</Typography>
                    </Grid>
                    <Grid item xs={12} md={4}>
                        <Typography variant="caption" color="text.secondary">模型</Typography>
                        <Typography variant="caption" color="text.secondary">{translate("ai.common.model")}</Typography>
                        <Typography variant="body2">{record.model || "--"}</Typography>
                    </Grid>
                    <Grid item xs={12} md={4}>
                        <Typography variant="caption" color="text.secondary">状态</Typography>
                        <Typography variant="caption" color="text.secondary">{translate("common.field.status")}</Typography>
                        <Typography variant="body2">{record.status || "--"}</Typography>
                    </Grid>
                    <Grid item xs={12}>
                        <Typography variant="caption" color="text.secondary">MCP 挂载</Typography>
                        <Typography variant="caption" color="text.secondary">{translate("ai.observe.fields.mountedMcp")}</Typography>
                        <Typography variant="body2">{record.mountedMcpNames || "--"}</Typography>
                    </Grid>
                    {record.errorMessage && (
@@ -219,12 +222,12 @@
                    <Grid item xs={12}>
                        <Divider sx={{ my: 1 }} />
                        <Stack direction="row" justifyContent="space-between" alignItems="center" mb={1}>
                            <Typography variant="h6">MCP 工具调用日志</Typography>
                            <Typography variant="h6">{translate("ai.observe.detail.mcpLogs")}</Typography>
                            {loading && <CircularProgress size={20} />}
                        </Stack>
                        {!loading && !logs.length && (
                            <Typography variant="body2" color="text.secondary">
                                当前调用没有产生 MCP 工具日志。
                                {translate("ai.observe.detail.noMcpLogs")}
                            </Typography>
                        )}
                        <Stack spacing={1.5}>
@@ -244,13 +247,11 @@
                                                label={item.status || "--"}
                                            />
                                        </Stack>
                                        <Typography variant="caption" color="text.secondary">输入摘要</Typography>
                                        <Typography variant="caption" color="text.secondary">{translate("ai.observe.detail.inputSummary")}</Typography>
                                        <Typography variant="body2" sx={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
                                            {item.inputSummary || "--"}
                                        </Typography>
                                        <Typography variant="caption" color="text.secondary" display="block" mt={1}>
                                            输出摘要 / 错误
                                        </Typography>
                                        <Typography variant="caption" color="text.secondary" display="block" mt={1}>{translate("ai.observe.detail.outputSummary")}</Typography>
                                        <Typography variant="body2" sx={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
                                            {item.outputSummary || item.errorMessage || "--"}
                                        </Typography>
@@ -262,13 +263,14 @@
                </Grid>
            </DialogContent>
            <DialogActions>
                <Button onClick={onClose}>关闭</Button>
                <Button onClick={onClose}>{translate("ai.common.close")}</Button>
            </DialogActions>
        </Dialog>
    );
};
const AiCallLogCards = ({ onView }) => {
    const translate = useTranslate();
    const { data, isLoading } = useListContext();
    const records = useMemo(() => (Array.isArray(data) ? data : []), [data]);
@@ -284,9 +286,9 @@
        return (
            <Box px={2} py={6}>
                <Card variant="outlined" sx={{ p: 3, textAlign: "center", borderStyle: "dashed" }}>
                    <Typography variant="subtitle1">暂无 AI 调用日志</Typography>
                    <Typography variant="subtitle1">{translate("ai.observe.list.emptyTitle")}</Typography>
                    <Typography variant="body2" color="text.secondary" mt={1}>
                        发起 AI 对话后,这里会展示调用统计和审计记录。
                        {translate("ai.observe.list.emptyDescription")}
                    </Typography>
                </Card>
            </Box>
@@ -297,14 +299,14 @@
        <Box px={2} py={2}>
            <Grid container spacing={2}>
                {records.map((record) => {
                    const statusMeta = resolveStatusChip(record.status);
                    const statusMeta = resolveStatusChip(record.status, translate);
                    return (
                        <Grid item xs={12} md={6} xl={4} key={record.id}>
                            <Card variant="outlined" sx={{ height: "100%", borderRadius: 3, boxShadow: "0 8px 24px rgba(15, 23, 42, 0.06)" }}>
                                <CardContent sx={{ pb: 1.5 }}>
                                    <Stack direction="row" justifyContent="space-between" alignItems="flex-start" spacing={1}>
                                        <Box>
                                            <Typography variant="h6">{record.promptName || "AI 对话"}</Typography>
                                            <Typography variant="h6">{record.promptName || translate("ai.drawer.title")}</Typography>
                                            <Typography variant="body2" color="text.secondary">
                                                {record.promptCode || "--"} / {record.model || "--"}
                                            </Typography>
@@ -312,31 +314,31 @@
                                        <Chip size="small" color={statusMeta.color} label={statusMeta.label} />
                                    </Stack>
                                    <Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap mt={1.5}>
                                        <Chip size="small" variant="outlined" label={`用户 ${record.userId || "--"}`} />
                                        <Chip size="small" variant="outlined" label={`耗时 ${record.elapsedMs ?? 0} ms`} />
                                        <Chip size="small" variant="outlined" label={`Token ${record.totalTokens ?? 0}`} />
                                        <Chip size="small" variant="outlined" label={translate("ai.observe.list.userValue", { value: record.userId || "--" })} />
                                        <Chip size="small" variant="outlined" label={translate("ai.observe.list.elapsedValue", { value: record.elapsedMs ?? 0 })} />
                                        <Chip size="small" variant="outlined" label={translate("ai.observe.list.tokenValue", { value: record.totalTokens ?? 0 })} />
                                    </Stack>
                                    <Divider sx={{ my: 1.5 }} />
                                    <Typography variant="caption" color="text.secondary">请求ID</Typography>
                                    <Typography variant="caption" color="text.secondary">{translate("ai.observe.fields.requestId")}</Typography>
                                    <Typography variant="body2" sx={{ wordBreak: "break-all" }}>{record.requestId || "--"}</Typography>
                                    <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>
                                        MCP / 工具调用
                                    </Typography>
                                    <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>{translate("ai.observe.list.mcpToolCalls")}</Typography>
                                    <Typography variant="body2">
                                        挂载 {record.mountedMcpCount ?? 0} 个,工具成功 {record.toolSuccessCount ?? 0},失败 {record.toolFailureCount ?? 0}
                                        {translate("ai.observe.list.mcpToolSummary", {
                                            mcp: record.mountedMcpCount ?? 0,
                                            success: record.toolSuccessCount ?? 0,
                                            failure: record.toolFailureCount ?? 0,
                                        })}
                                    </Typography>
                                    {record.errorMessage && (
                                        <>
                                            <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>
                                                错误
                                            </Typography>
                                            <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>{translate("ai.common.error")}</Typography>
                                            <Typography variant="body2">{record.errorMessage}</Typography>
                                        </>
                                    )}
                                </CardContent>
                                <CardActions sx={{ px: 2, pb: 2, pt: 0 }}>
                                    <Button size="small" startIcon={<VisibilityOutlinedIcon />} onClick={() => onView(record)}>
                                        详情
                                        {translate("ai.common.detail")}
                                    </Button>
                                </CardActions>
                            </Card>