mirror of
https://github.com/ibelick/webclaw.git
synced 2026-08-14 09:02:04 +00:00
Merge pull request #13 from robbyczgw-cla/feat/context-meter-upstream
feat: Context Window Meter
This commit is contained in:
@@ -88,6 +88,7 @@ export function ChatScreen({
|
||||
const {
|
||||
sessionsQuery,
|
||||
sessions,
|
||||
activeSession,
|
||||
activeExists,
|
||||
activeSessionKey,
|
||||
activeTitle,
|
||||
@@ -161,7 +162,8 @@ export function ChatScreen({
|
||||
streamStop()
|
||||
setPendingGeneration(false)
|
||||
setWaitingForResponse(false)
|
||||
}, [streamStop])
|
||||
void queryClient.invalidateQueries({ queryKey: chatQueryKeys.sessions })
|
||||
}, [queryClient, streamStop])
|
||||
const streamStart = useCallback(() => {
|
||||
if (!activeFriendlyId || isNewChat) return
|
||||
if (streamTimer.current) window.clearInterval(streamTimer.current)
|
||||
@@ -608,6 +610,8 @@ export function ChatScreen({
|
||||
wrapperRef={headerRef}
|
||||
showSidebarButton={isMobile}
|
||||
onOpenSidebar={handleOpenSidebar}
|
||||
usedTokens={activeSession?.totalTokens}
|
||||
maxTokens={activeSession?.contextTokens}
|
||||
/>
|
||||
|
||||
{hideUi ? null : (
|
||||
|
||||
@@ -2,12 +2,15 @@ import { memo } from 'react'
|
||||
import { HugeiconsIcon } from '@hugeicons/react'
|
||||
import { Menu01Icon } from '@hugeicons/core-free-icons'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { ContextMeter } from './context-meter'
|
||||
|
||||
type ChatHeaderProps = {
|
||||
activeTitle: string
|
||||
wrapperRef?: React.Ref<HTMLDivElement>
|
||||
showSidebarButton?: boolean
|
||||
onOpenSidebar?: () => void
|
||||
usedTokens?: number
|
||||
maxTokens?: number
|
||||
}
|
||||
|
||||
function ChatHeaderComponent({
|
||||
@@ -15,6 +18,8 @@ function ChatHeaderComponent({
|
||||
wrapperRef,
|
||||
showSidebarButton = false,
|
||||
onOpenSidebar,
|
||||
usedTokens,
|
||||
maxTokens,
|
||||
}: ChatHeaderProps) {
|
||||
return (
|
||||
<div
|
||||
@@ -33,6 +38,11 @@ function ChatHeaderComponent({
|
||||
</Button>
|
||||
) : null}
|
||||
<div className="text-sm font-medium truncate">{activeTitle}</div>
|
||||
<ContextMeter
|
||||
usedTokens={usedTokens}
|
||||
maxTokens={maxTokens}
|
||||
className="ml-3 hidden sm:flex"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { memo, useMemo } from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
type ContextMeterProps = {
|
||||
usedTokens?: number
|
||||
maxTokens?: number
|
||||
className?: string
|
||||
}
|
||||
|
||||
function ContextMeterComponent({
|
||||
usedTokens,
|
||||
maxTokens,
|
||||
className,
|
||||
}: ContextMeterProps) {
|
||||
const { percentage, color, label } = useMemo(() => {
|
||||
if (!usedTokens || !maxTokens) return { percentage: 0, color: '', label: '' }
|
||||
const pct = Math.min((usedTokens / maxTokens) * 100, 100)
|
||||
const fmt = (n: number) =>
|
||||
n >= 1000 ? `${(n / 1000).toFixed(0)}K` : String(n)
|
||||
return {
|
||||
percentage: pct,
|
||||
color:
|
||||
pct >= 90
|
||||
? 'bg-red-500'
|
||||
: pct >= 70
|
||||
? 'bg-yellow-500'
|
||||
: 'bg-green-500',
|
||||
label: `${fmt(usedTokens)} / ${fmt(maxTokens)} tokens (${pct.toFixed(0)}%)`,
|
||||
}
|
||||
}, [usedTokens, maxTokens])
|
||||
|
||||
if (!usedTokens || !maxTokens || percentage === 0) return null
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center gap-2 text-xs text-primary-500',
|
||||
className,
|
||||
)}
|
||||
title={label}
|
||||
>
|
||||
<div className="w-20 h-1.5 bg-primary-100 rounded-full overflow-hidden">
|
||||
<div
|
||||
className={cn(
|
||||
'h-full rounded-full transition-all duration-500',
|
||||
color,
|
||||
percentage >= 95 && 'animate-pulse',
|
||||
)}
|
||||
style={{ width: `${percentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="tabular-nums whitespace-nowrap">
|
||||
{percentage.toFixed(0)}%
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const ContextMeter = memo(ContextMeterComponent)
|
||||
@@ -49,6 +49,8 @@ export type SessionSummary = {
|
||||
updatedAt?: number
|
||||
lastMessage?: GatewayMessage | null
|
||||
friendlyId?: string
|
||||
totalTokens?: number
|
||||
contextTokens?: number
|
||||
}
|
||||
|
||||
export type SessionListResponse = {
|
||||
@@ -69,6 +71,8 @@ export type SessionMeta = {
|
||||
label?: string
|
||||
updatedAt?: number
|
||||
lastMessage?: GatewayMessage | null
|
||||
totalTokens?: number
|
||||
contextTokens?: number
|
||||
}
|
||||
|
||||
export type PathsPayload = {
|
||||
|
||||
@@ -97,6 +97,14 @@ export function normalizeSessions(
|
||||
updatedAt:
|
||||
typeof session.updatedAt === 'number' ? session.updatedAt : undefined,
|
||||
lastMessage: session.lastMessage ?? null,
|
||||
totalTokens:
|
||||
typeof session.totalTokens === 'number'
|
||||
? session.totalTokens
|
||||
: undefined,
|
||||
contextTokens:
|
||||
typeof session.contextTokens === 'number'
|
||||
? session.contextTokens
|
||||
: undefined,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user