Coverage for src/lilbee/cli/tui/widgets/context_chip.py: 100%
30 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-14 11:46 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-14 11:46 +0000
1"""Live context-window usage, and whether a summarize call is in flight.
3One chip for both: they are the same question from the user's side -- how much
4can the model still see, and why did it just pause.
5"""
7from __future__ import annotations
9from pathlib import Path
10from typing import ClassVar
12from textual.content import Content
13from textual.reactive import reactive
14from textual.widget import Widget
16from lilbee.cli.tui import messages as msg
17from lilbee.core.config import cfg
18from lilbee.retrieval.query.compaction import COMPACT_TRIGGER_FRACTION
20_CSS_FILE = Path(__file__).with_suffix(".tcss")
22# Below this the chip stays quiet: a half-empty window is not news.
23_QUIET_BELOW = 0.5
24# Linked, not duplicated: amber must mean exactly "where compaction fires",
25# or the gauge lies when the trigger moves.
26_PRESSURE_AT = COMPACT_TRIGGER_FRACTION
29class ContextChip(Widget):
30 """How full the chat's history budget is, and whether it is condensing now."""
32 DEFAULT_CSS: ClassVar[str] = _CSS_FILE.read_text(encoding="utf-8")
34 # layout=True is load-bearing: width is `auto`, and a repaint redraws
35 # inside the already-measured box. Mounted empty (usage 0 -> no text) the
36 # chip measures zero columns and stays invisible without a re-layout.
37 usage: reactive[float] = reactive(0.0, layout=True)
38 """Fraction of the history budget the conversation currently occupies."""
40 compacting: reactive[bool] = reactive(False, layout=True)
41 """True while a summarizing model call is in flight and blocking the turn."""
43 def on_mount(self) -> None:
44 self.tooltip = msg.CONTEXT_CHIP_TOOLTIP
46 def render(self) -> Content:
47 if self.compacting:
48 return Content.styled(msg.CONTEXT_CHIP_COMPACTING, "$warning")
49 if self.usage < _QUIET_BELOW:
50 return Content("")
51 percent = min(int(self.usage * 100), 100)
52 if self.usage < _PRESSURE_AT:
53 return Content.styled(msg.CONTEXT_CHIP_USAGE.format(percent=percent), "$text-muted")
54 # Nearly full. With compaction on this resolves itself, so the number is
55 # enough; with it off, turns are about to leave the model's view and the
56 # user still has time to do something about it.
57 template = (
58 msg.CONTEXT_CHIP_USAGE if cfg.chat_compaction else msg.CONTEXT_CHIP_USAGE_DROPPING
59 )
60 return Content.styled(template.format(percent=percent), "$warning")