Skip to content
Open
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
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ DeepSeek Monitor Windows is a Windows desktop adaptation inspired by JayHome137/

## 下载安装

普通用户可从 [GitHub Releases](https://github.com/Joyi-code/DeepSeekMonitorWindows/releases/latest) 下载并运行 `DeepSeekMonitorWindows_1.1.0_x64-setup.exe`。覆盖安装新版本前无需卸载旧版本。
普通用户可从 [GitHub Releases](https://github.com/Joyi-code/DeepSeekMonitorWindows/releases/latest) 下载并运行 `DeepSeekMonitorWindows_1.2.0_x64-setup.exe`。覆盖安装新版本前无需卸载旧版本。

安装包 SHA256:`B13EF28BB7E803D923E1A00BCE4A873B4EB7F2F592AFF690173C2E9291F1D13F`
安装包 SHA256:构建 1.2.0 后于 GitHub Release 安装包资产中更新(见发布页)

运行环境要求:

Expand Down Expand Up @@ -204,12 +204,17 @@ Rust 后端依赖:

完整发布记录见 [GitHub Releases](https://github.com/Joyi-code/DeepSeekMonitorWindows/releases)。

### v1.2.0

- 新增 `deepseek-v4-flash-vision-exp`(V4 Flash Vision)模型的用量监控:仪表盘新增第三个模型行(置于 V4 Flash 与 V4 Pro 之间),缓存命中趋势图合并该模型的命中/未命中/输出 Token,模型详情页支持查看其按日消耗。
- 版本号升至 1.2.0。

### v1.1.0

- 支持缓存命中、缓存未命中与输出 Token 的明细显示。
- 增加亮色 UI 皮肤,支持在主面板一键切换并记住用户选择。
- 设置页增加当前版本号显示。
- 当前 GitHub Release `v1.1.0` 已标记为 Latest,安装包为 `DeepSeekMonitorWindows_1.1.0_x64-setup.exe`。
- GitHub Release `v1.1.0` 曾标记为 Latest,安装包为 `DeepSeekMonitorWindows_1.1.0_x64-setup.exe`(历史保留)
- 安装包 SHA256:`B13EF28BB7E803D923E1A00BCE4A873B4EB7F2F592AFF690173C2E9291F1D13F`。
- 历史 Release `v1.0.1` 和旧安装包继续保留,便于回退和版本追溯。

Expand Down
11 changes: 8 additions & 3 deletions README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ Douyin: 像素与咖啡时光

## Download and Install

Users can download and run `DeepSeekMonitorWindows_1.1.0_x64-setup.exe` from [GitHub Releases](https://github.com/Joyi-code/DeepSeekMonitorWindows/releases/latest). You do not need to uninstall an older version before installing an update.
Users can download and run `DeepSeekMonitorWindows_1.2.0_x64-setup.exe` from [GitHub Releases](https://github.com/Joyi-code/DeepSeekMonitorWindows/releases/latest). You do not need to uninstall an older version before installing an update.

Installer SHA256: `B13EF28BB7E803D923E1A00BCE4A873B4EB7F2F592AFF690173C2E9291F1D13F`.
Installer SHA256: generated and published together with the 1.2.0 installer on GitHub Releases.

Runtime requirements:

Expand Down Expand Up @@ -202,12 +202,17 @@ Rust backend dependencies:

See [GitHub Releases](https://github.com/Joyi-code/DeepSeekMonitorWindows/releases) for the complete release history.

### v1.2.0

- Added usage monitoring for the `deepseek-v4-flash-vision-exp` (V4 Flash Vision) model: a new third model row on the dashboard (placed between V4 Flash and V4 Pro), the cache-hit trend chart now merges this model's hit/miss/output tokens, and the model detail page shows its daily usage.
- Bumped the version to 1.2.0.

### v1.1.0

- Added detailed cache hit, cache miss, and output token statistics.
- Added a light UI theme that can be toggled from the dashboard and persists across sessions.
- Added the current version number to the Settings page.
- GitHub Release `v1.1.0` is marked as Latest. The installer is `DeepSeekMonitorWindows_1.1.0_x64-setup.exe`.
- GitHub Release `v1.1.0` was marked as Latest. The installer is `DeepSeekMonitorWindows_1.1.0_x64-setup.exe` (kept for history).
- Installer SHA256: `B13EF28BB7E803D923E1A00BCE4A873B4EB7F2F592AFF690173C2E9291F1D13F`.
- The historical `v1.0.1` release and its installer remain available for rollback and version tracking.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "deepseek-monitor-windows",
"version": "1.1.0",
"version": "1.2.0",
"private": true,
"license": "MIT",
"type": "module",
Expand Down
6 changes: 5 additions & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "app"
version = "1.1.0"
version = "1.2.0"
description = "DeepSeek Monitor Windows desktop app"
authors = ["you"]
license = "MIT"
Expand All @@ -25,3 +25,7 @@ tauri = { version = "2.11.2", features = ["tray-icon"] }
tauri-plugin-log = "2"
tauri-plugin-single-instance = "2"
reqwest = { version = "0.12", features = ["json"] }

[features]
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
custom-protocol = ["tauri/custom-protocol"]
19 changes: 19 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,10 @@ pub fn run() {
pro_cache_hit: u64,
pro_cache_miss: u64,
pro_response: u64,
vision_tokens: u64,
vision_cache_hit: u64,
vision_cache_miss: u64,
vision_response: u64,
total_tokens: u64,
total_cost: f64,
}
Expand Down Expand Up @@ -817,6 +821,7 @@ pub fn run() {
let label = match model_usage.model.as_str() {
"deepseek-v4-flash" => Some(("flash", "V4 Flash")),
"deepseek-v4-pro" => Some(("pro", "V4 Pro")),
"deepseek-v4-flash-vision-exp" => Some(("flashvision", "V4 Flash Vision")),
_ => None,
};
if let Some((key, name)) = label {
Expand Down Expand Up @@ -853,6 +858,10 @@ pub fn run() {
let mut pro_hit = 0u64;
let mut pro_miss = 0u64;
let mut pro_resp = 0u64;
let mut vision = 0u64;
let mut vision_hit = 0u64;
let mut vision_miss = 0u64;
let mut vision_resp = 0u64;
let mut total = 0u64;
for model_usage in &day.data {
let (tokens, _, hit, miss, response) = token_breakdown(&model_usage.usage);
Expand All @@ -870,6 +879,12 @@ pub fn run() {
pro_miss += miss;
pro_resp += response;
}
"deepseek-v4-flash-vision-exp" => {
vision += tokens;
vision_hit += hit;
vision_miss += miss;
vision_resp += response;
}
_ => {}
}
}
Expand All @@ -883,6 +898,10 @@ pub fn run() {
pro_cache_hit: pro_hit,
pro_cache_miss: pro_miss,
pro_response: pro_resp,
vision_tokens: vision,
vision_cache_hit: vision_hit,
vision_cache_miss: vision_miss,
vision_response: vision_resp,
total_tokens: total,
total_cost: cost_by_date.get(&day.date).copied().unwrap_or(0.0),
});
Expand Down
10 changes: 6 additions & 4 deletions src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "DeepSeekMonitorWindows",
"version": "1.1.0",
"version": "1.2.0",
"identifier": "com.deepseek.monitor.windows",
"build": {
"frontendDist": "../dist",
Expand All @@ -15,9 +15,11 @@
{
"label": "main",
"title": "DeepSeek Monitor Windows",
"width": 356,
"height": 600,
"resizable": false,
"width": 384,
"height": 660,
"minWidth": 340,
"minHeight": 540,
"resizable": true,
"fullscreen": false,
"decorations": false,
"transparent": true,
Expand Down
85 changes: 52 additions & 33 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
CheckCircle2,
Clipboard,
CreditCard,
Eye,
Info,
KeyRound,
Power,
Expand All @@ -23,7 +24,7 @@ import {
import "./styles.css";

type ViewName = "dashboard" | "settings" | "detail";
type ModelName = "flash" | "pro";
type ModelName = "flash" | "pro" | "flashvision";
type AppConfig = {
apiKeyConfigured: boolean;
apiKeyPreview: string | null;
Expand Down Expand Up @@ -62,6 +63,10 @@ type UsageDay = {
proCacheHit: number;
proCacheMiss: number;
proResponse: number;
visionTokens: number;
visionCacheHit: number;
visionCacheMiss: number;
visionResponse: number;
totalTokens: number;
totalCost: number;
};
Expand Down Expand Up @@ -99,21 +104,25 @@ const recentUsageDays = (days: UsageDay[], count = 7): UsageDay[] => {
const today = new Date();
return Array.from({ length: count }, (_, index) => {
const date = dateKey(addDays(today, index - count + 1));
return (
source.get(date) ?? {
date,
flashTokens: 0,
flashCacheHit: 0,
flashCacheMiss: 0,
flashResponse: 0,
proTokens: 0,
proCacheHit: 0,
proCacheMiss: 0,
proResponse: 0,
totalTokens: 0,
totalCost: 0,
}
);
return (
source.get(date) ?? {
date,
flashTokens: 0,
flashCacheHit: 0,
flashCacheMiss: 0,
flashResponse: 0,
proTokens: 0,
proCacheHit: 0,
proCacheMiss: 0,
proResponse: 0,
visionTokens: 0,
visionCacheHit: 0,
visionCacheMiss: 0,
visionResponse: 0,
totalTokens: 0,
totalCost: 0,
}
);
});
};
const previousMonth = (date: Date) => {
Expand Down Expand Up @@ -313,7 +322,8 @@ function DashboardPanel({
};
const flash = usage?.models.find((item) => item.key === "flash") ?? null;
const pro = usage?.models.find((item) => item.key === "pro") ?? null;
const maxTokens = Math.max(flash?.totalTokens ?? 0, pro?.totalTokens ?? 0, 1);
const vision = usage?.models.find((item) => item.key === "flashvision") ?? null;
const maxTokens = Math.max(flash?.totalTokens ?? 0, pro?.totalTokens ?? 0, vision?.totalTokens ?? 0, 1);
const today = usage?.days.find((day) => day.date === todayStr()) ?? null;
const todayCost = usageState === "ok" && today ? today.totalCost : null;
const monthCost = usageState === "ok" && usage ? usage.monthCost : null;
Expand Down Expand Up @@ -364,6 +374,13 @@ function DashboardPanel({
state={usageState}
onClick={() => onDetail("flash")}
/>
<UsageRow
modelKey="flashvision"
data={vision}
maxTokens={maxTokens}
state={usageState}
onClick={() => onDetail("flashvision")}
/>
<UsageRow
modelKey="pro"
data={pro}
Expand Down Expand Up @@ -451,7 +468,8 @@ function UsageRow({
onClick: () => void;
}) {
const isFlash = modelKey === "flash";
const name = isFlash ? "V4 Flash" : "V4 Pro";
const isVision = modelKey === "flashvision";
const name = isFlash ? "V4 Flash" : isVision ? "V4 Flash Vision" : "V4 Pro";
const tokensText = data
? `${fmtInt(data.totalTokens)} Tokens`
: state === "loading"
Expand All @@ -467,19 +485,19 @@ function UsageRow({

return (
<button className="card usage-row" onClick={onClick}>
<div className={`model-badge ${isFlash ? "flash" : "pro"}`}>
{isFlash ? <Zap size={27} fill="currentColor" /> : <Brain size={25} />}
<div className={`model-badge ${isFlash ? "flash" : isVision ? "vision" : "pro"}`}>
{isFlash ? <Zap size={27} fill="currentColor" /> : isVision ? <Eye size={26} /> : <Brain size={25} />}
</div>
<div className="usage-main">
<h2>{name}</h2>
<div className="token-line">
<span>{tokensText}</span>
<div className="progress-track">
<i className={isFlash ? "flash-fill" : "pro-fill"} style={{ width }} />
<i className={isFlash ? "flash-fill" : isVision ? "vision-fill" : "pro-fill"} style={{ width }} />
</div>
</div>
{data && data.cacheHitTokens + data.cacheMissTokens > 0 && (
<span className={`cache-hit-rate ${isFlash ? "flash" : "pro"}`}>
<span className={`cache-hit-rate ${isFlash ? "flash" : isVision ? "vision" : "pro"}`}>
缓存命中{" "}
{((data.cacheHitTokens / (data.cacheHitTokens + data.cacheMissTokens)) * 100).toFixed(0)}%
</span>
Expand Down Expand Up @@ -507,9 +525,9 @@ function UsageChart({
const days = recentUsageDays(usage?.days ?? []);
const points = days.map((day) => {
// Flash 与 Pro 合并,不分模型
const hit = day.flashCacheHit + day.proCacheHit;
const miss = day.flashCacheMiss + day.proCacheMiss;
const response = day.flashResponse + day.proResponse;
const hit = day.flashCacheHit + day.proCacheHit + day.visionCacheHit;
const miss = day.flashCacheMiss + day.proCacheMiss + day.visionCacheMiss;
const response = day.flashResponse + day.proResponse + day.visionResponse;
return { date: day.date, hit, miss, response, total: hit + miss + response };
});
const maxVal = Math.max(...points.map((point) => point.total), 1);
Expand Down Expand Up @@ -638,7 +656,7 @@ function SettingsPanel({
const [usageStatus, setUsageStatus] = React.useState("");
const [usageSyncing, setUsageSyncing] = React.useState(false);
const [showManualPaste, setShowManualPaste] = React.useState(false);
const [appVersion, setAppVersion] = React.useState("1.1.0");
const [appVersion, setAppVersion] = React.useState("1.2.0");
const configPath = config?.configPath ?? "%APPDATA%\\DeepSeekMonitorWindows\\config.json";

React.useEffect(() => {
Expand All @@ -659,7 +677,7 @@ function SettingsPanel({
React.useEffect(() => {
void getVersion()
.then(setAppVersion)
.catch(() => setAppVersion("1.1.0"));
.catch(() => setAppVersion("1.2.0"));
}, []);

const refreshUsageAfterToken = React.useCallback(
Expand Down Expand Up @@ -1029,17 +1047,18 @@ function ModelDetailPanel({
onBack: () => void;
}) {
const isFlash = model === "flash";
const isVision = model === "flashvision";
const data = usage?.models.find((item) => item.key === model) ?? null;
const title = isFlash ? "V4 Flash" : "V4 Pro";
const tintClass = isFlash ? "flash" : "pro";
const title = isFlash ? "V4 Flash" : isVision ? "V4 Flash Vision" : "V4 Pro";
const tintClass = isFlash ? "flash" : isVision ? "vision" : "pro";
const cost = data ? fmtMoney(data.cost) : "—";
const totalText = data ? fmtTokensShort(data.totalTokens) : "—";

const days = recentUsageDays(usage?.days ?? []);
const points = days.map((day) => {
const hit = isFlash ? day.flashCacheHit : day.proCacheHit;
const miss = isFlash ? day.flashCacheMiss : day.proCacheMiss;
const response = isFlash ? day.flashResponse : day.proResponse;
const hit = isFlash ? day.flashCacheHit : isVision ? day.visionCacheHit : day.proCacheHit;
const miss = isFlash ? day.flashCacheMiss : isVision ? day.visionCacheMiss : day.proCacheMiss;
const response = isFlash ? day.flashResponse : isVision ? day.visionResponse : day.proResponse;
return { date: day.date, hit, miss, response, total: hit + miss + response };
});
const maxVal = Math.max(...points.map((point) => point.total), 1);
Expand All @@ -1056,7 +1075,7 @@ function ModelDetailPanel({
</button>
<article className="card detail-hero" data-tauri-drag-region>
<div className={`model-badge large ${tintClass}`}>
{isFlash ? <Zap size={34} fill="currentColor" /> : <Brain size={33} />}
{isFlash ? <Zap size={34} fill="currentColor" /> : isVision ? <Eye size={33} /> : <Brain size={33} />}
</div>
<div>
<h1>{title}</h1>
Expand Down
Loading