Skip to content

Commit 1cea174

Browse files
committed
Release 0.1.2
1 parent 95ca002 commit 1cea174

9 files changed

Lines changed: 84 additions & 16 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codexauth-switch",
3-
"version": "0.1.1",
3+
"version": "0.1.2",
44
"private": true,
55
"description": "Local Windows desktop account switcher for Codex App.",
66
"author": "GboyCode",

src/main.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ let mainWindow;
6363
let widgetWindow;
6464
let tray;
6565
let isQuitting = false;
66+
let widgetAlwaysOnTop = false;
6667
let widgetManualSize = false;
6768
let widgetResizeSession = null;
6869
let widgetDockState = {
@@ -3609,7 +3610,7 @@ function createWidgetWindow() {
36093610
resizable: false,
36103611
maximizable: false,
36113612
minimizable: false,
3612-
alwaysOnTop: true,
3613+
alwaysOnTop: widgetAlwaysOnTop,
36133614
skipTaskbar: true,
36143615
transparent: true,
36153616
backgroundColor: "#00000000",
@@ -3622,7 +3623,7 @@ function createWidgetWindow() {
36223623
},
36233624
});
36243625
hardenWindowNavigation(widgetWindow);
3625-
widgetWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: false });
3626+
widgetWindow.setVisibleOnAllWorkspaces(widgetAlwaysOnTop, { visibleOnFullScreen: false });
36263627
widgetWindow.on("close", (event) => {
36273628
if (isQuitting) return;
36283629
event.preventDefault();
@@ -3636,6 +3637,14 @@ function createWidgetWindow() {
36363637
return widgetWindow;
36373638
}
36383639

3640+
function setWidgetTopmost(pinned) {
3641+
widgetAlwaysOnTop = pinned === true;
3642+
const win = createWidgetWindow();
3643+
win.setAlwaysOnTop(widgetAlwaysOnTop);
3644+
win.setVisibleOnAllWorkspaces(widgetAlwaysOnTop, { visibleOnFullScreen: false });
3645+
return { ok: true, pinned: widgetAlwaysOnTop };
3646+
}
3647+
36393648
function resizeWidgetForAccounts(accountCount) {
36403649
if (!widgetWindow || widgetWindow.isDestroyed()) return { ok: false };
36413650
if (widgetManualSize) return { ok: true, skipped: true };
@@ -4118,6 +4127,8 @@ function registerIpc() {
41184127
return { ok: true };
41194128
});
41204129
ipcMain.handle("window:resize-widget", (_event, accountCount) => resizeWidgetForAccounts(accountCount));
4130+
ipcMain.handle("window:get-widget-topmost", () => ({ ok: true, pinned: widgetAlwaysOnTop }));
4131+
ipcMain.handle("window:set-widget-topmost", (_event, pinned) => setWidgetTopmost(pinned));
41214132
ipcMain.handle("window:resize-widget-start", (_event, edge) => startWidgetResize(edge));
41224133
ipcMain.handle("window:resize-widget-update", () => updateWidgetResize());
41234134
ipcMain.handle("window:resize-widget-end", () => finishWidgetResize());

src/preload.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ contextBridge.exposeInMainWorld("codexAuth", {
1919
hideWidget: () => ipcRenderer.invoke("window:hide-widget"),
2020
toggleWidget: () => ipcRenderer.invoke("window:toggle-widget"),
2121
resizeWidget: (accountCount) => ipcRenderer.invoke("window:resize-widget", accountCount),
22+
getWidgetTopmost: () => ipcRenderer.invoke("window:get-widget-topmost"),
23+
setWidgetTopmost: (pinned) => ipcRenderer.invoke("window:set-widget-topmost", pinned),
2224
startWidgetResize: (edge) => ipcRenderer.invoke("window:resize-widget-start", edge),
2325
updateWidgetResize: () => ipcRenderer.invoke("window:resize-widget-update"),
2426
endWidgetResize: () => ipcRenderer.invoke("window:resize-widget-end"),

src/ui/app.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,9 @@ function formatDate(value) {
9797

9898
function compactNumber(value) {
9999
const number = Number(value || 0);
100-
if (number >= 1_000_000_000) return `${(number / 1_000_000_000).toFixed(1)}B`;
101-
if (number >= 1_000_000) return `${(number / 1_000_000).toFixed(1)}M`;
102-
if (number >= 1_000) return `${(number / 1_000).toFixed(1)}K`;
100+
const trim = (text) => text.replace(/\.0$/, "");
101+
if (number >= 100_000_000) return `${trim((number / 100_000_000).toFixed(1))}亿`;
102+
if (number >= 10_000) return `${trim((number / 10_000).toFixed(1))}`;
103103
return String(Math.round(number));
104104
}
105105

@@ -641,7 +641,7 @@ function renderDailyBars(days) {
641641
els.dailyBars.replaceChildren();
642642
const title = document.createElement("p");
643643
title.className = "usage-title";
644-
title.textContent = "每日 Tokens";
644+
title.textContent = "每日用量";
645645
els.dailyBars.append(title);
646646

647647
if (!days.length) {
@@ -693,7 +693,7 @@ function renderRecentSessions(sessions) {
693693
const main = document.createElement("span");
694694
main.textContent = session.title || "会话";
695695
const sub = document.createElement("small");
696-
sub.textContent = `${compactNumber(session.tokenUsage?.totalTokens)} tokens · ${session.model || "未知模型"} · ${formatDate(session.updatedAt)}`;
696+
sub.textContent = `${compactNumber(session.tokenUsage?.totalTokens)} Token · ${session.model || "未知模型"} · ${formatDate(session.updatedAt)}`;
697697
row.append(main, sub);
698698
list.append(row);
699699
});
@@ -723,7 +723,7 @@ function renderProjectStats(projects) {
723723
const name = document.createElement("strong");
724724
name.textContent = project.project;
725725
const sub = document.createElement("small");
726-
sub.textContent = `${compactNumber(project.tokenUsage?.totalTokens)} tokens · ${project.sessions} 次会话`;
726+
sub.textContent = `${compactNumber(project.tokenUsage?.totalTokens)} Token · ${project.sessions} 次会话`;
727727
row.append(name, sub);
728728
list.append(row);
729729
});

src/ui/index.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,15 +140,15 @@ <h2>当前账号概览</h2>
140140

141141
<div class="usage-grid">
142142
<article class="metric-card">
143-
<span>Total Tokens</span>
143+
<span>总用量</span>
144144
<strong id="totalTokens">--</strong>
145145
</article>
146146
<article class="metric-card">
147-
<span>Input Tokens</span>
147+
<span>输入用量</span>
148148
<strong id="inputTokens">--</strong>
149149
</article>
150150
<article class="metric-card">
151-
<span>Output Tokens</span>
151+
<span>输出用量</span>
152152
<strong id="outputTokens">--</strong>
153153
</article>
154154
<article class="metric-card">
@@ -157,7 +157,7 @@ <h2>当前账号概览</h2>
157157
</article>
158158
</div>
159159
<p class="usage-note">
160-
Tokens 是本机日志里的原始用量,不按高速版或模型倍率折算;5 小时和周额度以 Codex 写入的额度快照为准。
160+
Token 是本机日志里的原始用量,不按高速版或模型倍率折算;5 小时和周额度以 Codex 写入的额度快照为准。
161161
</p>
162162

163163
<div class="usage-detail">

src/ui/widget.css

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ button {
111111
gap: 10px;
112112
}
113113

114+
.brand > div {
115+
min-width: 0;
116+
}
117+
114118
.mark {
115119
flex: 0 0 auto;
116120
width: 30px;
@@ -128,7 +132,7 @@ button {
128132
.brand strong {
129133
display: block;
130134
overflow: hidden;
131-
max-width: 210px;
135+
max-width: 178px;
132136
font-weight: 560;
133137
text-overflow: ellipsis;
134138
white-space: nowrap;
@@ -168,6 +172,19 @@ button {
168172
transform: translateY(0);
169173
}
170174

175+
.head-actions button.active {
176+
color: #fff;
177+
border-color: rgba(79, 108, 255, 0.12);
178+
background: var(--gradient);
179+
box-shadow: 0 12px 24px -18px rgba(79, 108, 255, 0.86);
180+
}
181+
182+
.head-actions button.active:hover {
183+
color: #fff;
184+
border-color: rgba(79, 108, 255, 0.16);
185+
background: linear-gradient(135deg, #9185ff 0%, #617eff 46%, #3950f0 100%);
186+
}
187+
171188
.head-actions svg {
172189
width: 14px;
173190
height: 14px;

src/ui/widget.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@
2626
</div>
2727
</div>
2828
<div class="head-actions">
29+
<button id="pinBtn" title="固定在最前" aria-label="固定在最前" aria-pressed="false">
30+
<svg viewBox="0 0 24 24" aria-hidden="true">
31+
<path d="M12 17v4" />
32+
<path d="M8 4h8" />
33+
<path d="m9 4 1 7-3 3v3h10v-3l-3-3 1-7" />
34+
</svg>
35+
</button>
2936
<button id="settingsBtn" title="浮窗设置" aria-label="浮窗设置" aria-expanded="false">
3037
<svg viewBox="0 0 24 24" aria-hidden="true">
3138
<path d="M4 7h10" />

src/ui/widget.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const els = {
1818
refreshBtn: document.querySelector("#refreshBtn"),
1919
restartBtn: document.querySelector("#restartBtn"),
2020
mainBtn: document.querySelector("#mainBtn"),
21+
pinBtn: document.querySelector("#pinBtn"),
2122
settingsBtn: document.querySelector("#settingsBtn"),
2223
settingsPanel: document.querySelector("#settingsPanel"),
2324
opacityRange: document.querySelector("#opacityRange"),
@@ -231,6 +232,22 @@ function setSettingsOpen(open) {
231232
els.settingsBtn.setAttribute("aria-expanded", String(open));
232233
}
233234

235+
function applyPinState(pinned) {
236+
els.pinBtn.classList.toggle("active", pinned);
237+
els.pinBtn.setAttribute("aria-pressed", String(pinned));
238+
els.pinBtn.title = pinned ? "取消固定" : "固定在最前";
239+
els.pinBtn.setAttribute("aria-label", pinned ? "取消固定" : "固定在最前");
240+
}
241+
242+
async function loadPinState() {
243+
try {
244+
const result = await api.getWidgetTopmost?.();
245+
applyPinState(result?.pinned === true);
246+
} catch {
247+
applyPinState(false);
248+
}
249+
}
250+
234251
function resetRestartConfirm() {
235252
restartArmed = false;
236253
window.clearTimeout(restartConfirmTimer);
@@ -555,6 +572,19 @@ async function reauthAccount(accountId, button) {
555572

556573
function wireEvents() {
557574
els.refreshBtn.addEventListener("click", () => refresh(false));
575+
els.pinBtn.addEventListener("click", async (event) => {
576+
event.stopPropagation();
577+
const nextPinned = els.pinBtn.getAttribute("aria-pressed") !== "true";
578+
applyPinState(nextPinned);
579+
try {
580+
const result = await api.setWidgetTopmost?.(nextPinned);
581+
applyPinState(result?.pinned === true);
582+
showToast(result?.pinned ? "浮窗已固定在最前" : "浮窗已取消固定");
583+
} catch (error) {
584+
applyPinState(!nextPinned);
585+
showToast(error instanceof Error ? error.message : String(error));
586+
}
587+
});
558588
els.settingsBtn.addEventListener("click", (event) => {
559589
event.stopPropagation();
560590
setSettingsOpen(els.settingsPanel.hidden);
@@ -657,6 +687,7 @@ function wireResizeHandles() {
657687
wireEvents();
658688
wireResizeHandles();
659689
loadWidgetOpacity();
690+
loadPinState();
660691
refresh(true);
661692
window.setInterval(() => {
662693
refresh(true);

0 commit comments

Comments
 (0)