Files
cowork-local/presentation/dashboard/token_usage_card_widget.py
T
Nam Pham Dinh ThanhandClaude Opus 5 062ea4ba21 refactor(dashboard): R08-T13 — dashboard_tab.py 438 -> 215, tách 3 widget
token_usage_card_widget.py    93   5 thẻ số liệu + thẻ Ngân sách
    usage_chart_widget.py        119   biểu đồ tuần/tháng/năm + đường so sánh
    habits_widget.py             171   thói quen dùng token + nhận xét của AI

Ba widget THẬT, không phải mixin — khác với shell và Co4E, ba mảng này tách
bạch trên màn hình và không đọc state của nhau. Giao tiếp bằng signal:
budget_applied, filter_changed, status_message.

Điểm cần biết: các nút lật khoảng và hai ô chọn thuộc về UsageChartWidget
nhưng được Dashboard nhấc lên hàng điều khiển ở trên. Chúng là control của
biểu đồ, chỉ hiển thị ở chỗ khác.

Giữ 18 cầu tương thích cho tên cũ vì check_dashboard, check_design_parity và
check_controls_alive đọc thẳng self.card_total, self._chart_period_lbl...

756 test xanh. check_dashboard, check_design_parity, check_controls_alive qua.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 22:05:48 +09:00

94 lines
4.4 KiB
Python
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.
"""Hàng thẻ số liệu trên đầu Dashboard — R08-T13.
Năm con số (tổng / vào / ra / cache / chi phí) cộng thẻ Ngân sách.
Bố cục không phải sáu ô bằng nhau: chi phí là con số màn hình này sinh ra để
trả lời, nên nó chiếm một thẻ cao gấp đôi bên trái, bốn con số phụ xếp 2×2
bên cạnh. Sáu ô bằng nhau thì không có gì nói cho người dùng biết cái nào
đáng nhìn trước.
"""
from __future__ import annotations
from PySide6.QtCore import Signal
from PySide6.QtWidgets import QGridLayout, QWidget
from ...core import usage_tracker as ut
from ...i18n import tr
from ...ui.icons import icon
from ...ui.widgets import BudgetCard, StatCard
from ...ui.widgets import fmt_tokens as _fmt_tokens
class TokenUsageCardWidget(QWidget):
#: Người dùng bấm Áp dụng trên thẻ Ngân sách. Widget không tự ghi cấu hình
#: — nó chỉ báo; ai sở hữu cấu hình thì người đó ghi.
budget_applied = Signal(float)
def __init__(self, parent=None):
super().__init__(parent)
grid = QGridLayout(self)
grid.setContentsMargins(0, 0, 0, 0)
grid.setSpacing(8)
self.card_total = StatCard()
self.card_in = StatCard()
self.card_out = StatCard()
self.card_cache = StatCard()
self.card_cost = StatCard().as_hero()
# Thẻ chi phí bên trái, chiếm cả hai hàng; bốn con số phụ lấp khối 2×2.
grid.addWidget(self.card_cost, 0, 0, 2, 1)
for i, card in enumerate((self.card_total, self.card_in,
self.card_out, self.card_cache)):
grid.addWidget(card, i // 2, 1 + i % 2)
self.budget_card = BudgetCard()
self.budget_card.apply_btn.setIcon(icon("check"))
self.budget_card.apply_btn.clicked.connect(
lambda: self.budget_applied.emit(self.budget_card.budget_spin.value()))
grid.addWidget(self.budget_card, 0, 3, 2, 1)
# Cột thẻ chi phí và cột Ngân sách rộng hơn bốn ô nhỏ.
for col, stretch in ((0, 3), (1, 2), (2, 2), (3, 3)):
grid.setColumnStretch(col, stretch)
# ---- nạp số liệu -----------------------------------------------------
def set_usage(self, s: dict, costs: dict, pricing: dict) -> None:
est_note = (tr("dashboard.estimated_note", pct=int(s["estimated_share"] * 100))
if s["estimated_share"] > 0 else "")
self.card_total.set(tr("dashboard.card_total"), _fmt_tokens(s["total"]),
tr("dashboard.card_turns", n=s["turns"]))
self.card_in.set(tr("dashboard.card_in"), _fmt_tokens(s["in"]),
ut.format_cost(costs["in"], pricing))
self.card_out.set(tr("dashboard.card_out"), _fmt_tokens(s["out"]),
ut.format_cost(costs["out"], pricing))
self.card_cache.set(tr("dashboard.card_cache"), _fmt_tokens(s["cache"]),
ut.format_cost(costs["cache"], pricing))
self.card_cost.set(tr("dashboard.card_cost"),
ut.format_cost(sum(costs.values()), pricing, digits=2),
est_note)
def set_budget(self, status, pricing: dict, convert) -> None:
"""``status`` là None khi người dùng chưa đặt ngân sách nào."""
if status is None:
self.budget_card.set(tr("usage.budget_title"), "—",
tr("usage.budget_no_budget"))
self.budget_card.budget_spin.setValue(0.0)
return
ccy = pricing.get("currency", "USD")
value = ("%s / %s" % (ut.format_cost(status["remaining_usd"], pricing, digits=2),
ut.format_cost(status["amount_usd"], pricing, digits=2)))
pct = int(round(status["pct_used"] * 100))
sub = (tr("usage.budget_over_warning") if status["over_85"]
else tr("usage.budget_used_pct", pct=pct))
self.budget_card.set(tr("usage.budget_title"), value, sub,
warn=status["over_85"])
# Ô nhập giữ giá trị ngân sách HIỆN TẠI, nhưng không giẫm lên thứ người
# dùng đang gõ dở.
if not self.budget_card.budget_spin.hasFocus():
self.budget_card.budget_spin.setValue(
round(convert(status["amount_usd"], "USD", ccy), 2))