Cowork's status strip: the drawn order, and figures that are there on arrival

The drawing reads it left to right —

  Agent: qwen2.5-coder · Định tuyến: Tắt · ↓292.8K ↑102.7K · $0.31 · 📁 folder

— and the app had it split down the middle: usage and folder on the left, Agent
and routing away on the right. They are one run on the left now, in that order.
Nén and Tự chạy stay on the right, where the control inventory marks them
"giữ nguyên tại chỗ".

The figures were also missing. _usage_total_lbl was written in exactly one
place, at the end of a turn, so a thread opened from History showed an empty
strip however much it had already spent — the numbers only appeared once you
sent another message. refresh_usage() reads the same per-conversation events
_show_usage does and now runs wherever the thread changes: opening one, starting
a new one, or deriving a title from the first turn. Opening a seeded thread now
reads ↓103.5k ↑44.6k ▤201.0k $0.1187 straight away.

check_cowork_screen asserts the strip is non-empty for a thread with recorded
usage and that the four parts run left to right in the drawn order.

23/23 checkers pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nam Pham Dinh Thanh
2026-08-18 16:30:45 +09:00
co-authored by Claude Opus 5
parent 60545654d6
commit 2f73aaa333
2 changed files with 59 additions and 5 deletions
+22
View File
@@ -87,6 +87,28 @@ def main() -> int:
fails.append(f"thieu nut {name} tren thanh cong cu")
print(f"nut tren thanh cong cu: {c.skills_btn.text()!r}, {c._new_btn.text()!r}")
# the status strip, read left to right, is
# Agent: … · Định tuyến: … · ↓in ↑out · $cost · 📁 folder
from PySide6.QtCore import QPoint as _P
bar = c._usage_total_lbl.parentWidget()
def _x(widget):
return widget.mapTo(bar, _P(0, 0)).x()
usage_text = c._usage_total_lbl.text()
print(f"usage tren dai: {usage_text!r}")
if not usage_text.strip():
fails.append("dai duoi khong hien token/chi phi cua thread da mo")
order = [("Agent", _x(c._agent_lbl)), ("Dinh tuyen", _x(c.routing_toggle)),
("usage", _x(c._usage_total_lbl))]
folder = getattr(c, "folder_lbl", None)
if folder is not None:
order.append(("thu muc", _x(folder)))
print("thu tu: " + " < ".join(f"{n}({x})" for n, x in order))
for (n1, x1), (n2, x2) in zip(order, order[1:]):
if x1 >= x2:
fails.append(f"dai duoi sai thu tu: {n1} khong dung truoc {n2}")
# the drawing gives Cowork two columns; History lives in the rail's RECENTS
# and its pane arrives folded to the strip the drawing keeps
w = win.workspace
+37 -5
View File
@@ -143,7 +143,7 @@ class ChatPanel(QWidget):
self._usage_total_lbl = QLabel("")
self._usage_total_lbl.setObjectName("hint")
self._usage_total_lbl.setStyleSheet(f"color: {current_palette().text_faint};")
self.composer.add_bottom_left(self._usage_total_lbl)
# Per-tab "Agent" = which MODEL this tab uses (e.g. deepseek / gemma /
# qwen for the local provider). Cowork and Code pick independently and
@@ -167,13 +167,19 @@ class ChatPanel(QWidget):
self.agent_combo.setMinimumWidth(150)
self.agent_combo.setToolTip(tr("chatpanel.agent_tooltip"))
self.agent_combo.currentIndexChanged.connect(self._on_agent_changed)
self.composer.add_bottom_right(self._agent_lbl)
self.composer.add_bottom_right(self.agent_combo)
self.composer.add_bottom_left(self._agent_lbl)
self.composer.add_bottom_left(self.agent_combo)
# Off/Auto/Manual routing toggle — lets the router pick the best-fit
# model per message (see core/routing + _apply_routing).
from .routing_toggle import RoutingToggle
self.routing_toggle = RoutingToggle(ctx, self.kind)
self.composer.add_bottom_right(self.routing_toggle)
# The drawing reads the strip left to right as
# Agent: … · Định tuyến: … · ↓in ↑out · $cost · 📁 folder
# so these sit together on the left, with the folder box the Cowork tab
# appends landing after them. Nén and Tự chạy stay on the right, where
# the control inventory marks them "giữ nguyên tại chỗ".
self.composer.add_bottom_left(self.routing_toggle)
self.composer.add_bottom_left(self._usage_total_lbl)
# Manual "compress conversation" — trim old history to cut tokens.
self.compress_btn = QPushButton(tr("chatpanel.compress_btn"))
self.compress_btn.setIcon(app_icon("compress"))
@@ -1413,6 +1419,26 @@ class ChatPanel(QWidget):
return [e for e in ut.load_events()
if e.get("source") == self.kind and e.get("label") == label]
def refresh_usage(self) -> None:
"""Show what this conversation has already cost.
The label was written only at the end of a turn, so opening a thread
from History left the strip blank however much it had spent.
"""
from ..core import model_pricing as mp
from ..core import usage_tracker as ut
cur = self._usage_snapshot()
if not (cur["in"] or cur["out"] or cur["cache"]):
self._usage_total_lbl.setText("")
return
# same source _show_usage reads, so the two never disagree
pricing = {**ut.DEFAULT_PRICING, **(self.ctx.config.data.get("usage") or {})}
self._usage_total_lbl.setText(
f"↓{mp.format_tokens(cur['in'])} ↑{mp.format_tokens(cur['out'])} "
f"▤{mp.format_tokens(cur['in'] + cur['out'] + cur['cache'])} "
f"{ut.format_cost(self._session_cost_usd(), pricing)}")
def _usage_snapshot(self) -> Dict[str, int]:
"""Cumulative in/out/cache tokens for THIS conversation so far."""
snap = {"in": 0, "out": 0, "cache": 0}
@@ -1684,10 +1710,16 @@ class ChatPanel(QWidget):
self.history_changed.emit() # current view changed → refresh History highlight
def _notify_title(self) -> None:
"""Let a screen that heads itself with the thread title follow along."""
"""Let a screen that heads itself with the thread title follow along.
The thread also decides what the usage strip should read, so refresh
that here rather than at each of the three places the title changes.
"""
hook = getattr(self, "refresh_title", None)
if callable(hook):
hook()
if getattr(self, "_usage_total_lbl", None) is not None:
self.refresh_usage()
def load_conversation(self, conv: Dict[str, Any]) -> None:
"""Switch the view to a stored conversation. Allowed while work is running —