"use client"; 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 WaterDropRounded from "@mui/icons-material/WaterDropRounded"; import SensorsRounded from "@mui/icons-material/SensorsRounded"; import TroubleshootRounded from "@mui/icons-material/TroubleshootRounded"; import MapRounded from "@mui/icons-material/MapRounded"; import { AgentTurn } from "./AgentTurn"; import { TypingIndicator } from "./GlobalChatbox.parts"; import type { PermissionReply } from "@/lib/chatStream"; import type { Message, SpeechState, } from "./GlobalChatbox.types"; type AgentWorkspaceProps = { messages: Message[]; isStreaming: boolean; bottomRef: React.RefObject; speakingMessageId: string | null; speechState: SpeechState; onSpeak: (messageId: string, text: string) => void; onPauseSpeech: () => void; onResumeSpeech: () => void; onStopSpeech: () => void; isTtsSupported: boolean; onCreateBranch: (messageId: string) => void; onReplyPermission: (requestId: string, reply: PermissionReply) => void; onReplyQuestion: (requestId: string, answers: string[][]) => void; onRejectQuestion: (requestId: string) => void; }; type TurnListProps = { messages: Message[]; isStreaming: boolean; speakingMessageId: string | null; speechState: SpeechState; onSpeak: (messageId: string, text: string) => void; onPauseSpeech: () => void; onResumeSpeech: () => void; onStopSpeech: () => void; isTtsSupported: boolean; onCreateBranch: (messageId: string) => void; onReplyPermission: (requestId: string, reply: PermissionReply) => void; onReplyQuestion: (requestId: string, answers: string[][]) => void; onRejectQuestion: (requestId: string) => void; }; const sameMessages = (left: Message[], right: Message[]) => left.length === right.length && left.every((message, index) => message === right[index]); const TurnListInner = ({ messages, isStreaming, speakingMessageId, speechState, onSpeak, onPauseSpeech, onResumeSpeech, onStopSpeech, isTtsSupported, onCreateBranch, onReplyPermission, onReplyQuestion, onRejectQuestion, }: TurnListProps) => { return ( <> {messages.map((message) => ( ))} ); }; const TurnList = React.memo( TurnListInner, (prevProps, nextProps) => sameMessages(prevProps.messages, nextProps.messages) && prevProps.isStreaming === nextProps.isStreaming && prevProps.speakingMessageId === nextProps.speakingMessageId && prevProps.speechState === nextProps.speechState && prevProps.onSpeak === nextProps.onSpeak && prevProps.onPauseSpeech === nextProps.onPauseSpeech && prevProps.onResumeSpeech === nextProps.onResumeSpeech && prevProps.onStopSpeech === nextProps.onStopSpeech && prevProps.isTtsSupported === nextProps.isTtsSupported && prevProps.onCreateBranch === nextProps.onCreateBranch && prevProps.onReplyPermission === nextProps.onReplyPermission && prevProps.onReplyQuestion === nextProps.onReplyQuestion && prevProps.onRejectQuestion === nextProps.onRejectQuestion, ); TurnList.displayName = "TurnList"; const EmptyState = () => { const theme = useTheme(); const capabilities = [ { icon: , label: "水力瓶颈识别" }, { icon: , label: "异常状态预警" }, { icon: , label: "调度与改造建议" }, { icon: , label: "GIS 地图联动" }, ]; return ( TJWater Agent 我已就绪,请描述任务 你可以使用自然语言下达指令,我会自主规划决策执行、并在地图上呈现分析结果。 {capabilities.map((item) => ( {item.icon} {item.label} ))} ); }; export const AgentWorkspace = ({ messages, isStreaming, bottomRef, speakingMessageId, speechState, onSpeak, onPauseSpeech, onResumeSpeech, onStopSpeech, isTtsSupported, onCreateBranch, onReplyPermission, onReplyQuestion, onRejectQuestion, }: 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))); const streamingMessage = isStreaming && messages.at(-1)?.role === "assistant" ? messages.at(-1) : undefined; const historyMessages = streamingMessage !== undefined ? messages.slice(0, -1) : messages; return ( {messages.length === 0 ? : null} {messages.length > 0 ? ( {streamingMessage ? ( ) : null} ) : null} {showTypingIndicator ? ( ) : null}
); };