"""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))