From 38072b27f51d8599455644db11987fd8a2bb8bc4 Mon Sep 17 00:00:00 2001 From: phact Date: Wed, 14 Jan 2026 11:20:55 -0500 Subject: [PATCH] usage frontend --- .../chat/_components/assistant-message.tsx | 6 ++++- frontend/app/chat/_components/token-usage.tsx | 27 +++++++++++++++++++ frontend/app/chat/_types/types.ts | 13 +++++++++ frontend/app/chat/page.tsx | 13 +++++++++ frontend/hooks/useChatStreaming.ts | 7 +++++ 5 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 frontend/app/chat/_components/token-usage.tsx diff --git a/frontend/app/chat/_components/assistant-message.tsx b/frontend/app/chat/_components/assistant-message.tsx index 0a046af0..ae6cffbb 100644 --- a/frontend/app/chat/_components/assistant-message.tsx +++ b/frontend/app/chat/_components/assistant-message.tsx @@ -3,9 +3,10 @@ import { motion } from "motion/react"; import DogIcon from "@/components/icons/dog-icon"; import { MarkdownRenderer } from "@/components/markdown-renderer"; import { cn } from "@/lib/utils"; -import type { FunctionCall } from "../_types/types"; +import type { FunctionCall, TokenUsage as TokenUsageType } from "../_types/types"; import { FunctionCalls } from "./function-calls"; import { Message } from "./message"; +import { TokenUsage } from "./token-usage"; interface AssistantMessageProps { content: string; @@ -21,6 +22,7 @@ interface AssistantMessageProps { animate?: boolean; delay?: number; isInitialGreeting?: boolean; + usage?: TokenUsageType; } export function AssistantMessage({ @@ -37,6 +39,7 @@ export function AssistantMessage({ animate = true, delay = 0.2, isInitialGreeting = false, + usage, }: AssistantMessageProps) { return ( + {usage && !isStreaming && } diff --git a/frontend/app/chat/_components/token-usage.tsx b/frontend/app/chat/_components/token-usage.tsx new file mode 100644 index 00000000..2fc5f03a --- /dev/null +++ b/frontend/app/chat/_components/token-usage.tsx @@ -0,0 +1,27 @@ +import { Zap } from "lucide-react"; +import type { TokenUsage as TokenUsageType } from "../_types/types"; + +interface TokenUsageProps { + usage: TokenUsageType; +} + +export function TokenUsage({ usage }: TokenUsageProps) { + // Guard against partial/malformed usage data + if (typeof usage.input_tokens !== "number" || typeof usage.output_tokens !== "number") { + return null; + } + + return ( +
+ + + {usage.input_tokens.toLocaleString()} in / {usage.output_tokens.toLocaleString()} out + {usage.input_tokens_details?.cached_tokens ? ( + + ({usage.input_tokens_details.cached_tokens.toLocaleString()} cached) + + ) : null} + +
+ ); +} diff --git a/frontend/app/chat/_types/types.ts b/frontend/app/chat/_types/types.ts index c0d732ea..c605da7c 100644 --- a/frontend/app/chat/_types/types.ts +++ b/frontend/app/chat/_types/types.ts @@ -1,3 +1,15 @@ +export interface TokenUsage { + input_tokens: number; + output_tokens: number; + total_tokens: number; + input_tokens_details?: { + cached_tokens?: number; + }; + output_tokens_details?: { + reasoning_tokens?: number; + }; +} + export interface Message { role: "user" | "assistant"; content: string; @@ -5,6 +17,7 @@ export interface Message { functionCalls?: FunctionCall[]; isStreaming?: boolean; source?: "langflow" | "chat"; + usage?: TokenUsage; } export interface FunctionCall { diff --git a/frontend/app/chat/page.tsx b/frontend/app/chat/page.tsx index f15cf788..4d2ddfef 100644 --- a/frontend/app/chat/page.tsx +++ b/frontend/app/chat/page.tsx @@ -501,6 +501,17 @@ function ChatPage() { } else { console.log("No function calls found in message"); } + + // Extract usage data from response_data + if (msg.response_data && typeof msg.response_data === "object") { + const responseData = + typeof msg.response_data === "string" + ? JSON.parse(msg.response_data) + : msg.response_data; + if (responseData.usage) { + message.usage = responseData.usage; + } + } } return message; @@ -849,6 +860,7 @@ function ChatPage() { role: "assistant", content: result.response, timestamp: new Date(), + usage: result.usage, }; setMessages((prev) => [...prev, assistantMessage]); if (result.response_id) { @@ -1164,6 +1176,7 @@ function ChatPage() { messages.length === 1 && message.content === "How can I assist?" } + usage={message.usage} /> ), diff --git a/frontend/hooks/useChatStreaming.ts b/frontend/hooks/useChatStreaming.ts index 89d0d810..6d19ac08 100644 --- a/frontend/hooks/useChatStreaming.ts +++ b/frontend/hooks/useChatStreaming.ts @@ -3,6 +3,7 @@ import type { FunctionCall, Message, SelectedFilters, + TokenUsage, } from "@/app/chat/_types/types"; import { useChat } from "@/contexts/chat-context"; @@ -130,6 +131,7 @@ export function useChatStreaming({ let currentContent = ""; const currentFunctionCalls: FunctionCall[] = []; let newResponseId: string | null = null; + let usageData: TokenUsage | undefined; // Initialize streaming message if (!controller.signal.aborted && thisStreamId === streamIdRef.current) { @@ -448,6 +450,10 @@ export function useChatStreaming({ else if (chunk.type === "response.output_text.delta") { currentContent += chunk.delta || ""; } + // Handle response.completed event - capture usage + else if (chunk.type === "response.completed" && chunk.response?.usage) { + usageData = chunk.response.usage; + } // Handle OpenRAG backend format else if (chunk.output_text) { currentContent += chunk.output_text; @@ -567,6 +573,7 @@ export function useChatStreaming({ currentFunctionCalls.length > 0 ? currentFunctionCalls : undefined, timestamp: new Date(), isStreaming: false, + usage: usageData, }; if (!controller.signal.aborted && thisStreamId === streamIdRef.current) {