diff --git a/web/src/components/chat/ChatMessage.tsx b/web/src/components/chat/ChatMessage.tsx index 9a91d70352..0a5c02763f 100644 --- a/web/src/components/chat/ChatMessage.tsx +++ b/web/src/components/chat/ChatMessage.tsx @@ -17,6 +17,7 @@ import { import { cn } from "@/lib/utils"; import { ChatAttachmentChip } from "@/components/chat/ChatAttachmentChip"; import { parseAttachedEvent } from "@/utils/chatUtil"; +import type { ChatStats, ShowStatsMode } from "@/types/chat"; type MessageBubbleProps = { role: "user" | "assistant"; @@ -24,14 +25,29 @@ type MessageBubbleProps = { messageIndex?: number; onEditSubmit?: (messageIndex: number, newContent: string) => void; isComplete?: boolean; + stats?: ChatStats; + showStats?: ShowStatsMode; }; +function formatTokens(n: number | undefined): string | null { + if (n === undefined) return null; + if (n >= 1000) return `${(n / 1000).toFixed(1)}k`; + return String(n); +} + +function formatRate(rate: number | undefined): string | null { + if (rate === undefined || rate <= 0) return null; + return rate >= 10 ? rate.toFixed(0) : rate.toFixed(1); +} + export function MessageBubble({ role, content, messageIndex = 0, onEditSubmit, isComplete = true, + stats, + showStats = "while_generating", }: MessageBubbleProps) { const { t } = useTranslation(["views/chat", "common"]); const isUser = role === "user"; @@ -214,7 +230,7 @@ export function MessageBubble({ )} -
+
{isUser && onEditSubmit != null && ( @@ -256,6 +272,27 @@ export function MessageBubble({ )} + {!isUser && + stats && + (showStats === "always" || !isComplete) && + (() => { + const ctx = formatTokens(stats.promptTokens); + const rate = formatRate(stats.tokensPerSecond); + if (ctx === null && rate === null) return null; + return ( +
+ {ctx !== null && ( + {t("stats.context", { tokens: ctx })} + )} + {ctx !== null && rate !== null && ( + + )} + {rate !== null && ( + {t("stats.tokens_per_second", { rate })} + )} +
+ ); + })()}
);