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 gui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import ClaudeCode from "./pages/ClaudeCode";
import Startup from "./pages/Startup";
import ErrorBoundary from "./components/ErrorBoundary";
import { IconGrid, IconServer, IconBoxes, IconBot, IconList, IconActivity, IconHardDrive, IconKey, IconGithub, IconMenu, IconSun, IconMoon, IconMonitor, IconGlobe, IconPower, IconSparkle, IconX } from "./icons";
import { useI18n, useT, LOCALES, type Locale, type TKey } from "./i18n";
import { useI18n, useT, LOCALES, type Locale, type TKey } from "./i18n/shared";
import { Select, Switch } from "./ui";
import { installApiAuthFetch } from "./api";
import { readJsonIfOk } from "./fetch-json";
Expand Down
2 changes: 1 addition & 1 deletion gui/src/components/AddCodexAccountModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useReducer, useRef } from "react";
import { useT } from "../i18n";
import { useT } from "../i18n/shared";
import {
addCodexAccountUiReducer,
initialAddCodexAccountUiState,
Expand Down
2 changes: 1 addition & 1 deletion gui/src/components/AddProviderModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useReducer, useRef } from "react";
import { IconX } from "../icons";
import { useT } from "../i18n";
import { useT } from "../i18n/shared";
import { useKeyedClientResource } from "../client-resource";
import {
buildProviderPostBody,
Expand Down
2 changes: 1 addition & 1 deletion gui/src/components/CodexAccountPool.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useState } from "react";
import { useT } from "../i18n";
import { useT } from "../i18n/shared";
import { IconPlus } from "../icons";
import { Notice, EmptyState } from "../ui";
import AddCodexAccountModal from "./AddCodexAccountModal";
Expand Down
2 changes: 1 addition & 1 deletion gui/src/components/CodexAutoSwitchSetting.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useRef } from "react";
import { useT } from "../i18n";
import { useT } from "../i18n/shared";

export type AutoSwitchFeedback = { tone: "ok" | "err"; message: string } | null;

Expand Down
45 changes: 36 additions & 9 deletions gui/src/components/MemoryObservabilityCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,35 @@ interface SystemMemory {
* memory diagnostic must not introduce. The number itself goes through the active locale so it
* matches every other figure on this dashboard.
*/
const byteNumberFormats = new Map<string, Intl.NumberFormat>();
function byteNumberFormat(locale: Locale, fractionDigits: number): Intl.NumberFormat {
const key = `${locale}:${fractionDigits}`;
let fmt = byteNumberFormats.get(key);
if (!fmt) {
fmt = new Intl.NumberFormat(locale, {
minimumFractionDigits: fractionDigits,
maximumFractionDigits: fractionDigits,
});
byteNumberFormats.set(key, fmt);
}
return fmt;
}
const plainNumberFormats = new Map<string, Intl.NumberFormat>();
function plainNumberFormat(locale: Locale): Intl.NumberFormat {
let fmt = plainNumberFormats.get(locale);
if (!fmt) {
fmt = new Intl.NumberFormat(locale);
plainNumberFormats.set(locale, fmt);
}
return fmt;
}

function formatBytes(bytes: number, locale: Locale): string {
if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
const units = ["B", "KiB", "MiB", "GiB", "TiB"];
const exp = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
const value = bytes / 1024 ** exp;
const formatted = new Intl.NumberFormat(locale, {
minimumFractionDigits: exp === 0 ? 0 : 1,
maximumFractionDigits: exp === 0 ? 0 : 1,
}).format(value);
const formatted = byteNumberFormat(locale, exp === 0 ? 0 : 1).format(value);
return `${formatted} ${units[exp]}`;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

Expand Down Expand Up @@ -95,12 +115,19 @@ export default function MemoryObservabilityCard({ apiBase }: { apiBase: string }
if (inFlight) return;
inFlight = true;
// Bound each poll so a hung request cannot pin inFlight forever and
// starve the unavailable fallback.
// starve the unavailable fallback. Prefer AbortSignal.timeout; fall back
// to a manual timer when the browser lacks AbortSignal.any/timeout.
const controller = new AbortController();
activeController = controller;
const timeout = setTimeout(() => controller.abort(), 10_000);
let timeoutId: ReturnType<typeof setTimeout> | undefined;
const signal = typeof AbortSignal !== "undefined" && "any" in AbortSignal && "timeout" in AbortSignal
? AbortSignal.any([controller.signal, AbortSignal.timeout(10_000)])
: (() => {
timeoutId = setTimeout(() => controller.abort(), 10_000);
return controller.signal;
})();
try {
const res = await fetch(`${apiBase}/api/system/memory`, { signal: controller.signal });
const res = await fetch(`${apiBase}/api/system/memory`, { signal });
if (!res.ok) throw new Error("memory unavailable");
const json = await res.json() as SystemMemory;
if (!cancelled) {
Expand All @@ -111,7 +138,7 @@ export default function MemoryObservabilityCard({ apiBase }: { apiBase: string }
// Old servers (pre-#314) 404 this route; degrade to a quiet unavailable note.
if (!cancelled) setUnavailable(true);
} finally {
clearTimeout(timeout);
if (timeoutId !== undefined) clearTimeout(timeoutId);
if (activeController === controller) activeController = null;
inFlight = false;
}
Expand Down Expand Up @@ -166,7 +193,7 @@ export default function MemoryObservabilityCard({ apiBase }: { apiBase: string }
<div className="muted text-label" style={{ margin: "14px 0 6px" }}>{t("dash.mem.store")}</div>
<div className="muted text-control" style={{ marginBottom: 10 }}>{t("dash.mem.storeHint")}</div>
<div className="stat-row">
<Stat label={t("dash.mem.storeEntries")} value={responseState ? new Intl.NumberFormat(locale).format(responseState.count) : "—"} />
<Stat label={t("dash.mem.storeEntries")} value={responseState ? plainNumberFormat(locale).format(responseState.count) : "—"} />
<Stat label={t("dash.mem.storeTotal")} value={responseState ? formatBytes(responseState.totalBytes, locale) : "—"} />
<Stat label={t("dash.mem.storeLargest")} value={responseState ? formatBytes(responseState.largestBytes, locale) : "—"} />
<Stat
Expand Down
22 changes: 2 additions & 20 deletions gui/src/components/OAuthTosWarningModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* are restricted (or risky) when used outside the official client.
*/
import { useCallback, useEffect, useId, useRef, useState } from "react";
import { useT } from "../i18n";
import { useT } from "../i18n/shared";
import { IconAlert } from "../icons";
import {
oauthTosRisk,
Expand Down Expand Up @@ -68,26 +68,8 @@ export default function OAuthTosWarningModal({
aria-describedby={bodyId}
className="modal-overlay"
onCancel={handleCancel}
onClick={onCancel}
onKeyDown={e => {
if (e.key !== "Tab") return;
const dialog = dialogRef.current;
if (!dialog) return;
const focusable = dialog.querySelectorAll<HTMLElement>(
"input:not([disabled]), button:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex='-1'])",
);
if (focusable.length === 0) return;
const first = focusable.item(0);
const last = focusable.item(focusable.length - 1);
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}}
>
<button type="button" className="modal-backdrop-dismiss" aria-label={t("common.close")} tabIndex={-1} onClick={onCancel} />
<div
className="modal-card"
onClick={e => e.stopPropagation()}
Expand Down
12 changes: 6 additions & 6 deletions gui/src/components/QuotaBars.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Locale, TFn } from "../i18n";
import { useI18n } from "../i18n";
import type { Locale, TFn } from "../i18n/shared";
import { useI18n } from "../i18n/shared";
import { IconAlert } from "../icons";
import { type AccountQuota, normalizeQuotaForPlan } from "../codex-quota-utils";

Expand Down Expand Up @@ -148,17 +148,17 @@ export default function QuotaBars({ quota, plan, threshold, t, className, layout
if (layout === "stacked") {
return (
<div className={`quota-stacked${className ? ` ${className}` : ""}`}>
{rows.map((row, index) => (
<StackedQuotaRow key={`${row.limitLabel}-${index}`} row={row} threshold={threshold} t={t} locale={locale} />
{rows.map(row => (
<StackedQuotaRow key={row.limitLabel} row={row} threshold={threshold} t={t} locale={locale} />
))}
</div>
);
}
return (
<div className={`quota-compact${className ? ` ${className}` : ""}`}>
{rows.map((row, index) => (
{rows.map(row => (
<QuotaRow
key={`${row.label}-${index}`}
key={row.label}
label={row.label}
percent={row.percent}
resetAt={row.resetAt}
Expand Down
2 changes: 1 addition & 1 deletion gui/src/components/add-codex-account-pick-step.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IconGlobe } from "../icons";
import { useT } from "../i18n";
import { useT } from "../i18n/shared";

export function AddCodexAccountPickStep({
id,
Expand Down
2 changes: 1 addition & 1 deletion gui/src/components/add-codex-account-waiting-step.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IconLink } from "../icons";
import { useT } from "../i18n";
import { useT } from "../i18n/shared";
import type { StatusTone } from "./add-codex-account-reducer";

export function AddCodexAccountWaitingStep({
Expand Down
2 changes: 1 addition & 1 deletion gui/src/components/add-provider-form-pane.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IconExternal, IconKey } from "../icons";
import { useT } from "../i18n";
import { useT } from "../i18n/shared";
import type { CatalogPreset } from "./provider-catalog/provider-presets";
import type { ProviderPayloadForm } from "../provider-payload";
import { AddProviderField } from "./add-provider-modal-field";
Expand Down
2 changes: 1 addition & 1 deletion gui/src/components/add-provider-oauth-pane.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IconLock } from "../icons";
import { useT } from "../i18n";
import { useT } from "../i18n/shared";
import type { CatalogPreset } from "./provider-catalog/provider-presets";

export function AddProviderOAuthPane({
Expand Down
2 changes: 1 addition & 1 deletion gui/src/components/codex-account-pool-cards.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useT } from "../i18n";
import { useT } from "../i18n/shared";
import { IconAlert, IconX } from "../icons";
import type { CodexAccountEntry } from "./codex-account-pool-types";
import type { CodexAccountModeState } from "../codex-multi-state";
Expand Down
2 changes: 1 addition & 1 deletion gui/src/components/codex-account-pool-handlers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { TFn } from "../i18n";
import type { TFn } from "../i18n/shared";
import { readJsonIfOk } from "../fetch-json";

function remainingCreditsToast(
Expand Down
2 changes: 1 addition & 1 deletion gui/src/components/codex-account-pool-helpers.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { TFn } from "../i18n";
import type { TFn } from "../i18n/shared";
import { IconTicket } from "../icons";
import type { CodexAccountEntry } from "./codex-account-pool-types";
import { daysUntil, formatCreditDate } from "./codex-account-pool-utils";
Expand Down
2 changes: 1 addition & 1 deletion gui/src/components/codex-account-pool-main-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import QuotaBars from "./QuotaBars";
import { CodexTicketBadge } from "./codex-account-pool-helpers";
import type { CodexAccountEntry } from "./codex-account-pool-types";
import type { CodexAccountModeState } from "../codex-multi-state";
import type { TFn } from "../i18n";
import type { TFn } from "../i18n/shared";

export function CodexAccountPoolMainCard({
t,
Expand Down
5 changes: 3 additions & 2 deletions gui/src/components/codex-account-reset-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useRef } from "react";
import { useT } from "../i18n";
import { useT } from "../i18n/shared";
import { IconAlert, IconTicket } from "../icons";
import type { CodexAccountEntry } from "./codex-account-pool-types";
import { CodexCreditItem } from "./codex-account-pool-helpers";
Expand Down Expand Up @@ -45,8 +45,9 @@ export function CodexAccountResetModal({
className="modal-overlay"
aria-labelledby="codex-reset-title"
onCancel={handleCancel}
onClick={onClose}

>
<button type="button" className="modal-backdrop-dismiss" aria-label={t("common.close")} tabIndex={-1} onClick={onClose} />
<div className="modal-card" onClick={e => e.stopPropagation()} role="document">
{!resetConfirm ? (
<>
Expand Down
5 changes: 3 additions & 2 deletions gui/src/components/codex-account-switch-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useRef } from "react";
import { useT } from "../i18n";
import { useT } from "../i18n/shared";
import { IconAlert } from "../icons";
import type { CodexAccountEntry } from "./codex-account-pool-types";
import type { CodexAccountModeState } from "../codex-multi-state";
Expand Down Expand Up @@ -38,8 +38,9 @@ export function CodexAccountSwitchModal({
className="modal-overlay"
aria-labelledby="codex-switch-title"
onCancel={handleCancel}
onClick={onCancel}

>
<button type="button" className="modal-backdrop-dismiss" aria-label={t("common.close")} tabIndex={-1} onClick={onCancel} />
<div className="modal-card" onClick={e => e.stopPropagation()} role="document">
<h3 id="codex-switch-title">{accountModeState === "direct"
? t("codexAuth.preparePoolTitle")
Expand Down
4 changes: 1 addition & 3 deletions gui/src/components/combo-workspace-add-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,8 @@ export function AddComboModal({
className="modal-overlay"
aria-labelledby="cwi-add-title"
onCancel={handleCancel}
onClick={(e) => {
if (e.target === e.currentTarget) requestClose();
}}
>
<button type="button" className="modal-backdrop-dismiss" aria-label={t("common.close")} tabIndex={-1} onClick={requestClose} />
<div className="modal-card" style={{ width: "min(560px, 94vw)" }} onClick={(e) => e.stopPropagation()}>
<div className="row" style={{ justifyContent: "space-between", marginBottom: 8 }}>
<h3 id="cwi-add-title" style={{ margin: 0 }}>{t("cws.addTitle")}</h3>
Expand Down
8 changes: 2 additions & 6 deletions gui/src/components/combo-workspace-dialogs.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { useCallback, useEffect, useRef } from "react";
import { useT } from "../i18n/shared";

function dismissIfBackdrop(e: React.MouseEvent<HTMLDialogElement>, onDismiss: () => void) {
if (e.target === e.currentTarget) onDismiss();
}

export function RemoveComboDialog({
model,
onCancel,
Expand Down Expand Up @@ -33,8 +29,8 @@ export function RemoveComboDialog({
className="modal-overlay"
aria-labelledby="cwi-remove-title"
onCancel={handleCancel}
onClick={(e) => dismissIfBackdrop(e, onCancel)}
>
<button type="button" className="modal-backdrop-dismiss" aria-label={t("common.close")} tabIndex={-1} onClick={onCancel} />
<div className="modal-card pwi-remove-confirm-card" onClick={(e) => e.stopPropagation()}>
<h3 id="cwi-remove-title" className="pwi-remove-confirm-title">
{t("cws.removeConfirmTitle", { model })}
Expand Down Expand Up @@ -75,8 +71,8 @@ export function UnsavedLeaveDialog({
className="modal-overlay"
aria-labelledby="cwi-unsaved-title"
onCancel={handleCancel}
onClick={(e) => dismissIfBackdrop(e, onKeep)}
>
<button type="button" className="modal-backdrop-dismiss" aria-label={t("common.close")} tabIndex={-1} onClick={onKeep} />
<div className="modal-card pwi-json-unsaved-card" onClick={(e) => e.stopPropagation()}>
<h3 id="cwi-unsaved-title" className="pwi-json-unsaved-title">{t("cws.unsavedTitle")}</h3>
<p className="muted pwi-json-unsaved-desc">{t("cws.unsavedDesc")}</p>
Expand Down
Loading
Loading