fix(chat): add history loading skeletons
Build Push and Deploy / docker-image (push) Successful in 1m2s
Build Push and Deploy / deploy-fallback-log (push) Has been skipped

This commit is contained in:
2026-06-10 17:51:28 +08:00
parent ab9e2a0420
commit 9c0a7a2864
6 changed files with 254 additions and 57 deletions
+110 -40
View File
@@ -3,7 +3,7 @@
import Image from "next/image";
import React from "react";
import { AnimatePresence, motion } from "framer-motion";
import { Box, Paper, Stack, Typography, alpha, useTheme, Grid } from "@mui/material";
import { Box, Paper, Skeleton, Stack, Typography, alpha, useTheme, Grid } from "@mui/material";
import WaterDropRounded from "@mui/icons-material/WaterDropRounded";
import SensorsRounded from "@mui/icons-material/SensorsRounded";
import TroubleshootRounded from "@mui/icons-material/TroubleshootRounded";
@@ -20,6 +20,7 @@ import type {
type AgentWorkspaceProps = {
messages: Message[];
isStreaming: boolean;
isLoadingSession?: boolean;
bottomRef: React.RefObject<HTMLDivElement | null>;
speakingMessageId: string | null;
speechState: SpeechState;
@@ -226,9 +227,72 @@ const EmptyState = () => {
);
};
const SessionLoadingSkeleton = () => (
<Stack
spacing={2.25}
aria-label="正在加载历史记录"
sx={{ width: "100%", maxWidth: 760, alignSelf: "stretch" }}
>
{Array.from({ length: 2 }, (_, turnIndex) => (
<Stack key={turnIndex} spacing={1.25}>
<Stack direction="row" justifyContent="flex-end">
<Paper
elevation={0}
sx={{
width: turnIndex === 0 ? "72%" : "64%",
maxWidth: "86%",
p: 1.75,
borderRadius: 5,
borderBottomRightRadius: 2,
bgcolor: alpha("#00acc1", 0.16),
border: `1px solid ${alpha("#00acc1", 0.12)}`,
boxShadow: `0 8px 24px -12px ${alpha("#00acc1", 0.35)}`,
}}
>
<Stack spacing={0.85}>
<Skeleton variant="text" width="76%" height={18} />
<Skeleton variant="text" width="48%" height={15} />
</Stack>
</Paper>
</Stack>
<Stack direction="row" spacing={1.5} alignItems="flex-start">
<Skeleton
variant="circular"
width={34}
height={34}
sx={{ bgcolor: alpha("#00acc1", 0.12), flexShrink: 0, mt: 0.25 }}
/>
<Paper
elevation={0}
sx={{
flex: 1,
minWidth: 0,
p: 2,
borderRadius: 5,
bgcolor: alpha("#ffffff", 0.52),
border: `1px solid ${alpha("#fff", 0.72)}`,
boxShadow: `0 10px 30px -10px ${alpha("#000", 0.06)}`,
}}
>
<Stack spacing={1}>
<Skeleton variant="text" width="38%" height={16} />
<Skeleton variant="text" width="94%" height={16} />
<Skeleton variant="text" width={turnIndex === 0 ? "88%" : "82%"} height={16} />
<Skeleton variant="text" width={turnIndex === 0 ? "78%" : "70%"} height={16} />
<Skeleton variant="rounded" width="100%" height={turnIndex === 0 ? 104 : 76} sx={{ borderRadius: 2 }} />
</Stack>
</Paper>
</Stack>
</Stack>
))}
</Stack>
);
export const AgentWorkspace = ({
messages,
isStreaming,
isLoadingSession = false,
bottomRef,
speakingMessageId,
speechState,
@@ -270,49 +334,55 @@ export const AgentWorkspace = ({
zIndex: 5,
}}
>
<AnimatePresence initial={false}>
{messages.length === 0 ? <EmptyState /> : null}
</AnimatePresence>
{isLoadingSession ? (
<SessionLoadingSkeleton />
) : (
<>
<AnimatePresence initial={false}>
{messages.length === 0 ? <EmptyState /> : null}
</AnimatePresence>
{messages.length > 0 ? (
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
<TurnList
messages={historyMessages}
isStreaming={isStreaming}
speakingMessageId={speakingMessageId}
speechState={speechState}
onSpeak={onSpeak}
onPauseSpeech={onPauseSpeech}
onResumeSpeech={onResumeSpeech}
onStopSpeech={onStopSpeech}
isTtsSupported={isTtsSupported}
onCreateBranch={onCreateBranch}
onReplyPermission={onReplyPermission}
onReplyQuestion={onReplyQuestion}
onRejectQuestion={onRejectQuestion}
/>
{messages.length > 0 ? (
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
<TurnList
messages={historyMessages}
isStreaming={isStreaming}
speakingMessageId={speakingMessageId}
speechState={speechState}
onSpeak={onSpeak}
onPauseSpeech={onPauseSpeech}
onResumeSpeech={onResumeSpeech}
onStopSpeech={onStopSpeech}
isTtsSupported={isTtsSupported}
onCreateBranch={onCreateBranch}
onReplyPermission={onReplyPermission}
onReplyQuestion={onReplyQuestion}
onRejectQuestion={onRejectQuestion}
/>
{streamingMessage ? (
<TurnList
messages={[streamingMessage]}
isStreaming={isStreaming}
speakingMessageId={speakingMessageId}
speechState={speechState}
onSpeak={onSpeak}
onPauseSpeech={onPauseSpeech}
onResumeSpeech={onResumeSpeech}
onStopSpeech={onStopSpeech}
isTtsSupported={isTtsSupported}
onCreateBranch={onCreateBranch}
onReplyPermission={onReplyPermission}
onReplyQuestion={onReplyQuestion}
onRejectQuestion={onRejectQuestion}
/>
{streamingMessage ? (
<TurnList
messages={[streamingMessage]}
isStreaming={isStreaming}
speakingMessageId={speakingMessageId}
speechState={speechState}
onSpeak={onSpeak}
onPauseSpeech={onPauseSpeech}
onResumeSpeech={onResumeSpeech}
onStopSpeech={onStopSpeech}
isTtsSupported={isTtsSupported}
onCreateBranch={onCreateBranch}
onReplyPermission={onReplyPermission}
onReplyQuestion={onReplyQuestion}
onRejectQuestion={onRejectQuestion}
/>
) : null}
</Box>
) : null}
</Box>
) : null}
</>
)}
{showTypingIndicator ? (
{!isLoadingSession && showTypingIndicator ? (
<motion.div
initial={{ opacity: 0, y: 10, scale: 0.94 }}
animate={{ opacity: 1, y: 0, scale: 1 }}