feat(ui): 添加 Token 消耗统计显示
- 状态栏显示当前会话 Token 消耗总量,悬停显示详情 - AI 消息底部显示本次响应的输入/输出 Token - 会话列表顶部显示项目 Token 消耗总量 - 会话列表每项显示该会话的 Token 消耗 - 新增 Token 统计 API 客户端函数 - Server done 事件携带 usage 信息
This commit is contained in:
@@ -15,6 +15,7 @@ import {
|
||||
CheckCircle2,
|
||||
Loader2,
|
||||
GitCompare,
|
||||
Coins,
|
||||
} from 'lucide-react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { useState, forwardRef } from 'react';
|
||||
@@ -41,11 +42,28 @@ interface ChatMessageProps {
|
||||
onDenyPermission?: (requestId: string, remember: boolean) => void;
|
||||
}
|
||||
|
||||
// 格式化 Token 数量
|
||||
const formatTokens = (tokens: number): string => {
|
||||
if (tokens >= 1000000) {
|
||||
return `${(tokens / 1000000).toFixed(2)}M`;
|
||||
}
|
||||
if (tokens >= 1000) {
|
||||
return `${(tokens / 1000).toFixed(1)}k`;
|
||||
}
|
||||
return `${tokens}`;
|
||||
};
|
||||
|
||||
export const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>(
|
||||
({ message, isStreaming = false, onAnswerQuestion, onViewDiff, onAllowPermission, onDenyPermission }, ref) => {
|
||||
const isUser = message.role === 'user';
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
// Token 信息
|
||||
const hasTokenInfo = !isUser && message.metadata?.inputTokens !== undefined;
|
||||
const inputTokens = message.metadata?.inputTokens ?? 0;
|
||||
const outputTokens = message.metadata?.outputTokens ?? 0;
|
||||
const totalTokens = message.metadata?.totalTokens ?? (inputTokens + outputTokens);
|
||||
|
||||
const handleCopy = async () => {
|
||||
await navigator.clipboard.writeText(message.content ?? '');
|
||||
setCopied(true);
|
||||
@@ -184,6 +202,22 @@ export const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>(
|
||||
</button>
|
||||
</div>
|
||||
{renderContent()}
|
||||
{/* Token 统计显示(仅 AI 消息,非流式输出时) */}
|
||||
{hasTokenInfo && !isStreaming && totalTokens > 0 && (
|
||||
<div className="mt-2 pt-2 border-t border-line/50 flex items-center gap-3 text-xs text-fg-subtle">
|
||||
<span className="flex items-center gap-1" title="Token 消耗">
|
||||
<Coins size={12} className="text-fg-muted" />
|
||||
<span>{formatTokens(totalTokens)}</span>
|
||||
</span>
|
||||
<span className="text-fg-muted/40">|</span>
|
||||
<span title="输入 Token">
|
||||
↓ {formatTokens(inputTokens)}
|
||||
</span>
|
||||
<span title="输出 Token">
|
||||
↑ {formatTokens(outputTokens)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user