Agent 初版设计

This commit is contained in:
2026-04-29 17:15:49 +08:00
parent 2c1afdc97c
commit e5ca9e24aa
13 changed files with 1819 additions and 1255 deletions
+177
View File
@@ -0,0 +1,177 @@
"use client";
import React from "react";
import { AnimatePresence, motion } from "framer-motion";
import { Box, Paper, Stack, Typography, alpha, useTheme } from "@mui/material";
import AutoAwesome from "@mui/icons-material/AutoAwesome";
import WaterDropRounded from "@mui/icons-material/WaterDropRounded";
import SensorsRounded from "@mui/icons-material/SensorsRounded";
import TroubleshootRounded from "@mui/icons-material/TroubleshootRounded";
import { AgentTurn } from "./AgentTurn";
import { TypingIndicator } from "./GlobalChatbox.parts";
import type { Message, SpeechState } from "./GlobalChatbox.types";
type AgentWorkspaceProps = {
messages: Message[];
isStreaming: boolean;
bottomRef: React.RefObject<HTMLDivElement | null>;
speakingMessageId: string | null;
speechState: SpeechState;
onSpeak: (messageId: string, text: string) => void;
onPauseSpeech: () => void;
onResumeSpeech: () => void;
onStopSpeech: () => void;
isTtsSupported: boolean;
};
const EmptyState = () => {
const theme = useTheme();
const capabilities = [
{ icon: <WaterDropRounded sx={{ fontSize: 18 }} />, label: "水力瓶颈识别" },
{ icon: <SensorsRounded sx={{ fontSize: 18 }} />, label: "SCADA 异常分析" },
{ icon: <TroubleshootRounded sx={{ fontSize: 18 }} />, label: "改造与调度建议" },
];
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ type: "spring", stiffness: 200, damping: 20 }}
style={{ margin: "auto", width: "100%" }}
>
<Paper
elevation={0}
sx={{
p: 3,
borderRadius: 5,
bgcolor: alpha("#fff", 0.68),
border: `1px solid ${alpha(theme.palette.divider, 0.12)}`,
maxWidth: 380,
mx: "auto",
textAlign: "center",
backdropFilter: "blur(10px)",
}}
>
<motion.div
animate={{ y: [-5, 5, -5] }}
transition={{ duration: 4, repeat: Infinity, ease: "easeInOut" }}
>
<AutoAwesome
sx={{
fontSize: 54,
color: "primary.main",
mb: 1.6,
filter: "drop-shadow(0 4px 8px rgba(0,0,0,0.1))",
}}
/>
</motion.div>
<Typography variant="h6" color="text.primary" fontWeight={900} gutterBottom>
Agent
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ lineHeight: 1.65, mb: 2 }}>
</Typography>
<Stack direction="row" spacing={0.8} useFlexGap flexWrap="wrap" justifyContent="center">
{capabilities.map((item) => (
<Stack
key={item.label}
direction="row"
spacing={0.5}
alignItems="center"
sx={{
px: 1,
py: 0.55,
borderRadius: 99,
bgcolor: alpha(theme.palette.primary.main, 0.07),
color: "text.secondary",
}}
>
{item.icon}
<Typography variant="caption" fontWeight={700}>
{item.label}
</Typography>
</Stack>
))}
</Stack>
</Paper>
</motion.div>
);
};
export const AgentWorkspace = ({
messages,
isStreaming,
bottomRef,
speakingMessageId,
speechState,
onSpeak,
onPauseSpeech,
onResumeSpeech,
onStopSpeech,
isTtsSupported,
}: AgentWorkspaceProps) => {
const theme = useTheme();
const latestAssistant = [...messages]
.reverse()
.find((message) => message.role === "assistant");
const showTypingIndicator =
isStreaming &&
(!latestAssistant ||
(latestAssistant.content.trim().length === 0 &&
!(latestAssistant.artifacts?.length)));
return (
<Box
sx={{
flex: 1,
overflowY: "auto",
px: 2.5,
py: 2,
display: "flex",
flexDirection: "column",
gap: 2,
zIndex: 5,
}}
>
<AnimatePresence initial={false}>
{messages.length === 0 ? <EmptyState /> : null}
{messages.map((message) => (
<AgentTurn
key={message.id}
message={message}
messageSpeechState={speakingMessageId === message.id ? speechState : "idle"}
onSpeak={onSpeak}
onPause={onPauseSpeech}
onResume={onResumeSpeech}
onStopSpeech={onStopSpeech}
isTtsSupported={isTtsSupported}
/>
))}
</AnimatePresence>
{showTypingIndicator ? (
<motion.div
initial={{ opacity: 0, y: 10, scale: 0.94 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ type: "spring", stiffness: 300 }}
style={{ alignSelf: "flex-start", display: "flex", gap: 12, marginTop: 4, marginLeft: 44 }}
>
<Paper
elevation={0}
sx={{
p: 1.3,
borderRadius: 4,
bgcolor: alpha("#fff", 0.82),
boxShadow: `0 4px 12px ${alpha(theme.palette.common.black, 0.05)}`,
}}
>
<TypingIndicator />
</Paper>
</motion.div>
) : null}
<div ref={bottomRef} style={{ height: 1 }} />
</Box>
);
};