"use client"; import React from "react"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import { motion } from "framer-motion"; import { Avatar, Box, Chip, IconButton, LinearProgress, Paper, Stack, Typography, alpha, } from "@mui/material"; import type { Theme } from "@mui/material/styles"; import AutoAwesome from "@mui/icons-material/AutoAwesome"; import CheckCircleRounded from "@mui/icons-material/CheckCircleRounded"; import ErrorOutlineRounded from "@mui/icons-material/ErrorOutlineRounded"; import HourglassEmptyRounded from "@mui/icons-material/HourglassEmptyRounded"; import VolumeUpRounded from "@mui/icons-material/VolumeUpRounded"; import PauseRounded from "@mui/icons-material/PauseRounded"; import PlayArrowRounded from "@mui/icons-material/PlayArrowRounded"; import StopRounded from "@mui/icons-material/StopRounded"; import { parseAssistantMessageSections, parseContentWithToolCalls, type ContentSegment, } from "./chatMessageSections"; import { ChatInlineChart } from "./ChatInlineChart"; import { ChatToolCallBlock } from "./ChatToolCallBlock"; import markdownStyles from "./GlobalChatboxMarkdown.module.css"; import type { ChatProgress, Message, SpeechState } from "./GlobalChatbox.types"; import { stripMarkdown } from "./GlobalChatbox.utils"; export const TypingIndicator = () => { return ( {[0, 1, 2].map((i) => ( ))} ); }; export const Blob = ({ color, size, top, left, delay, }: { color: string; size: number; top: string; left: string; delay: number; }) => ( ); type ChatMessageItemProps = { message: Message; theme: Theme; messageSpeechState: SpeechState; onSpeak: (messageId: string, text: string) => void; onPause: () => void; onResume: () => void; onStopSpeech: () => void; isTtsSupported: boolean; sseChartParams?: Array<{ tool: string; params: Record }>; }; export const ChatMessageItem = React.memo( ({ message, theme, messageSpeechState, onSpeak, onPause, onResume, onStopSpeech, isTtsSupported, sseChartParams, }: ChatMessageItemProps) => { const isUser = message.role === "user"; const isErrorMessage = Boolean(message.isError); const parsedAssistantSections = !isUser && !isErrorMessage ? parseAssistantMessageSections(message.content) : null; const answerContent = parsedAssistantSections?.answer ?? message.content; const contentSegments: ContentSegment[] = !isUser && !isErrorMessage ? parseContentWithToolCalls(answerContent).segments : [{ type: "text", content: answerContent }]; return ( {!isUser && ( {isErrorMessage ? ( ) : ( )} )} {!isUser && !isErrorMessage && message.progress?.length ? ( ) : null} {contentSegments.map((segment, segIdx) => { if (segment.type === "text") { const text = segment.content.trim(); if (!text && contentSegments.length > 1) return null; return (
{text || "..."}
); } if (segment.type === "tool_call") { if (segment.toolCall.tool === "chart") { return ( )} /> ); } if (segment.toolCall.tool === "show_chart") { const p = segment.toolCall.params; return ( ); } return ( ); } if (segment.type === "tool_call_pending") { return ( 正在准备工具调用... ); } return null; })} {sseChartParams?.map((chart, idx) => ( ))}
{!isUser && !isErrorMessage && isTtsSupported && ( {messageSpeechState === "idle" && ( onSpeak(message.id, stripMarkdown(answerContent))} aria-label="朗读消息" sx={{ color: "text.secondary", opacity: 0.6, "&:hover": { opacity: 1 }, p: 0.5, }} > )} {messageSpeechState === "playing" && ( <> )} {messageSpeechState === "paused" && ( <> )} )}
); }, ); ChatMessageItem.displayName = "ChatMessageItem"; const ChatProgressPanel = ({ progress }: { progress: ChatProgress[] }) => { const isComplete = progress.some( (item) => item.phase === "complete" && item.status === "completed", ); const latestRunning = isComplete ? undefined : [...progress].reverse().find((item) => item.status === "running"); return ( Agent 过程 {latestRunning ? ( ) : null} {latestRunning ? : null} {progress.slice(-5).map((item) => ( {item.status === "completed" ? ( ) : item.status === "error" ? ( ) : ( )} {item.title} {item.detail ? ( {item.detail} ) : null} ))} ); };