Files
windmill/frontend/src/lib/components/copilot/chat/ContextUsageIndicator.svelte
T
GuilhemandClaude Fable 5.1 a9ec0aec3a feat: render the flow chat through the shared session chat components (#11175)
* feat: render the flow chat through the shared session chat components

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: settle the flow chat queue on the turn's outcome and guard IME enter

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: keep copilot free-tier banners and deploy-locked sends out of the flow chat

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 17:45:01 +02:00

76 lines
3.3 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import { copilotInfo, copilotSessionModel } from '$lib/aiStore'
import { getKnownModelContextWindow, getModelContextWindow } from '../modelConfig'
import { getChatViewHost } from './chatViewHost'
import { AIMode } from './AIChatManager.svelte'
import UsageMeter from './UsageMeter.svelte'
import { formatTokenCount } from './tokenUsage'
const chatHost = getChatViewHost()
// The `/compact` slash command is only wired up in session-chat GLOBAL mode,
// so only advertise it where it actually works.
let canCompact = $derived(chatHost.isSessionChat && chatHost.mode === AIMode.GLOBAL)
let providerModel = $derived(
$copilotSessionModel ?? $copilotInfo.defaultModel ?? $copilotInfo.aiModels[0]
)
// The same number the compaction trigger uses: the known window when the
// model is listed, otherwise the conservative window the trigger assumes.
// The tooltip marks the assumed case so the guess never reads as a spec.
let contextWindow = $derived(
providerModel ? getModelContextWindow(providerModel.model) : undefined
)
let windowIsAssumed = $derived(
providerModel !== undefined && getKnownModelContextWindow(providerModel.model) === undefined
)
// The same number the compaction trigger uses: the provider's report when
// one describes the current history (one turn stale by nature), otherwise
// a live chars/4 estimate of the stored context.
let usedTokens = $derived(Math.round(chatHost.contextTokens))
// Always surface usage once a conversation has started, at any fill level, so
// the user can watch context grow toward the compaction threshold.
let visible = $derived(usedTokens > 0 && chatHost.messages.length > 0)
// Compaction triggers at 80% of the window (COMPACTION_TRIGGER_RATIO); the
// gauge fills toward that point and turns red once it is reached.
const COMPACTION_TRIGGER_RATIO = 0.8
let ratio = $derived(contextWindow ? Math.min(usedTokens / contextWindow, 1) : undefined)
let fillPct = $derived(ratio !== undefined ? Math.round(ratio * 100) : undefined)
let fillClass = $derived(
ratio === undefined
? 'bg-tertiary'
: ratio >= COMPACTION_TRIGGER_RATIO
? 'bg-red-500'
: ratio >= COMPACTION_TRIGGER_RATIO * 0.75
? 'bg-amber-500'
: 'bg-surface-accent-primary'
)
</script>
{#if visible}
<!-- Only a meter when we know the window: it's a 0100% reading. With an unknown
window there's no max to measure against, so it's a plain labeled indicator
(the bar is decorative/full and the token count lives in the tooltip). -->
<UsageMeter {fillPct} {fillClass} ariaLabel="Context window usage">
{#snippet tooltip()}
<div class="text-xs whitespace-nowrap">
<p class="font-semibold">Context usage</p>
<p class="mt-1 tabular-nums">
~{formatTokenCount(usedTokens)}{contextWindow
? ` / ${formatTokenCount(contextWindow)}${windowIsAssumed ? ' assumed' : ''}`
: ''}{fillPct !== undefined ? ` (${fillPct}%)` : ''}
</p>
{#if ratio !== undefined && ratio >= COMPACTION_TRIGGER_RATIO}
<p class="mt-1 text-tertiary">History will be compacted soon to free up space.</p>
{/if}
{#if canCompact}
<p class="mt-1 text-tertiary">
Type <span class="font-mono">/compact</span> to summarize and free up space now.
</p>
{/if}
</div>
{/snippet}
</UsageMeter>
{/if}