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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f8e22f5f5b
commit
062ea4ba21
@@ -0,0 +1,93 @@
|
||||
"""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))
|
||||
Reference in New Issue
Block a user