Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ TokenMeter/

## Version and releases

Current version: `1.11.3`. See [GitHub Releases](https://github.com/zensoku142/TokenMeter/releases) for change notes and checksums.
Current version: `1.11.4`. See [GitHub Releases](https://github.com/zensoku142/TokenMeter/releases) for change notes and checksums.

## License

Expand Down
2 changes: 1 addition & 1 deletion README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ TokenMeter/

## バージョンと Release

現在のバージョン:`1.11.3`。変更履歴とチェックサムは [GitHub Releases](https://github.com/zensoku142/TokenMeter/releases) を参照してください。
現在のバージョン:`1.11.4`。変更履歴とチェックサムは [GitHub Releases](https://github.com/zensoku142/TokenMeter/releases) を参照してください。

## License

Expand Down
2 changes: 1 addition & 1 deletion README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ TokenMeter/

## 버전 및 Release

현재 버전: `1.11.3`. 변경 사항과 체크섬은 [GitHub Releases](https://github.com/zensoku142/TokenMeter/releases)에서 확인하세요.
현재 버전: `1.11.4`. 변경 사항과 체크섬은 [GitHub Releases](https://github.com/zensoku142/TokenMeter/releases)에서 확인하세요.

## License

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ TokenMeter/

## 版本与 Release

当前版本:`1.11.3`。更新记录及校验文件见 [GitHub Releases](https://github.com/zensoku142/TokenMeter/releases)。
当前版本:`1.11.4`。更新记录及校验文件见 [GitHub Releases](https://github.com/zensoku142/TokenMeter/releases)。

2.0 的 Provider 能力边界、分批计划和已执行基础见 [TokenMeter 2.0 路线图](docs/V2_ROADMAP.md)。

Expand Down
2 changes: 1 addition & 1 deletion README.zh-TW.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ TokenMeter/

## 版本與 Release

目前版本:`1.11.3`。更新說明與校驗檔請見 [GitHub Releases](https://github.com/zensoku142/TokenMeter/releases)。
目前版本:`1.11.4`。更新說明與校驗檔請見 [GitHub Releases](https://github.com/zensoku142/TokenMeter/releases)。

## License

Expand Down
2 changes: 1 addition & 1 deletion api/providers/codex.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ def _local_activity(self) -> tuple[tuple[tuple[str, int], ...], tuple[QuotaMetri
statistics = (
QuotaMetric("累计 Token 数", self._compact_tokens(total_tokens)),
QuotaMetric("峰值 Token 数", self._compact_tokens(peak_tokens)),
QuotaMetric("最长聊天时长", self._duration_text(longest_seconds)),
QuotaMetric("最长任务时长", self._duration_text(longest_seconds)),
QuotaMetric("当前连续天数", f"{current_streak} 天"),
QuotaMetric("最长连续天数", f"{longest_streak} 天"),
)
Expand Down
2 changes: 1 addition & 1 deletion app_identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

APP_DISPLAY_NAME = "TokenMeter"
APP_STORAGE_NAME = "TokenSpider"
APP_VERSION = "1.11.3"
APP_VERSION = "1.11.4"

# Keep the legacy storage and mutex identities so upgrades retain user data,
# credentials, and single-instance coordination across every public rename.
Expand Down
2 changes: 1 addition & 1 deletion installer/TokenMeter.iss
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#ifndef MyAppVersion
#define MyAppVersion "1.11.3"
#define MyAppVersion "1.11.4"
#endif
#define MyAppName "TokenMeter"
#define MyAppExeName "TokenMeter.exe"
Expand Down
29 changes: 29 additions & 0 deletions release-notes/v1.11.4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
更新类型:功能优化与 Bug 修复

### 优化

- Codex 订阅摘要不再展示 `GPT-5.3-Codex-Spark` 额度,保留主要周额度、订阅套餐和套餐到期信息。
- 套餐到期时间仅保留月日,减少面板中的冗余信息。
- 重新设计 Provider 下拉框、选中态和高分辨率箭头,使浅色、深色主题与面板风格保持一致。
- “最长聊天时长”调整为“最长任务时长”,统计口径保持不变。

### 修复

- 修复连续点击 Provider 下拉框时可能拖动主面板的问题。
- 修复下拉菜单展开过程中出现黑色边角或系统阴影的问题,保持圆角边界完整。

### 兼容

- 不改变 Codex、DeepSeek 和 Xiaomi MiMo 的登录、额度查询及数据存储流程。
- 不改变 Provider 返回结构、SQLite 历史数据、配置字段、凭据存储和自动更新协议。

### 验证

- 完整自动化测试通过。
- Python 编译检查与 `git diff --check` 通过。
- 已完成 Provider 下拉框交互、圆角边界和浅色/深色主题的本地界面验证。

### 安装与校验

- 从本 Release 下载 `TokenMeter-Setup-v1.11.4-x64.exe`。
- 可使用同页 `SHA256SUMS.txt` 校验安装包完整性;SHA256 不等同于独立发布签名。
2 changes: 1 addition & 1 deletion tests/test_providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def token_event(total, cached=0):
self.assertEqual([item.title for item in statistics], [
"累计 Token 数",
"峰值 Token 数",
"最长聊天时长",
"最长任务时长",
"当前连续天数",
"最长连续天数",
])
Expand Down
104 changes: 100 additions & 4 deletions tests/test_qt_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from datetime import date, datetime, timedelta, timezone
from decimal import Decimal
from pathlib import Path
from unittest.mock import patch
from unittest.mock import Mock, patch

os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
os.environ["APPDATA"] = str(Path.cwd() / ".test-appdata")
Expand All @@ -15,6 +15,7 @@
from PySide6.QtWidgets import (
QApplication,
QDialog,
QFrame,
QLabel,
QLineEdit,
QPushButton,
Expand Down Expand Up @@ -198,7 +199,7 @@ def test_panel_quick_switches_provider_and_renders_subscription_quota():
data.quota_statistics = [
QuotaMetric("累计 Token 数", "21.7亿"),
QuotaMetric("峰值 Token 数", "1.1亿"),
QuotaMetric("最长聊天时长", "52分 35秒"),
QuotaMetric("最长任务时长", "52分 35秒"),
QuotaMetric("当前连续天数", "0 天"),
QuotaMetric("最长连续天数", "27 天"),
]
Expand All @@ -225,9 +226,20 @@ def test_panel_quick_switches_provider_and_renders_subscription_quota():
assert panel.provider_quick_combo.view().objectName() == "headerProviderView"
assert panel.provider_quick_combo.view().minimumWidth() == 132
panel.show()
combo_animation_enabled = APP.isEffectEnabled(Qt.UIEffect.UI_AnimateCombo)
panel.provider_quick_combo.showPopup()
APP.processEvents()
assert panel.provider_quick_combo.view().window().size() == QSize(132, 134)
popup = panel.provider_quick_combo.view().window()
assert popup.size() == QSize(132, 140)
assert popup.minimumSize() == QSize(132, 140)
assert popup.maximumSize() == QSize(132, 140)
assert popup.windowFlags() & Qt.WindowType.FramelessWindowHint
assert popup.testAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
assert popup.frameShape() == QFrame.Shape.NoFrame
assert panel.provider_quick_combo.view().frameShape() == QFrame.Shape.NoFrame
assert not popup.mask().contains(QPoint(0, 0))
assert popup.mask().contains(popup.rect().center())
assert APP.isEffectEnabled(Qt.UIEffect.UI_AnimateCombo) == combo_animation_enabled
panel.provider_quick_combo.hidePopup()
assert panel.today_card.title_label.text() == "每周额度"
assert panel.today_card.value.text() == "已用 25%"
Expand Down Expand Up @@ -288,12 +300,96 @@ def test_subscription_expiry_replaces_codex_placeholder_without_email():
assert panel.balance_card.detail.isHidden()
assert panel.month_card.title_label.text() == "套餐到期"
assert panel.month_card.value.text() == local_until.strftime("%m-%d")
assert panel.month_card.detail.text() == local_until.strftime("%Y")
assert panel.month_card.detail.isHidden()
assert "a@example.com" not in panel.balance_card.value.toolTip()
assert "a@example.com" not in panel.month_card.value.toolTip()
panel.close()


def test_header_does_not_start_drag_from_provider_selector_events():
panel = MainPanel()
panel.show()
APP.processEvents()
pressed: list[QPoint] = []
dragged: list[QPoint] = []
released: list[QPoint] = []
panel.header.pressed.connect(pressed.append)
panel.header.dragged.connect(dragged.append)
panel.header.released.connect(released.append)

combo_point = panel.provider_quick_combo.mapTo(
panel.header, panel.provider_quick_combo.rect().center()
)

def mouse_event(event_point: QPoint, *, pressed_button: bool) -> Mock:
event = Mock()
event.button.return_value = (
Qt.MouseButton.LeftButton if pressed_button else Qt.MouseButton.NoButton
)
event.buttons.return_value = (
Qt.MouseButton.LeftButton if pressed_button else Qt.MouseButton.NoButton
)
event.position.return_value = QPointF(event_point)
event.globalPosition.return_value = QPointF(panel.header.mapToGlobal(event_point))
return event

panel.header.mousePressEvent(mouse_event(combo_point, pressed_button=True))
panel.header.mouseMoveEvent(
mouse_event(combo_point + QPoint(12, 0), pressed_button=True)
)
panel.header.mouseReleaseEvent(mouse_event(combo_point, pressed_button=True))

assert pressed == []
assert dragged == []
assert released == []

free_point = QPoint(430, panel.header.height() // 2)
panel.header.mousePressEvent(mouse_event(free_point, pressed_button=True))
panel.header.mouseMoveEvent(
mouse_event(free_point + QPoint(12, 0), pressed_button=True)
)
panel.header.mouseReleaseEvent(mouse_event(free_point, pressed_button=True))

assert len(pressed) == 1
assert len(dragged) == 1
assert len(released) == 1
panel.close()


def test_codex_spark_quota_is_hidden_without_hiding_other_subscription_details():
panel = MainPanel()
data = sample_data()
active_until = datetime(2026, 8, 11, 6, 17, tzinfo=timezone.utc)
data.quota_windows = [
QuotaWindow("codex-primary", "每周额度", 7),
QuotaWindow("codex-extra-0-primary", "GPT-5.3-Codex-Spark", 0),
]
data.account_plan = "prolite"
data.account_plan_active_until = active_until
data.per_provider = [
PerProviderData(
"codex",
"Codex",
quota_windows=list(data.quota_windows),
account_plan="prolite",
account_plan_active_until=active_until,
)
]

panel.update_data(data)

assert panel.today_card.title_label.text() == "每周额度"
assert panel.balance_card.title_label.text() == "订阅套餐"
assert panel.balance_card.value.text() == "prolite"
assert panel.month_card.title_label.text() == "套餐到期"
assert panel.month_card.value.text() == active_until.astimezone().strftime("%m-%d")
assert all(
"Spark" not in card.title_label.text()
for card in (panel.today_card, panel.balance_card, panel.month_card)
)
panel.close()


def test_reset_countdown_is_timezone_safe_and_readable():
now = datetime(2026, 8, 9, 8, 0, tzinfo=timezone.utc)
assert format_reset_countdown(now + timedelta(days=1, hours=2), now) == "1 天 2 小时后重置"
Expand Down
Loading
Loading