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
23 changes: 23 additions & 0 deletions apps/codex-plus-manager/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ export type RelayProfile = {
protocol: RelayProtocol;
relayMode: RelayMode;
officialMixApiKey: boolean;
hideOfficialUsageAlert: boolean;
testModel: string;
configContents: string;
authContents: string;
Expand Down Expand Up @@ -854,6 +855,7 @@ const defaultSettings: BackendSettings = {
protocol: "responses",
relayMode: "official",
officialMixApiKey: false,
hideOfficialUsageAlert: false,
testModel: "",
configContents: "",
authContents: "",
Expand Down Expand Up @@ -6228,6 +6230,21 @@ function RelayProfileEditor({
<p className="field-hint">{t("当前继承公共配置;修改后将为该供应商保存独立设置。")}</p>
) : null}
</Field>
{profile.relayMode === "official" ? (
<Field className="relay-field-official-usage-alert" label={t("官方登录")}>
<label className="inline-check">
<input
checked={profile.hideOfficialUsageAlert}
onChange={(event) => updateDraft({ hideOfficialUsageAlert: event.currentTarget.checked })}
type="checkbox"
/>
<span>{t("关闭官方低额度提示")}</span>
</label>
<p className="field-hint">
{t("关闭后仍可从 Codex 左下角账户菜单查看官方剩余额度。")}
</p>
</Field>
) : null}
<div className="relay-advanced-toggle">
<Button
aria-expanded={showAdvanced}
Expand Down Expand Up @@ -8409,6 +8426,7 @@ function normalizeSettings(settings: BackendSettings): BackendSettings {
protocol: "responses" as RelayProtocol,
relayMode: "official" as RelayMode,
officialMixApiKey: false,
hideOfficialUsageAlert: false,
testModel: "",
configContents: "",
authContents: "",
Expand Down Expand Up @@ -8489,6 +8507,7 @@ function normalizeRelayProfile(profile: RelayProfile, defaultContextSelection =
protocol: "responses",
relayMode: "aggregate",
officialMixApiKey: false,
hideOfficialUsageAlert: false,
testModel: profile.testModel || "",
configContents: "",
authContents: "",
Expand Down Expand Up @@ -8519,6 +8538,7 @@ function normalizeRelayProfile(profile: RelayProfile, defaultContextSelection =
protocol: profile.protocol === "chatCompletions" ? "chatCompletions" : "responses",
relayMode,
officialMixApiKey,
hideOfficialUsageAlert: profile.hideOfficialUsageAlert === true,
testModel: profile.testModel || "",
configContents: relayMode === "official" && !officialMixApiKey ? "" : profile.configContents || "",
authContents: relayMode === "official" && !officialMixApiKey ? buildOfficialRelayAuthJson(profile.authContents || "") : profile.authContents || "",
Expand Down Expand Up @@ -9253,6 +9273,7 @@ function createRelayProfile(settings: BackendSettings): RelayProfile {
protocol: "responses" as RelayProtocol,
relayMode: "official" as RelayMode,
officialMixApiKey: false,
hideOfficialUsageAlert: false,
testModel: "",
configContents: "",
authContents: "",
Expand Down Expand Up @@ -9290,6 +9311,7 @@ function createAggregateRelayProfile(settings: BackendSettings): RelayProfile {
protocol: "responses",
relayMode: "aggregate",
officialMixApiKey: false,
hideOfficialUsageAlert: false,
testModel: "",
configContents: "",
authContents: "",
Expand Down Expand Up @@ -9429,6 +9451,7 @@ function normalizeAggregateRelayProfile(profile: RelayProfile, settings: Backend
protocol: "responses",
relayMode: "aggregate",
officialMixApiKey: false,
hideOfficialUsageAlert: false,
configContents: "",
authContents: "",
sub2apiEnabled: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type RelayProfile = {
protocol: RelayProtocol;
relayMode: string;
officialMixApiKey: boolean;
hideOfficialUsageAlert: boolean;
testModel: string;
configContents: string;
authContents: string;
Expand Down Expand Up @@ -48,6 +49,7 @@ export function createPresetPatch(preset: ProviderPreset): PresetPatch {
modelList: preset.modelList?.join("\n") ?? "",
relayMode: preset.category === "official" ? "official" : "pureApi",
officialMixApiKey: false,
hideOfficialUsageAlert: false,
};
}

Expand Down
3 changes: 3 additions & 0 deletions apps/codex-plus-manager/src/i18n-en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,9 @@ export const EN_PLAIN: Record<string, string> = {
"深色": "Dark",
"混入 API": "Mixed-in API",
"混入 API KEY": "Mix in API KEY",
"关闭官方低额度提示": "Hide official low-usage alert",
"关闭后仍可从 Codex 左下角账户菜单查看官方剩余额度。":
"When hidden, you can still view the official quota from the account menu in the lower-left corner of Codex.",
"混入 API Key": "Mix in API Key",
"添加供应商": "Add provider",
"添加模型": "Add model",
Expand Down
1 change: 1 addition & 0 deletions apps/codex-plus-manager/src/model-windows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const _profileTypeCheck: RelayProfile = {
protocol: "responses",
relayMode: "official",
officialMixApiKey: false,
hideOfficialUsageAlert: false,
testModel: "",
configContents: "",
authContents: "",
Expand Down
113 changes: 113 additions & 0 deletions apps/codex-plus-manager/src/renderer-inject.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,82 @@ import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { readFile } from "node:fs/promises";

type FakeElementOptions = {
className?: string;
dismissLabel?: string;
hasProgress?: boolean;
styleDisplay?: string;
};

class FakeElement {
children: FakeElement[] = [];
dataset: Record<string, string> = {};
parentElement: FakeElement | null = null;
style: { display: string };
private readonly className: string;
private readonly dismissLabel: string;
private readonly hasProgress: boolean;

constructor(options: FakeElementOptions = {}) {
this.className = options.className ?? "";
this.dismissLabel = options.dismissLabel ?? "";
this.hasProgress = options.hasProgress ?? false;
this.style = { display: options.styleDisplay ?? "" };
}

appendChild(child: FakeElement) {
child.parentElement = this;
this.children.push(child);
}

getAttribute(name: string) {
return name === "aria-label" ? this.dismissLabel : null;
}

matches(selector: string) {
return selector === "div.w-full" && this.className.split(/\s+/).includes("w-full");
}

querySelector(selector: string) {
return selector === 'progress[max="100"]' && this.hasProgress ? new FakeElement() : null;
}

querySelectorAll(selector: string) {
return selector === "button" && this.dismissLabel ? [this] : [];
}
}

function usageAlertRuntime(renderer: string, cards: FakeElement[], managed: FakeElement[]) {
const start = renderer.indexOf(" function officialUsageAlertHidden(");
const end = renderer.indexOf("\n let zedRemoteStatusPromise", start);
assert.ok(start >= 0 && end > start);
const source = renderer.slice(start, end);
const selectors: string[] = [];
const document = {
querySelectorAll(selector: string) {
selectors.push(selector);
return selector === '[data-codex-plus-usage-alert-hidden="true"]'
? managed.filter((node) => node.dataset.codexPlusUsageAlertHidden === "true")
: cards;
},
};
const windowValue: Record<string, unknown> = {};
const create = new Function(
"window",
"document",
"HTMLElement",
`${source}\nreturn { officialUsageAlertHidden, refreshOfficialUsageAlertVisibility };`,
) as (
windowValue: Record<string, unknown>,
documentValue: typeof document,
elementType: typeof FakeElement,
) => {
officialUsageAlertHidden: () => boolean;
refreshOfficialUsageAlertVisibility: () => void;
};
return { runtime: create(windowValue, document, FakeElement), selectors, windowValue };
}

describe("renderer injection header compatibility", () => {
it("anchors the Codex++ menu to current and legacy application top bars only", async () => {
const renderer = await readFile(new URL("../../../assets/inject/renderer-inject.js", import.meta.url), "utf8");
Expand All @@ -20,4 +96,41 @@ describe("renderer injection header compatibility", () => {
assert.ok(renderer.includes("/^app:\\\/\\\/\\-\\//i.test(window.location.href)"));
assert.match(renderer, /codexPlusIsNodeTestHarness/);
});

it("hides only the official usage alert and restores it without changing upstream styles", async () => {
const renderer = await readFile(new URL("../../../assets/inject/renderer-inject.js", import.meta.url), "utf8");
const wrapper = new FakeElement({ className: "w-full", styleDisplay: "grid" });
const usageAlert = new FakeElement({ dismissLabel: "Dismiss usage alert", hasProgress: true });
const otherStatus = new FakeElement({ dismissLabel: "Dismiss sync status", hasProgress: true });
wrapper.appendChild(usageAlert);
const { runtime, selectors, windowValue } = usageAlertRuntime(renderer, [usageAlert, otherStatus], [wrapper]);

windowValue.__CODEX_PLUS_HIDE_OFFICIAL_USAGE_ALERT__ = true;
runtime.refreshOfficialUsageAlertVisibility();

assert.equal(wrapper.dataset.codexPlusUsageAlertHidden, "true");
assert.equal(wrapper.style.display, "grid");
assert.equal(otherStatus.dataset.codexPlusUsageAlertHidden, undefined);
assert.deepEqual(selectors, [
'[data-codex-plus-usage-alert-hidden="true"]',
'aside.app-shell-left-panel [role="status"][aria-live="polite"]',
]);

windowValue.__CODEX_PLUS_HIDE_OFFICIAL_USAGE_ALERT__ = false;
runtime.refreshOfficialUsageAlertVisibility();

assert.equal(wrapper.dataset.codexPlusUsageAlertHidden, undefined);
assert.equal(wrapper.style.display, "grid");
assert.equal(wrapper.children[0], usageAlert);
assert.equal(selectors.at(-1), '[data-codex-plus-usage-alert-hidden="true"]');
});

it("refreshes active-profile usage alert settings through the existing backend heartbeat", async () => {
const renderer = await readFile(new URL("../../../assets/inject/renderer-inject.js", import.meta.url), "utf8");

assert.match(renderer, /typeof nextStatus\.hideOfficialUsageAlert === "boolean"/);
assert.match(renderer, /window\.__CODEX_PLUS_HIDE_OFFICIAL_USAGE_ALERT__ = nextStatus\.hideOfficialUsageAlert/);
assert.match(renderer, /\[data-codex-plus-usage-alert-hidden="true"\] \{ display: none !important; \}/);
assert.doesNotMatch(renderer, /container\.style\.(?:setProperty|removeProperty)\("display"/);
});
});
41 changes: 41 additions & 0 deletions assets/inject/renderer-inject.js
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,7 @@
.codex-project-move-item-path { margin-top: 2px; color: #6b7280; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.codex-project-move-empty { padding: 18px 12px; color: #6b7280; text-align: center; }
.codex-project-move-hidden { display: none !important; }
[data-codex-plus-usage-alert-hidden="true"] { display: none !important; }
[data-codex-project-move-injected-list="true"] { display: flex; flex-direction: column; }
.codex-archive-delete-all {
border: 1px solid #ef4444;
Expand Down Expand Up @@ -3368,6 +3369,10 @@
const nextStatus = await withBackendTimeout(postJson("/backend/status", {}));
if (seq !== codexPlusBackendCheckSeq) return;
codexPlusBackendStatus = nextStatus;
if (nextStatus?.status === "ok" && typeof nextStatus.hideOfficialUsageAlert === "boolean") {
window.__CODEX_PLUS_HIDE_OFFICIAL_USAGE_ALERT__ = nextStatus.hideOfficialUsageAlert;
refreshOfficialUsageAlertVisibility();
}
if (nextStatus?.status !== "ok") {
sendCodexPlusDiagnostic("backend_check_failed", {
status: nextStatus?.status || "unknown",
Expand Down Expand Up @@ -9202,6 +9207,7 @@

function scanLightweight() {
installStyle();
refreshOfficialUsageAlertVisibility();
installCodexServiceTierDispatcherPatch();
installCodexPlusMenu();
localizeCodexMenus();
Expand All @@ -9216,6 +9222,40 @@
refreshCodexServiceTierControls();
}

function officialUsageAlertHidden() {
return window.__CODEX_PLUS_HIDE_OFFICIAL_USAGE_ALERT__ === true;
}

function officialUsageAlertCards(scope = document) {
const root = scope?.querySelectorAll ? scope : document;
return Array.from(root.querySelectorAll('aside.app-shell-left-panel [role="status"][aria-live="polite"]')).filter((card) => {
if (!(card instanceof HTMLElement)) return false;
const progress = card.querySelector('progress[max="100"]');
if (!progress) return false;
const dismissButton = Array.from(card.querySelectorAll("button")).find((button) =>
/dismiss usage alert|关闭使用量提醒/i.test(button.getAttribute("aria-label") || ""),
);
return !!dismissButton;
});
}

function officialUsageAlertContainer(card) {
const parent = card.parentElement;
return parent?.children.length === 1 && parent.matches("div.w-full") ? parent : card;
}

function refreshOfficialUsageAlertVisibility() {
const hidden = officialUsageAlertHidden();
document.querySelectorAll('[data-codex-plus-usage-alert-hidden="true"]').forEach((container) => {
delete container.dataset.codexPlusUsageAlertHidden;
});
if (!hidden) return;
officialUsageAlertCards().forEach((card) => {
const container = officialUsageAlertContainer(card);
container.dataset.codexPlusUsageAlertHidden = "true";
});
}

let zedRemoteStatusPromise = null;
const zedRemoteMissingHostMessage = "Cannot determine remote SSH host for this file";

Expand Down Expand Up @@ -9830,6 +9870,7 @@
function scanRelevantSelector() {
return [
selectors.sidebarThread,
'aside.app-shell-left-panel [role="status"][aria-live="polite"]',
'[data-app-action-sidebar-section-heading="Chats"]',
'[data-app-action-sidebar-section-heading="Projects"]',
'[data-codex-project-move-row="true"]',
Expand Down
10 changes: 9 additions & 1 deletion crates/codex-plus-core/src/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,11 @@ pub fn injection_script(helper_port: u16) -> String {
injection_script_with_settings(helper_port, &BackendSettings::default())
}

pub fn hide_official_usage_alert_config(settings: &BackendSettings) -> bool {
let profile = settings.active_relay_profile();
profile.relay_mode == crate::settings::RelayMode::Official && profile.hide_official_usage_alert
}

pub fn injection_script_with_settings(helper_port: u16, settings: &BackendSettings) -> String {
let helper_url = format!("http://127.0.0.1:{helper_port}");
let image_overlay = image_overlay_config(helper_port, settings);
Expand All @@ -398,8 +403,9 @@ pub fn injection_script_with_settings(helper_port: u16, settings: &BackendSettin
let paste_fix = paste_fix_enabled_config(settings);
let force_chinese_locale = force_chinese_locale_config(settings);
let fast_startup = fast_startup_config(settings);
let hide_official_usage_alert = hide_official_usage_alert_config(settings);
format!(
"window.__CODEX_SESSION_DELETE_HELPER__ = {};\nwindow.__CODEX_PLUS_VERSION__ = {};\nwindow.__CODEX_PLUS_BUILD__ = {};\nwindow.__CODEX_PLUS_IMAGE_OVERLAY__ = {};\nwindow.__CODEX_PLUS_PLUGIN_MARKETPLACES__ = {};\nwindow.__CODEX_PLUS_EXTERNAL_DREAM_SKIN_RUNTIME__ = true;\nwindow.__CODEX_PLUS_DREAM_SKIN_PLATFORM__ = {};\nwindow.__CODEX_PLUS_DREAM_SKIN_REVISION__ = {};\nwindow.__CODEX_PLUS_DREAM_SKIN_ART__ = {};\nwindow.__CODEX_PLUS_DREAM_SKIN_ART_SIGNATURE__ = {};\nwindow.__CODEX_PLUS_DREAM_SKIN_THEME__ = {};\nwindow.__CODEX_PLUS_PASTE_FIX__ = {};\nwindow.__CODEX_PLUS_FORCE_CHINESE_LOCALE__ = {};\nwindow.__CODEX_PLUS_FAST_STARTUP__ = {};\n{}\n{}\n{}",
"window.__CODEX_SESSION_DELETE_HELPER__ = {};\nwindow.__CODEX_PLUS_VERSION__ = {};\nwindow.__CODEX_PLUS_BUILD__ = {};\nwindow.__CODEX_PLUS_IMAGE_OVERLAY__ = {};\nwindow.__CODEX_PLUS_PLUGIN_MARKETPLACES__ = {};\nwindow.__CODEX_PLUS_EXTERNAL_DREAM_SKIN_RUNTIME__ = true;\nwindow.__CODEX_PLUS_DREAM_SKIN_PLATFORM__ = {};\nwindow.__CODEX_PLUS_DREAM_SKIN_REVISION__ = {};\nwindow.__CODEX_PLUS_DREAM_SKIN_ART__ = {};\nwindow.__CODEX_PLUS_DREAM_SKIN_ART_SIGNATURE__ = {};\nwindow.__CODEX_PLUS_DREAM_SKIN_THEME__ = {};\nwindow.__CODEX_PLUS_PASTE_FIX__ = {};\nwindow.__CODEX_PLUS_FORCE_CHINESE_LOCALE__ = {};\nwindow.__CODEX_PLUS_FAST_STARTUP__ = {};\nwindow.__CODEX_PLUS_HIDE_OFFICIAL_USAGE_ALERT__ = {};\n{}\n{}\n{}",
serde_json::to_string(&helper_url).expect("helper URL should serialize"),
serde_json::to_string(crate::version::VERSION).expect("version should serialize"),
serde_json::to_string(DIAGNOSTIC_BUILD_ID).expect("build id should serialize"),
Expand All @@ -416,6 +422,8 @@ pub fn injection_script_with_settings(helper_port: u16, settings: &BackendSettin
serde_json::to_string(&force_chinese_locale)
.expect("force Chinese locale config should serialize"),
serde_json::to_string(&fast_startup).expect("fast startup config should serialize"),
serde_json::to_string(&hide_official_usage_alert)
.expect("usage alert config should serialize"),
renderer_script(),
stepwise_script(),
dream_skin_target_runtime,
Expand Down
1 change: 1 addition & 0 deletions crates/codex-plus-core/src/ccs_import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ pub fn relay_profile_from_ccs(
protocol: provider.protocol,
relay_mode: RelayMode::PureApi,
official_mix_api_key: false,
hide_official_usage_alert: false,
test_model: String::new(),
config_contents: provider.config_contents.clone(),
auth_contents: provider.auth_contents.clone(),
Expand Down
3 changes: 3 additions & 0 deletions crates/codex-plus-core/src/launcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,9 @@ async fn handle_helper_connection(
"status": "ok",
"message": "后端已连接",
"version": crate::version::VERSION,
"hideOfficialUsageAlert": crate::assets::hide_official_usage_alert_config(
&crate::settings::SettingsStore::default().load().unwrap_or_default()
),
"transport": "http-helper"
}))?,
"application/json; charset=utf-8".to_string(),
Expand Down
1 change: 1 addition & 0 deletions crates/codex-plus-core/src/provider_import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ fn relay_profile_from_request(
protocol: relay_protocol(&request.wire_api),
relay_mode: relay_mode(&request.relay_mode),
official_mix_api_key: false,
hide_official_usage_alert: false,
test_model: String::new(),
config_contents: request.config_contents.clone(),
auth_contents: request.auth_contents.clone(),
Expand Down
Loading
Loading