panel: as drawn in A and C, strictly — the tabs stay put above what scrolls; an agent's line is its icon, name, model in words, its effort as three bars (faint when default) and a chevron that turns down when open, all in fixed columns; open, it shows a Model field with its label and a segmented effort control under the name; Show N more sits left, as a quiet line; the footer is one hairline over even padding, with when usage was fetched on its left on the Usage tab; usage vendors are small caps headings with their accounts or plan on the right, each account a card with its reset times under it and a ring per window, balances two to a row (你没有严格按照设计稿来实现呀 / 底栏中上下间距竟然都不一致)

This commit is contained in:
yetone committed 2026-09-28 16:34:58 +08:00
1 parent 9687f3d297
commit e753cbafc5
4 files changed
+175 -87

No files matched your search

+65 -37
View File
@@ -527,33 +527,60 @@ body.window .field.solo { grid-column: auto; }
}
/* ---- the panel's tabs: agents, usage, profiles ---------------------------- */
.ptabs { display: flex; flex: none; gap: 2px; margin: 0 0 8px; padding: 2px; border-radius: 9px; background: color-mix(in srgb, var(--fg) 6%, transparent); }
/* outside the scrolling page, so they stay put under the header */
.ptabs { display: flex; flex: none; margin: 0 12px 10px; padding: 2px; border-radius: 9px; background: color-mix(in srgb, var(--fg) 6%, transparent); }
.ptabs[hidden] { display: none; }
.ptabs button { flex: 1; height: 24px; border-radius: 7px; font-size: 12px; color: var(--muted); transition: color .15s, background-color .15s; }
.ptabs button { flex: 1; display: flex; align-items: center; justify-content: center; gap: 3px; height: 26px; border-radius: 7px; font-size: 12px; color: var(--muted); transition: color .15s, background-color .15s; }
.ptabs button:hover:not(.on) { color: var(--fg-2); }
.ptabs button.on { color: var(--fg); font-weight: 550; background: var(--card); box-shadow: var(--card-edge); }
.ptabs button.on { color: var(--fg); font-weight: 550; background: var(--card); box-shadow: 0 1px 2px rgba(0, 0, 0, .08), var(--card-edge); }
.ptabs button[hidden] { display: none; }
.ptabs em { font-style: normal; font-weight: 400; color: var(--faint); font-variant-numeric: tabular-nums; }
body.panel[data-ptab="usage"] :is(#agents, .profiles),
body.panel[data-ptab="profiles"] :is(#agents, #panelQuota),
body.panel[data-ptab="agents"] :is(.profiles, #panelQuota) { display: none; }
/* ---- the panel's agents: what's set in words, controls once opened ------- */
body.panel .row.agent { flex-wrap: wrap; row-gap: 7px; cursor: default; }
body.panel .row.agent .fields { display: none; }
/* ---- the panel's agents: one quiet line each, opened in place ------------ */
/* icon · name · model · effort · chevron, the last three in columns of their
own so the bars and chevrons line up down the list */
body.panel .row.agent { display: grid; grid-template-columns: 20px minmax(0, 1fr) auto; grid-template-rows: 36px; align-items: center; column-gap: 10px; min-height: 36px; padding: 0 12px; cursor: default; }
body.panel .row.agent .ag-handle { width: 20px; height: 20px; margin: 0; }
body.panel .row.agent .ic { width: 18px; height: 18px; }
body.panel .row.agent .who { display: flex; align-items: center; gap: 6px; min-width: 0; }
body.panel .row.agent.open { background: var(--card-2); }
body.panel .row.agent.open .fields, body.panel #agents.extras .row.agent.open .fields { display: grid; order: 3; flex: 0 0 100%; width: 100%; padding: 0 0 3px 27px; }
body.panel .row.agent.open .field.main, body.panel .row.agent.open .field.solo { justify-self: stretch; }
.ag-sum { display: flex; align-items: center; gap: 7px; min-width: 0; max-width: 62%; margin-left: auto; height: 24px; padding: 0 2px 0 6px; border-radius: 6px; font-size: 12px; color: var(--muted); }
.ag-sum .v { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ag-sum .v.empty { color: var(--faint); }
.ag-sum .effort-ic { flex: none; width: 13px; height: 13px; color: var(--fg-2); }
.ag-sum .chev { flex: none; display: inline-flex; color: var(--faint); transform: rotate(-90deg); transition: transform .2s var(--ease); }
.row.agent:hover .ag-sum, .row.agent.open .ag-sum { color: var(--fg-2); }
.ag-sum { display: grid; grid-template-columns: auto 12px 10px; align-items: center; gap: 10px; min-width: 0; max-width: 100%; font-size: 12px; color: var(--muted); }
.ag-sum .v { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: right; }
.ag-sum .v.empty { color: var(--muted); }
.ag-sum .chev { display: inline-flex; width: 10px; color: var(--faint); transform: rotate(-90deg); transition: transform .2s var(--ease); }
.row.agent.open .ag-sum .chev { transform: none; }
body.panel .row.agent .ag-fix { top: 21px; }
.eff { display: inline-flex; align-items: flex-end; gap: 1.5px; height: 10px; }
.eff i { width: 2.5px; border-radius: 1px; background: var(--faint); opacity: .45; }
.eff i:nth-child(1) { height: 4px; }
.eff i:nth-child(2) { height: 7px; }
.eff i:nth-child(3) { height: 10px; }
.eff[data-l="1"] i:nth-child(-n+1), .eff[data-l="2"] i:nth-child(-n+2), .eff[data-l="3"] i { background: var(--fg-2); opacity: 1; }
.ag-open { display: none; }
.row.agent.open .ag-open { grid-column: 1 / -1; display: flex; flex-direction: column; gap: 8px; padding: 0 0 12px 30px; }
.ag-open .field { display: flex; justify-content: flex-start; width: 100%; height: 30px; gap: 8px; padding: 0 10px; border-radius: 8px; background: var(--card); font-size: 12.5px; box-shadow: inset 0 0 0 1px var(--pill-line); }
body.panel .ag-open .field:hover { background: var(--card); box-shadow: inset 0 0 0 1px var(--pill-line), var(--pill-shadow); }
body.panel .ag-open .field.open { box-shadow: inset 0 0 0 1px var(--accent); }
.ag-open .field .k { order: 0; width: 44px; font-size: 11px; color: var(--faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ag-open .field .v { order: 1; flex: 1 1 auto; font-weight: 400; text-align: left; }
.ag-open .field > .ic, .ag-open .field > .ic-stack { order: 2; flex: none; }
.row.agent .ag-open .field .ic { width: 14px; height: 14px; }
.ag-open .field .chev { order: 3; margin-left: 0; transform: none; }
.ag-seg { display: flex; padding: 2px; border-radius: 8px; background: color-mix(in srgb, var(--fg) 6%, transparent); }
.ag-seg button { flex: 1; min-width: 0; padding: 4px 0; border-radius: 6px; font-size: 11.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; transition: color .15s, background-color .15s; }
.ag-seg button:hover:not(.on) { color: var(--fg-2); }
.ag-seg button.on { background: var(--card); color: var(--fg); box-shadow: 0 1px 2px rgba(0, 0, 0, .08), var(--card-edge); }
.ag-open .extras-cell { display: flex; justify-content: flex-start; gap: 6px; }
.ag-open .extras-cell .field { width: 30px; padding: 0; justify-content: center; }
body.panel .row.agent .ag-fix { left: 21px; top: 22px; }
body.panel .agent-fold-cap { padding-left: 12px; }
/* "Show 9 more", the list's last line, where the names start */
body.panel .agent-more { justify-content: flex-start; gap: 6px; padding: 9px 14px; }
/* ---- the panel's profiles tab ---------------------------------------------- */
body.panel .profiles { flex-direction: column; align-items: stretch; gap: 8px; padding: 10px 0 2px; }
body.panel .profiles { flex-direction: column; align-items: stretch; gap: 8px; padding: 2px 0; }
body.panel .profiles > .label { display: none; }
body.panel .profiles .chips { flex: none; flex-wrap: wrap; }
body.panel .profiles .chips .hint { white-space: normal; line-height: 1.4; padding: 0 4px; color: var(--muted); }
@@ -561,32 +588,33 @@ body.panel .profiles .chips:has(.chip-input) .hint { display: none; }
body.panel .profiles #save { margin: 0; align-self: flex-start; }
/* ---- the panel's usage tab: accounts under their vendor, windows as rings -- */
.panel-quota { flex: none; display: flex; flex-direction: column; gap: 12px; padding: 0 0 4px; }
.pq-group { display: flex; flex-direction: column; gap: 6px; }
.pq-gh { display: flex; align-items: center; gap: 6px; padding: 0 2px; font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--faint); }
.pq-gh .ic { width: 13px; height: 13px; }
.pq-gc { font-weight: 500; letter-spacing: 0; }
.pq-card { display: flex; align-items: center; gap: 12px; min-height: 52px; padding: 8px 12px; border-radius: 10px; background: var(--card); box-shadow: var(--card-edge); }
.pq-who { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.pq-who > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pq-user { font-size: 12.5px; color: var(--fg); }
.pq-sub { font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums; }
.pq-sub.ends { color: var(--muted); }
.pq-sub.err { color: var(--muted); white-space: normal !important; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
.pq-rings { flex: none; display: flex; gap: 10px; }
.pq-ring { display: flex; flex-direction: column; align-items: center; gap: 3px; width: 44px; cursor: pointer; }
.panel-quota { flex: none; display: flex; flex-direction: column; gap: 14px; padding: 2px 2px 6px; }
.pq-group { display: flex; flex-direction: column; gap: 8px; }
.pq-gh { display: flex; align-items: center; gap: 8px; font-size: 10.5px; font-weight: 600; letter-spacing: .07em; text-transform: uppercase; color: var(--faint); }
.pq-gh .ic { width: 14px; height: 14px; flex: none; }
.pq-gn { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pq-gnote { flex: none; font-size: 12px; font-weight: 400; letter-spacing: 0; text-transform: none; font-variant-numeric: tabular-nums; }
.pq-gnote.ends { color: var(--amber); }
.pq-card { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 4px 12px; padding: 10px 12px; border-radius: 11px; background: var(--card); box-shadow: var(--card-edge); }
.pq-user { font-size: 12.5px; color: var(--fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pq-sub { font-size: 11px; color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-variant-numeric: tabular-nums; }
.pq-sub.err { color: var(--muted); white-space: normal; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
.pq-rings { grid-row: 1 / 3; grid-column: 2; display: flex; gap: 12px; }
.pq-ring { display: flex; flex-direction: column; align-items: center; gap: 3px; cursor: pointer; }
.pq-dial { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: conic-gradient(var(--fg-2) calc(var(--p) * 1%), color-mix(in srgb, var(--fg) 9%, transparent) 0); }
.pq-dial b { width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center; background: var(--card); font-size: 9.5px; font-weight: 600; color: var(--fg-2); font-variant-numeric: tabular-nums; }
.pq-dial b { width: 27px; height: 27px; border-radius: 50%; display: grid; place-items: center; background: var(--card); font-size: 9.5px; font-weight: 600; color: var(--fg-2); font-variant-numeric: tabular-nums; }
.pq-ring.full .pq-dial { background: conic-gradient(var(--amber) calc(var(--p) * 1%), color-mix(in srgb, var(--amber) 18%, transparent) 0); }
.pq-ring.full b { color: var(--fg); }
.pq-rn { max-width: 100%; font-size: 9.5px; line-height: 1.2; color: var(--faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pq-bals { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; }
.pq-card.bal { flex-direction: column; align-items: stretch; gap: 4px; min-height: 0; }
.pq-bn { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--muted); }
.pq-bn .ic { width: 13px; height: 13px; flex: none; }
.pq-rn { max-width: 44px; font-size: 9.5px; line-height: 1.2; color: var(--faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pq-bals { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.pq-card.bal { grid-template-columns: minmax(0, 1fr); }
.pq-amt { font-size: 15px; font-weight: 600; letter-spacing: -.01em; color: var(--fg); font-variant-numeric: tabular-nums; }
.pq-card .skeleton.sk-aq { width: 130px; height: 9px; border-radius: 4px; margin-right: auto; }
.pq-card .skeleton.sk-aq { width: 130px; height: 9px; border-radius: 4px; }
.pq-card .skeleton.sk-ring { width: 34px; height: 34px; border-radius: 50%; }
/* the panel's footer: a hairline over it, as much room above as below */
body.panel .foot { min-height: 0; padding: 6px 8px 6px 14px; border-top: 1px solid var(--line-2); }
.pq-age { font-size: 12px; color: var(--muted); white-space: nowrap; }
.pq-age:not([hidden]) + .status:empty { display: none; }
#view-agents.loading .panel-quota { visibility: hidden; }
/* ---- profiles ------------------------------------------------------------- */
+100 -45
View File
@@ -180,6 +180,7 @@ function renderAgents() {
list.append(e);
}
const { shown: used, folded } = arrangeAgents();
if (mode === "panel") $("#ptabN").textContent = state.agents.length || "";
const agentRow = (a, inFold) => {
const row = el("div", "row agent");
row.dataset.id = a.id;
@@ -224,37 +225,42 @@ function renderAgents() {
if (extras.childNodes.length) fields.append(extras);
// the panel shows what's set as words, and a row's controls only
// once it's opened: one row at a time, in place
let sum = null;
if (mode === "panel" && fields.childNodes.length) {
sum = el("button", "ag-sum");
sum.type = "button";
const main = sorted.find((f) => !extra(f) && !f.menu);
const effortOf = (f) => f.key === "effort" || f.label === "effort" || f.label === "thinking";
if (main && !effortOf(main)) {
const opt = optionFor(main, main.value);
sum.append(el("span", "v" + (main.value ? "" : " empty"), opt?.label || main.value || t("default")));
}
let sum = null, openBox = null;
const effortOf = (f) => f.key === "effort" || f.label === "effort" || f.label === "thinking";
if (mode === "panel") {
sum = el("span", "ag-sum");
const main = sorted.find((f) => !extra(f) && !f.menu && !effortOf(f));
const opt = main && optionFor(main, main.value);
sum.append(el("span", "v" + (main?.value ? "" : " empty"), main ? (opt?.label || main.value || t("default")) : ""));
// how much effort as three bars, in a column of its own down the list:
// none lit for the default, or for an agent that has no such setting
const ef = a.fields.find(effortOf);
if (ef) {
const e = effortIcon(ef);
e.title = t("{label}: {value}", { label: t(ef.label), value: effortName(optionFor(ef, ef.value) || { value: ef.value }) });
sum.append(e);
}
sum.append(effortBars(ef));
const c = el("span", "chev");
c.append(svg(CHEV, 11, 1.7));
c.append(svg(CHEV, 10, 1.6));
sum.append(c);
sum.setAttribute("aria-expanded", String(panelOpenAgent === a.id));
// opened: each setting on a line of its own, named, and the effort as
// its levels side by side
openBox = el("div", "ag-open");
for (const b of [...fields.querySelectorAll(":scope > .field")]) {
const f = sorted.find((x) => x.key === b.dataset.key) || (b.dataset.key === "tiers" ? tiers : null);
if (f && effortOf(f)) { openBox.append(effortSeg(a, f)); continue; }
if (f && !b.querySelector(":scope > .k")) b.prepend(el("span", "k", t(f.label)));
openBox.append(b);
}
if (extras.childNodes.length) openBox.append(extras);
row.classList.toggle("open", panelOpenAgent === a.id);
row.setAttribute("aria-expanded", String(panelOpenAgent === a.id));
row.onclick = (ev) => {
if (ev.target.closest(".fields, .ag-handle, .ag-fix, .ag-show")) return;
if (ev.target.closest(".ag-open, .ag-handle, .ag-fix, .ag-show")) return;
const open = panelOpenAgent !== a.id;
panelOpenAgent = open ? a.id : null;
for (const r of $("#agents").querySelectorAll(".row.agent.open")) {
r.classList.remove("open");
r.querySelector(".ag-sum")?.setAttribute("aria-expanded", "false");
r.setAttribute("aria-expanded", "false");
}
row.classList.toggle("open", open);
sum.setAttribute("aria-expanded", String(open));
row.setAttribute("aria-expanded", String(open));
fit();
};
}
@@ -277,8 +283,8 @@ function renderAgents() {
who.append(driftFix(a));
}
row.append(agentHandle(a, row, inFold), who);
if (sum) row.append(sum);
row.append(fields);
if (sum) row.append(sum, openBox);
else row.append(fields);
return row;
};
// the extras column is there for every row once any agent has one, so the
@@ -955,6 +961,43 @@ function effortIcon(f) {
return e;
}
// effortBars: the panel's effort at a glance, three bars lit up to it.
function effortBars(f) {
const levels = (f?.options || []).filter((o) => o.value && o.value !== "off");
const at = f ? levels.findIndex((o) => o.value === f.value) : -1;
const e = el("span", "eff");
e.dataset.l = at < 0 ? 0 : Math.max(1, Math.round(((at + 1) / levels.length) * 3));
e.append(el("i"), el("i"), el("i"));
if (f) e.title = t("{label}: {value}", { label: t(f.label), value: effortName(optionFor(f, f.value) || { value: f.value }) });
return e;
}
// effortSeg: an opened panel row's effort, every level side by side.
function effortSeg(a, f) {
const seg = el("div", "ag-seg");
seg.setAttribute("role", "radiogroup");
seg.setAttribute("aria-label", t(f.label));
for (const o of f.options) {
const b = el("button", o.value === f.value ? "on" : "", effortName(o));
b.type = "button";
b.setAttribute("role", "radio");
b.setAttribute("aria-checked", String(o.value === f.value));
if (o.note) b.title = o.note;
b.onclick = async () => {
if (o.value === f.value) return;
try {
state = await api("set", { agent: a.id, field: f.key, value: o.value });
renderAgents();
status(`${a.name} ${t(f.label)} → ${effortName(o)}`, "ok");
} catch (e) {
status(e.message, "err");
}
};
seg.append(b);
}
return seg;
}
function renderEffortPicker() {
const range = $("#effortRange");
const options = pick.options;
@@ -3889,6 +3932,7 @@ const PERIODS = [["today", "Today"], ["7d", "7 days"], ["30d", "30 days"], ["all
// never come without a proxy), so they load on their own and the local log
// never waits for them. They don't depend on the period either.
let quotas = null;
let quotasAt = 0; // when they came in
async function loadUsage() {
renderUsageTab();
if (usageTab === "sessions") return loadSessions();
@@ -3902,7 +3946,7 @@ let quotasLoading = null;
function loadQuotas() {
if (quotasLoading) return quotasLoading;
quotasLoading = api("usage/quotas")
.then((q) => { quotas = q || []; }, () => { quotas = quotas || []; })
.then((q) => { quotas = q || []; quotasAt = Date.now(); }, () => { quotas = quotas || []; })
.finally(() => { quotasLoading = null; renderQuotas(); });
return quotasLoading;
}
@@ -4037,6 +4081,7 @@ function setPanelTab(tab) {
b.classList.toggle("on", b.dataset.ptab === tab);
b.setAttribute("aria-selected", String(b.dataset.ptab === tab));
}
panelAge();
fit();
}
if (mode === "panel") {
@@ -4082,53 +4127,54 @@ function renderPanelQuota() {
for (const [name, qs] of groups) {
const g = el("div", "pq-group");
const head = el("div", "pq-gh");
head.append(icon(qs[0].icon), el("span", "pq-gn", name));
if (qs.length > 1) head.append(el("span", "pq-gc", String(qs.length)));
// how many accounts, or the one account's plan and until when
const note = qs.length > 1 ? t("{n} accounts", { n: qs.length }) : [qs[0].plan, qs[0].until ? planTerm(qs[0]) : ""].filter(Boolean).join(" · ");
head.append(icon(qs[0].icon), el("span", "pq-gn", name), el("span", "pq-gnote" + (qs.length === 1 && qs[0].renew === "off" ? " ends" : ""), note));
g.append(head);
for (const q of qs) g.append(panelQuotaCard(q));
box.append(g);
}
if (bals.length) {
const g = el("div", "pq-group");
g.append(el("div", "pq-gh", t("Balance")));
const head = el("div", "pq-gh");
head.append(el("span", "pq-gn", t("Balances")));
g.append(head);
const grid = el("div", "pq-bals");
for (const q of bals) {
const card = el("div", "pq-card bal");
const who = el("span", "pq-who");
const name = el("span", "pq-bn");
name.append(icon(q.icon), el("span", "", q.name));
who.append(name);
if (q.user && q.user !== q.name) who.append(el("span", "pq-sub", q.user));
card.title = [q.name, q.user].filter(Boolean).join(" · ");
card.append(who, el("b", "pq-amt", q.balance));
card.append(el("span", "pq-sub", q.name), el("b", "pq-amt", q.balance));
grid.append(card);
}
g.append(grid);
box.append(g);
}
panelAge();
fit();
}
// shortWindow: "5 hours" as 5h, "7 days" as 7d; any other name as it is.
function shortWindow(name) {
const m = /^(\d+)\s*(minute|hour|day|week|month)s?$/i.exec(name || "");
return m ? m[1] + { minute: "m", hour: "h", day: "d", week: "w", month: "mo" }[m[2].toLowerCase()] : t(name);
}
function panelQuotaCard(q) {
const card = el("div", "pq-card");
const who = el("span", "pq-who");
who.append(el("span", "pq-user", q.user || q.name));
card.title = [q.name, q.user, q.plan, planTerm(q)].filter(Boolean).join(" · ");
const sub = [];
if (q.plan) sub.push(q.plan);
if (q.until) sub.push(planTerm(q));
card.append(who);
card.append(el("span", "pq-user", q.user || q.name));
card.title = [q.name, q.user, q.plan, q.until ? planTerm(q) : ""].filter(Boolean).join(" · ");
if (q.error) {
card.classList.add("err");
who.append(el("span", "pq-sub err", quotaError(q.error)));
card.append(el("span", "pq-sub err", quotaError(q.error)));
card.title += "\n" + q.error;
if (sub.length) who.append(el("span", "pq-sub", sub.join(" · ")));
return card;
}
const ws = q.windows.slice(0, 3);
// when each window starts again, the ones that have begun
const resets = ws.filter((w) => w.resetsAt && w.used > 0).map((w) => (ws.length > 1 ? t(w.name) + " " : "") + "↻ " + resetClock(new Date(w.resetsAt)));
if (sub.length || resets.length) who.append(el("span", "pq-sub" + (q.renew === "off" ? " ends" : ""), [...sub, ...resets].join(" · ")));
// when the windows begun start again: the first bare, the others by name
const begun = ws.filter((w) => w.resetsAt && w.used > 0);
card.append(el("span", "pq-sub", begun.length
? begun.map((w, i) => (i ? shortWindow(w.name) + " " : "↻ ") + resetClock(new Date(w.resetsAt))).join(" · ")
: t("Not used yet")));
const rings = el("span", "pq-rings");
for (const w of ws) {
const used = Math.max(0, Math.min(100, w.used));
@@ -4136,7 +4182,7 @@ function panelQuotaCard(q) {
const dial = el("span", "pq-dial");
dial.style.setProperty("--p", quotaFill(w));
dial.append(el("b", "", quotaFill(w) + "%"));
r.append(dial, el("span", "pq-rn", t(w.name)));
r.append(dial, el("span", "pq-rn", shortWindow(w.name)));
r.title = t(w.name) + " · " + quotaText(w) + (w.resetsAt ? "\n" + t("Resets {when}", { when: new Date(w.resetsAt).toLocaleString() }) + " · " + untilText(new Date(w.resetsAt)) : "")
+ "\n" + t(quotaLeft ? "Show how much of each window is used" : "Show how much of each window is left");
// used or left turns here too, as on the Usage page (#124)
@@ -4147,6 +4193,15 @@ function panelQuotaCard(q) {
return card;
}
// panelAge: on the Usage tab, the footer says how old what it shows is.
function panelAge() {
const a = $("#pqAge");
if (!a) return;
a.hidden = mode !== "panel" || panelTab !== "usage" || !quotasAt;
if (!a.hidden) a.textContent = t("Updated {when}", { when: ago(quotasAt) });
}
if (mode === "panel") setInterval(panelAge, 30000);
// quotaFit puts every window's count under its name once one's doesn't fit
// beside it, so windows side by side read alike rather than one count up
// by its name and the next a line below (#90)
+3
View File
@@ -381,6 +381,9 @@ const I18N = {
"Balance field": "余额字段",
"Where the amount is in the reply, e.g. data.balance; it can be a sum with + - * / and brackets, e.g. data.total / 500000 or (1 - credits.used / 70) %; \"$\" in front adds the sign, \"%\" after it shows a percent; several, each with a label, go apart by \";\", e.g. 5h: a.used / a.cap %; $credits.left": "金额在返回 JSON 中的位置,例如 data.balance;可以用 + - * / 和括号算,例如 data.total / 500000 或 (1 - credits.used / 70) %;前面加 \"$\" 显示币种符号,后面加 \"%\" 显示为百分比;多个金额用 \";\" 隔开,各自可加标签,例如 5h: a.used / a.cap %; $credits.left",
"Balance": "余额",
"Balances": "余额",
"Updated {when}": "{when}更新",
"Not used yet": "还没用过",
"Allowances": "额度",
"What is left on the account: the vendor tells only this, so Used / Left leaves it as it is": "账户里剩下的金额:供应商只提供这个数,所以「已用 / 剩余」不会改变它",
"Headers": "请求头",
+7 -5
View File
@@ -43,12 +43,13 @@
</div>
</header>
<nav class="ptabs" id="ptabs" hidden>
<button type="button" data-ptab="agents"><span data-t>Agents</span><em id="ptabN"></em></button>
<button type="button" data-ptab="usage"><span data-t>Usage</span></button>
<button type="button" data-ptab="profiles"><span data-t>Profiles</span></button>
</nav>
<main class="view" id="view-agents">
<nav class="ptabs" id="ptabs" hidden>
<button type="button" data-ptab="agents" data-t>Agents</button>
<button type="button" data-ptab="usage" data-t>Usage</button>
<button type="button" data-ptab="profiles" data-t>Profiles</button>
</nav>
<div class="list" id="agents"></div>
<section class="profiles">
<span class="label" data-t>Profiles</span>
@@ -186,6 +187,7 @@
</main>
<footer class="foot">
<span id="pqAge" class="pq-age" hidden></span>
<span id="status" class="status"></span>
<span class="grow"></span>
<button class="text" id="openMain" data-t>Open magpie</button>