Agent 初版设计
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
"use client";
|
||||
|
||||
import React, { useMemo, useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Chip,
|
||||
Collapse,
|
||||
LinearProgress,
|
||||
Stack,
|
||||
Typography,
|
||||
alpha,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import AutoAwesome from "@mui/icons-material/AutoAwesome";
|
||||
import CheckCircleRounded from "@mui/icons-material/CheckCircleRounded";
|
||||
import ErrorOutlineRounded from "@mui/icons-material/ErrorOutlineRounded";
|
||||
import ManageSearchRounded from "@mui/icons-material/ManageSearchRounded";
|
||||
import BuildCircleRounded from "@mui/icons-material/BuildCircleRounded";
|
||||
import TaskAltRounded from "@mui/icons-material/TaskAltRounded";
|
||||
import PsychologyRounded from "@mui/icons-material/PsychologyRounded";
|
||||
import SyncRounded from "@mui/icons-material/SyncRounded";
|
||||
|
||||
import type { ChatProgress } from "./GlobalChatbox.types";
|
||||
|
||||
const phaseIcon = (phase: string, status: ChatProgress["status"]) => {
|
||||
const sx = { fontSize: 16 };
|
||||
if (status === "completed") return <CheckCircleRounded sx={{ ...sx, color: "success.main" }} />;
|
||||
if (status === "error") return <ErrorOutlineRounded sx={{ ...sx, color: "error.main" }} />;
|
||||
if (phase === "planning") return <PsychologyRounded sx={{ ...sx, color: "primary.main" }} />;
|
||||
if (phase === "tool") return <BuildCircleRounded sx={{ ...sx, color: "warning.main" }} />;
|
||||
if (phase === "complete") return <TaskAltRounded sx={{ ...sx, color: "success.main" }} />;
|
||||
if (phase === "session") return <SyncRounded sx={{ ...sx, color: "info.main" }} />;
|
||||
if (phase === "start") return <ManageSearchRounded sx={{ ...sx, color: "primary.main" }} />;
|
||||
return <AutoAwesome sx={{ ...sx, color: "primary.main" }} />;
|
||||
};
|
||||
|
||||
const formatToolTitle = (item: ChatProgress) => {
|
||||
const text = `${item.title} ${item.detail ?? ""}`;
|
||||
if (text.includes("dynamic_http_call")) return "查询后端数据";
|
||||
if (text.includes("show_chart")) return "生成图表";
|
||||
if (text.includes("locate_features")) return "地图定位";
|
||||
if (text.includes("view_history")) return "打开历史曲线";
|
||||
if (text.includes("view_scada")) return "打开 SCADA 面板";
|
||||
return item.title;
|
||||
};
|
||||
|
||||
export const AgentProgressTimeline = ({ progress }: { progress: ChatProgress[] }) => {
|
||||
const theme = useTheme();
|
||||
const hasComplete = progress.some(
|
||||
(item) => item.phase === "complete" && item.status === "completed",
|
||||
);
|
||||
const hasRunning =
|
||||
!hasComplete && progress.some((item) => item.status === "running");
|
||||
const hasError = progress.some((item) => item.status === "error");
|
||||
const [expanded, setExpanded] = useState(hasRunning);
|
||||
|
||||
const summary = useMemo(() => {
|
||||
const completedCount = progress.filter((item) => item.status === "completed").length;
|
||||
const runningItem = hasComplete
|
||||
? undefined
|
||||
: [...progress].reverse().find((item) => item.status === "running");
|
||||
if (runningItem) return runningItem.title;
|
||||
if (hasError) return "过程存在异常";
|
||||
if (hasComplete) return `已完成 ${progress.length} 步`;
|
||||
return `已完成 ${completedCount || progress.length} 步`;
|
||||
}, [hasComplete, hasError, progress]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
borderRadius: 3,
|
||||
bgcolor: alpha(theme.palette.primary.main, 0.045),
|
||||
border: `1px solid ${alpha(theme.palette.primary.main, 0.14)}`,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={1}
|
||||
alignItems="center"
|
||||
sx={{ px: 1.5, py: 1.1 }}
|
||||
>
|
||||
<AutoAwesome sx={{ fontSize: 17, color: "primary.main" }} />
|
||||
<Typography variant="caption" fontWeight={800} color="text.primary">
|
||||
Agent 过程
|
||||
</Typography>
|
||||
<Chip
|
||||
size="small"
|
||||
label={summary}
|
||||
color={hasError ? "error" : hasRunning ? "primary" : "success"}
|
||||
variant="outlined"
|
||||
sx={{ height: 22, fontSize: "0.68rem", maxWidth: 180 }}
|
||||
/>
|
||||
<Box sx={{ flex: 1 }} />
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => setExpanded((value) => !value)}
|
||||
sx={{ minWidth: 0, px: 0.75, fontSize: "0.72rem" }}
|
||||
>
|
||||
{expanded ? "收起" : "展开"}
|
||||
</Button>
|
||||
</Stack>
|
||||
{hasRunning ? <LinearProgress sx={{ height: 3 }} /> : null}
|
||||
<Collapse in={expanded} timeout="auto">
|
||||
<Stack spacing={1} sx={{ px: 1.5, pb: 1.35 }}>
|
||||
{progress.map((item, index) => (
|
||||
<Stack key={item.id} direction="row" spacing={1} alignItems="stretch">
|
||||
<Box
|
||||
sx={{
|
||||
position: "relative",
|
||||
width: 18,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
flexShrink: 0,
|
||||
pt: 0.1,
|
||||
}}
|
||||
>
|
||||
{index < progress.length - 1 ? (
|
||||
<Box
|
||||
aria-hidden
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: 18,
|
||||
bottom: -10,
|
||||
left: "50%",
|
||||
width: 2,
|
||||
transform: "translateX(-50%)",
|
||||
borderRadius: 99,
|
||||
bgcolor: alpha(
|
||||
item.status === "error"
|
||||
? theme.palette.error.main
|
||||
: theme.palette.primary.main,
|
||||
item.status === "completed" ? 0.22 : 0.36,
|
||||
),
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<Box
|
||||
sx={{
|
||||
position: "relative",
|
||||
zIndex: 1,
|
||||
width: 18,
|
||||
height: 18,
|
||||
borderRadius: "50%",
|
||||
bgcolor: alpha("#fff", 0.92),
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{phaseIcon(
|
||||
item.phase,
|
||||
hasComplete && item.status === "running"
|
||||
? "completed"
|
||||
: item.status,
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ minWidth: 0, flex: 1 }}>
|
||||
<Typography variant="caption" color="text.primary" fontWeight={700}>
|
||||
{item.phase === "tool" ? formatToolTitle(item) : item.title}
|
||||
</Typography>
|
||||
{item.detail ? (
|
||||
<Typography
|
||||
variant="caption"
|
||||
component="pre"
|
||||
color="text.secondary"
|
||||
sx={{
|
||||
display: "block",
|
||||
mt: 0.25,
|
||||
m: 0,
|
||||
whiteSpace: "pre-wrap",
|
||||
fontFamily: "inherit",
|
||||
fontSize: "0.7rem",
|
||||
}}
|
||||
>
|
||||
{item.detail}
|
||||
</Typography>
|
||||
) : null}
|
||||
</Box>
|
||||
</Stack>
|
||||
))}
|
||||
</Stack>
|
||||
</Collapse>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user