fix(platform): emit StreamUsage as SSE comment, move usage to popover

StreamUsage events crashed the frontend because the Vercel AI SDK uses
z.strictObject() and rejects unknown event types. Fix by overriding
to_sse() to emit as an SSE comment (invisible to the parser). Usage
data is already recorded server-side (session DB + Redis counters).

Move usage limits from sidebar footer back to a ChartBar icon button
in the sidebar header that opens a popover on click.
This commit is contained in:
Zamil Majdy
2026-03-13 15:14:14 +07:00
parent a5ed8fefa9
commit d4944fb22b
3 changed files with 33 additions and 9 deletions

View File

@@ -186,7 +186,12 @@ class StreamToolOutputAvailable(StreamBaseResponse):
class StreamUsage(StreamBaseResponse):
"""Token usage statistics."""
"""Token usage statistics.
Emitted as an SSE comment so the Vercel AI SDK parser ignores it
(it uses z.strictObject() and rejects unknown event types).
Usage data is recorded server-side (session DB + Redis counters).
"""
type: ResponseType = ResponseType.USAGE
promptTokens: int = Field(..., description="Number of uncached prompt tokens")
@@ -201,6 +206,10 @@ class StreamUsage(StreamBaseResponse):
default=0, description="Prompt tokens written to cache (25% cost)"
)
def to_sse(self) -> str:
"""Emit as SSE comment so the AI SDK parser ignores it."""
return f": usage {self.model_dump_json(exclude_none=True)}\n\n"
class StreamError(StreamBaseResponse):
"""Error response."""

View File

@@ -18,7 +18,6 @@ import { toast } from "@/components/molecules/Toast/use-toast";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarTrigger,
useSidebar,
@@ -259,6 +258,7 @@ export function ChatSidebar() {
Your chats
</Text>
<div className="relative left-5 flex items-center gap-1">
<UsageLimits />
<NotificationToggle />
<div className="relative left-1">
<SidebarTrigger />
@@ -418,11 +418,6 @@ export function ChatSidebar() {
</motion.div>
)}
</SidebarContent>
{!isCollapsed && (
<SidebarFooter className="border-t border-zinc-100 px-4 py-3">
<UsageLimits />
</SidebarFooter>
)}
</Sidebar>
<DeleteChatDialog

View File

@@ -1,4 +1,10 @@
import type { CoPilotUsageStatus } from "@/app/api/__generated__/models/coPilotUsageStatus";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/molecules/Popover/Popover";
import { ChartBar } from "@phosphor-icons/react";
import { useUsageLimits } from "./useUsageLimits";
function formatResetTime(resetsAt: Date): string {
@@ -97,7 +103,7 @@ export function UsagePanelContent({
href="/profile/credits"
className="text-[11px] text-blue-600 hover:underline dark:text-blue-400"
>
Manage billing &amp; credits
Learn more about usage limits
</a>
)}
</div>
@@ -110,5 +116,19 @@ export function UsageLimits() {
if (isLoading || !usage) return null;
if (usage.daily.limit <= 0 && usage.weekly.limit <= 0) return null;
return <UsagePanelContent usage={usage} />;
return (
<Popover>
<PopoverTrigger asChild>
<button
className="rounded p-1 text-black transition-colors hover:bg-zinc-50"
aria-label="Usage limits"
>
<ChartBar className="!size-5" />
</button>
</PopoverTrigger>
<PopoverContent align="start" className="w-64 p-3">
<UsagePanelContent usage={usage} />
</PopoverContent>
</Popover>
);
}