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
6 changes: 6 additions & 0 deletions docs-site/src/content/docs/guides/web-dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,3 +390,9 @@ While browser authentication is pending, the dashboard does not recommend restar
### Usage chart keyboard and touch controls

Usage heatmap days have one Tab entry point. Use Up/Down for adjacent days and Left/Right for adjacent weeks. Weekly bars expose the same day details on keyboard focus, pointer hover, or touch. Day labels include the date, request count, and token count; tooltip overlays stay within the viewport.

### Claude

The **Claude** sidebar page sits directly below **Codex**. One header and tab strip stay in place while you switch tabs, ordered Account, Code, Desktop, Settings. The Account tab shows what the Anthropic provider's **Accounts** tab shows on **Providers**: login, the browser option, the Claude account roster with switch, pause, remove, and reauthentication, the paste-code field, account pool settings, and quota. Provider-level controls such as the default provider, removal, and the enabled switch stay on **Providers**. When Anthropic is not configured, **Add Anthropic** starts Claude sign-in directly, after the same risk notice the Add provider dialog shows. Opening Claude without a tab selects Account when Anthropic is configured, otherwise Code. Code holds the Claude connection switch; Settings shows that connection as On or Off, interception as Running or Stopped, and the intercept port. When interception is stopped, Settings shows why and offers **Start interception**, which starts it in place without restarting OpenCodex. On Desktop, one status row shows whether Claude Desktop runs the profile and whether it is saved, with Save and Save & apply beside it. Compatibility, agent instructions, and context controls remain on Code.

Bookmarks select a tab directly: `#claude/account`, `#claude/code`, `#claude/desktop`, and `#claude/settings`. The former `#integrations/claude` and `#integrations/claude/desktop` bookmarks redirect to Code and Desktop. Arrow keys move between tabs; Home and End select the first and last tab.
15 changes: 6 additions & 9 deletions gui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Subagents from "./pages/Subagents";
import Logs from "./pages/Logs";
import Usage from "./pages/Usage";
import Storage from "./pages/Storage";
import Claude from "./pages/Claude";
import CodexSet from "./pages/CodexSet";
import Integrations from "./pages/Integrations";
import Startup from "./pages/Startup";
Expand All @@ -16,7 +17,7 @@ import ErrorBoundary from "./components/ErrorBoundary";
import QuotaSummaryBar from "./components/quota-summary-bar/QuotaSummaryBar";
import { SidebarGithubRow } from "./components/sidebar-github-row";
import { DesktopStarOnboarding } from "./components/desktop-star-onboarding";
import { IconGrid, IconServer, IconBoxes, IconBot, IconList, IconActivity, IconHardDrive, IconCodex, IconMenu, IconSun, IconMoon, IconMonitor, IconGlobe, IconPower, IconX, IconRefresh} from "./icons";
import { IconGrid, IconServer, IconBoxes, IconBot, IconList, IconActivity, IconHardDrive, IconCodex, IconClaude, IconMenu, IconSun, IconMoon, IconMonitor, IconGlobe, IconPower, IconX, IconRefresh} from "./icons";
import { useI18n, useT, LOCALES, localeDisplayName, type Locale, type TKey } from "./i18n/shared";
import { Notice, Select, ToastNotice, type NoticeTone } from "./ui";
import { configureApiTargets, hasApiSession, installApiAuthFetch, installApiSessionFromHtml, logoutApiSession, SESSION_UNAVAILABLE_EVENT } from "./api";
Expand Down Expand Up @@ -48,6 +49,7 @@ const PAGE_TKEY: Record<Page, TKey> = {
"remote-workspace": "nav.remoteWorkspace",
"codex-set": "nav.codexSet",
integrations: "nav.integrations",
claude: "nav.claude",
};

const API_BASE = import.meta.env.VITE_API_BASE || "";
Expand All @@ -56,14 +58,7 @@ configureApiTargets(INITIAL_TARGETS);
installApiAuthFetch();
const THEME_KEY = "ocx-theme";

/**
* Every sidebar row maps one-to-one onto a page again.
*
* The Claude row was the exception: a second entry pointing at a tab of Integrations,
* which needed `subPath`, `activeHashes`, and an `isNavEntryActive` helper whose only
* job was stopping the sidebar from lighting two rows and claiming the user was in two
* places. Removing the duplicate removed all four.
*/
/** Every sidebar row maps to its own page. */
type NavEntry = {
id: Page;
tkey: TKey;
Expand All @@ -73,6 +68,7 @@ type NavEntry = {
const NAV: NavEntry[] = [
{ id: "dashboard", tkey: "nav.dashboard", Icon: IconGrid },
{ id: "codex-set", tkey: "nav.codexSet", Icon: IconCodex },
{ id: "claude", tkey: "nav.claude", Icon: IconClaude },
{ id: "providers", tkey: "nav.providers", Icon: IconServer },
{ id: "models", tkey: "nav.models", Icon: IconBoxes },
{ id: "subagents", tkey: "nav.subagents", Icon: IconBot },
Expand Down Expand Up @@ -576,6 +572,7 @@ export default function App() {
{page === "remote" && !remotePairingRequired && <RemoteLink apiBase={sharedBase} sessionReady={sharedSessionReady} workspaceAvailable={remoteWorkspaceAvailable} onOpenWorkspace={() => navigateToPage("remote-workspace")} />}
{page === "remote-workspace" && <RemoteWorkspaceRoute available={remoteWorkspaceAvailable} apiBase={sharedBase} hubOrigin={targets.shared.serverOrigin} onOpenRemoteLink={() => navigateToPage("remote")} />}
{page === "codex-set" && <CodexSet apiBase={sharedBase} />}
{page === "claude" && <Claude apiBase={sharedBase} />}
{page === "integrations" && <Integrations apiBase={sharedBase} machineApiBase={machineBase} connected={targets.connected} />}
</>
)}
Expand Down
10 changes: 7 additions & 3 deletions gui/src/app-routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export type Page =
| "remote"
| "remote-workspace"
| "codex-set"
| "integrations";
| "integrations"
| "claude";

export const VALID_PAGES = new Set<Page>([
"dashboard",
Expand All @@ -29,6 +30,7 @@ export const VALID_PAGES = new Set<Page>([
"remote-workspace",
"codex-set",
"integrations",
"claude",
]);

export function readPageFromHash(hash?: string): Page {
Expand All @@ -53,8 +55,8 @@ export function readPageFromHash(hash?: string): Page {
// the destination page here keeps the initial hook state aligned until the
// resolver replaces the hash with the exact nested destination.
if (pageId === ("api" as Page)
|| pageId === ("claude" as Page)
|| pageId === ("grok" as Page)) return "integrations";
if (raw === "integrations/claude" || raw === "integrations/claude/desktop") return "claude";
return VALID_PAGES.has(pageId) ? pageId : "dashboard";
}

Expand Down Expand Up @@ -140,6 +142,7 @@ export const QUERY_HASH_PATHS: readonly string[] = ["providers", "models/compati

export function hashBelongsToPage(rawHash: string, page: Page): boolean {
return rawHash === page
|| (page === "claude" && ["claude/account", "claude/code", "claude/desktop", "claude/settings"].includes(rawHash))
|| (page === "logs" && rawHash === "logs/debug")
|| (page === "usage" && rawHash === "usage/companion")
|| (page === "codex-set" && rawHash === "codex-set/prompt")
Expand Down Expand Up @@ -199,7 +202,8 @@ export function resolveAppHashChange(rawHash: string): AppHashChangeAction {

/* Legacy top-level integration pages. */
if (rawHash === "api") return { page: "integrations", replaceTo: "integrations/keys" };
if (rawHash === "claude") return { page: "integrations", replaceTo: "integrations/claude" };
if (rawHash === "integrations/claude") return { page: "claude", replaceTo: "claude/code" };
if (rawHash === "integrations/claude/desktop") return { page: "claude", replaceTo: "claude/desktop" };
if (rawHash === "grok") return { page: "integrations", replaceTo: "integrations/grok" };

// Legacy deep link from the removed dual-layout era.
Expand Down
25 changes: 23 additions & 2 deletions gui/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,26 @@ import type { TKey } from "./en";
* German i18n catalog, generated from en.ts. Must match the `TKey` set (compile-checked).
*/
export const de: Record<TKey, string> = {
"nav.claude": "Claude",
"claude.tabAccount": "Konto",
"claude.tabSettings": "Einstellungen",
"claude.pageSub": "Konten und OpenCodex-Routing für Claude Code und Claude Desktop.",
"claude.addAnthropic": "Anthropic hinzufügen",
"claude.changeOnCode": "Im Tab {tab} ändern",
"claude.interceptRunning": "Läuft",
"claude.interceptStopped": "Gestoppt",
"claude.routingLoadFail": "Claude-Routingstatus konnte nicht geladen werden.",
"claude.interceptLoadFail": "Abfangstatus konnte nicht geladen werden.",
"claude.accountEmpty": "Fügen Sie den Anbieter Anthropic hinzu und melden Sie sich mit Ihrem Claude-Konto an. Konten, Kontingent und Einstellungen erscheinen dann hier.",
"claude.accountEmptyTitle": "Anthropic ist noch nicht eingerichtet",
"claude.interceptStatus": "Abfangen",
"claude.interceptDesc": "Leitet den für Anthropic-Dienste bestimmten Traffic von Claude Code/Desktop über den lokalen Proxy.",
"claude.interceptPort": "Abfangport",
"claude.interceptPortDesc": "Lokaler Port des Abfang-Proxys, getrennt vom OpenCodex-Proxy-Port.",
"claude.stateOn": "An",
"claude.stateOff": "Aus",
"claude.settingsHint": "Kompatibilität, Agent-Anweisungen und Kontexteinstellungen lassen sich im Tab Code ändern.",

"pws.anthropicAccountThresholdHint": "Überschreibt den Standard des Claude-Pools. 0 deaktiviert den nutzungsbasierten Wechsel nur für dieses Konto; Pause und Wiederherstellung bei Ratenlimits gelten weiterhin.",
"kiroLogin.title": "Bei Kiro anmelden",
"kiroLogin.chooseMethod": "Anmeldemethode wählen",
Expand Down Expand Up @@ -2855,7 +2875,7 @@ export const de: Record<TKey, string> = {
"codexAuth.addIdPlaceholder": "codex-work, codex-alt, team…",
"codexAuth.resetCreditsAria": "{count} Reset-Guthaben",
"claude.pageTitle": "Claude Code",
"claude.workspace.settings": "Einstellungen",
"claude.workspace.settings": "Allgemein",

// Combos workspace
"cws.loading": "Combos werden geladen…",
Expand Down Expand Up @@ -3075,7 +3095,7 @@ export const de: Record<TKey, string> = {
"claude.tabCode": "Code",
"claude.tabDesktop": "Desktop",
"claudeDesktop.title": "Claude Desktop",
"claudeDesktop.subtitle": "Leite jede Claude-Modellfamilie über ein verfügbares Modell auf Port {port}.",
"claudeDesktop.subtitle": "Leite jede Claude-Modellfamilie über ein verfügbares Modell auf Proxy-Port {port}.",
"claudeDesktop.importJson": "JSON importieren",
"claudeDesktop.exportJson": "JSON exportieren",
"claudeDesktop.loading": "Claude-Desktop-Profil wird geladen…",
Expand Down Expand Up @@ -3174,6 +3194,7 @@ export const de: Record<TKey, string> = {
"claude.intercept.reason.failed": "Abfangen konnte nicht starten. Prüfen Sie die lokale Proxy-Konfiguration und versuchen Sie es erneut.",
"claude.intercept.reason.stopped": "Abfangen wurde beendet. Starten Sie den OpenCodex-Dienst.",
"claude.firstParty.label": "Claude Code CLI First-Party",
"claude.firstParty.desc": "Die eigenständige claude-CLI behält ihre Claude-Anmeldung, ihr Traffic läuft aber über den lokalen Abfang-Proxy.",
"claude.firstParty.aria": "Claude Code CLI First-Party umschalten",
"claude.firstParty.risk": "Kontorisiko: First-Party leitet Claude-Abonnementverkehr über einen lokalen Abfangproxy. Anthropic kann dies als Verstoß gegen die Nutzungsbedingungen werten und das Konto sperren.",
"claude.firstParty.shared": "Wenn die gemeinsame Proxy-Umgebung angewendet ist, läuft auch der andere Claude-Client unverändert über den lokalen Proxy; TLS endet lokal. Für nativen Terminalverkehr NO_PROXY='*' in der Shell setzen.",
Expand Down
25 changes: 23 additions & 2 deletions gui/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@
* `{var}` are plain interpolations.
*/
export const en = {
"nav.claude": "Claude",
"claude.tabAccount": "Account",
"claude.tabSettings": "Settings",
"claude.pageSub": "Accounts and OpenCodex routing for Claude Code and Claude Desktop.",
"claude.addAnthropic": "Add Anthropic",
"claude.changeOnCode": "Change on {tab} tab",
"claude.interceptRunning": "Running",
"claude.interceptStopped": "Stopped",
"claude.routingLoadFail": "Could not load Claude routing status.",
"claude.interceptLoadFail": "Could not load interception status.",
"claude.accountEmpty": "Add the Anthropic provider and sign in with your Claude account. Its accounts, quota, and settings then appear here.",
"claude.accountEmptyTitle": "Anthropic is not set up yet",
"claude.interceptStatus": "Interception",
"claude.interceptDesc": "Routes Claude Code/Desktop traffic bound for Anthropic's services through the local proxy.",
"claude.interceptPort": "Intercept port",
"claude.interceptPortDesc": "Local port of the intercept proxy, separate from the OpenCodex proxy port.",
"claude.stateOn": "On",
"claude.stateOff": "Off",
"claude.settingsHint": "Compatibility, agent instructions, and context settings can be changed on the Code tab.",

"pws.anthropicAccountThresholdHint": "Overrides the Claude pool default. 0 disables usage-based switching only for this account; pause and rate-limit recovery still apply.",
"kiroLogin.title": "Sign in to Kiro",
"kiroLogin.chooseMethod": "Choose a sign-in method",
Expand Down Expand Up @@ -2902,7 +2922,7 @@ export const en = {
// Claude Code inbound
"claude.subtitle": "Use GPT, Gemini, and other models inside Claude Code.",
"claude.pageTitle": "Claude Code",
"claude.workspace.settings": "Settings",
"claude.workspace.settings": "General",
"claude.enabledLabel": "Claude connection",
"claude.enabledHint": "When off, Claude Code cannot use this proxy.",
"claude.authMode": "Auth Mode",
Expand Down Expand Up @@ -3189,7 +3209,7 @@ export const en = {
"claude.tabCode": "Code",
"claude.tabDesktop": "Desktop",
"claudeDesktop.title": "Claude Desktop",
"claudeDesktop.subtitle": "Route each Claude model family through an available model on port {port}.",
"claudeDesktop.subtitle": "Route each Claude model family through an available model on proxy port {port}.",
"claudeDesktop.importJson": "Import JSON",
"claudeDesktop.exportJson": "Export JSON",
"claudeDesktop.loading": "Loading Claude Desktop profile…",
Expand Down Expand Up @@ -3288,6 +3308,7 @@ export const en = {
"claude.intercept.reason.failed": "Interception could not start. Retry after checking the local proxy configuration.",
"claude.intercept.reason.stopped": "The interception lifecycle has stopped. Start a running OpenCodex service.",
"claude.firstParty.label": "Claude Code CLI first-party",
"claude.firstParty.desc": "Lets the standalone claude CLI keep its Claude sign-in while its traffic passes through the local intercept proxy.",
"claude.firstParty.aria": "Toggle Claude Code CLI first-party",
"claude.firstParty.risk": "Account risk: first-party routes Claude subscription traffic through a local interception proxy. Anthropic may treat this as a terms violation and suspend the account.",
"claude.firstParty.shared": "When the shared proxy env is applied, the other Claude client also transits the local proxy as an unchanged relay; TLS terminates locally. Set NO_PROXY='*' in the shell for fully native terminal use.",
Expand Down
25 changes: 23 additions & 2 deletions gui/src/i18n/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@ import type { TKey } from "./en";
* French i18n catalog. Must match the `TKey` set.
*/
export const fr: Record<TKey, string> = {
"nav.claude": "Claude",
"claude.tabAccount": "Compte",
"claude.tabSettings": "Paramètres",
"claude.pageSub": "Comptes et routage OpenCodex pour Claude Code et Claude Desktop.",
"claude.addAnthropic": "Ajouter Anthropic",
"claude.changeOnCode": "Modifier dans l’onglet {tab}",
"claude.interceptRunning": "En cours",
"claude.interceptStopped": "Arrêté",
"claude.routingLoadFail": "Impossible de charger le routage Claude.",
"claude.interceptLoadFail": "Impossible de charger l’interception.",
"claude.accountEmpty": "Ajoutez le fournisseur Anthropic et connectez-vous avec votre compte Claude. Ses comptes, son quota et ses réglages apparaîtront ici.",
"claude.accountEmptyTitle": "Anthropic n’est pas encore configuré",
"claude.interceptStatus": "Interception du trafic",
"claude.interceptDesc": "Achemine vers le proxy local le trafic de Claude Code/Desktop destiné aux services Anthropic.",
"claude.interceptPort": "Port d’interception",
"claude.interceptPortDesc": "Port local du proxy d’interception, distinct du port du proxy OpenCodex.",
"claude.stateOn": "Activé",
"claude.stateOff": "Désactivé",
"claude.settingsHint": "La compatibilité, les instructions des agents et le contexte se modifient dans l’onglet Code.",

"pws.anthropicAccountThresholdHint": "Remplace le seuil par défaut du pool Claude. 0 désactive le basculement selon l’utilisation uniquement pour ce compte ; la pause et la reprise après limitation restent actives.",
"kiroLogin.title": "Se connecter à Kiro",
"kiroLogin.chooseMethod": "Choisir une méthode de connexion",
Expand Down Expand Up @@ -2817,7 +2837,7 @@ export const fr: Record<TKey, string> = {
"api.attribution.railAmbiguous": "identifiant en double",
"claude.subtitle": "Utilisez GPT, Gemini et d’autres modèles dans Claude Code.",
"claude.pageTitle": "Claude Code",
"claude.workspace.settings": "Paramètres",
"claude.workspace.settings": "Général",
"claude.enabledLabel": "Connexion Claude",
"claude.enabledHint": "Lorsque cette option est désactivée, Claude Code ne peut pas utiliser ce proxy.",
"claude.authMode": "Mode d’authentification",
Expand Down Expand Up @@ -3102,7 +3122,7 @@ export const fr: Record<TKey, string> = {
"claude.tabCode": "Code",
"claude.tabDesktop": "Desktop",
"claudeDesktop.title": "Claude Desktop",
"claudeDesktop.subtitle": "Acheminez chaque famille de modèles Claude vers un modèle disponible sur le port {port}.",
"claudeDesktop.subtitle": "Acheminez chaque famille de modèles Claude vers un modèle disponible sur le port du proxy {port}.",
"claudeDesktop.importJson": "Importer le JSON",
"claudeDesktop.exportJson": "Exporter le JSON",
"claudeDesktop.loading": "Chargement du profil Claude Desktop…",
Expand Down Expand Up @@ -3201,6 +3221,7 @@ export const fr: Record<TKey, string> = {
"claude.intercept.reason.failed": "L’interception n’a pas pu démarrer. Vérifiez le proxy local et réessayez.",
"claude.intercept.reason.stopped": "L’interception est arrêtée. Démarrez le service OpenCodex.",
"claude.firstParty.label": "First-party de la CLI Claude Code",
"claude.firstParty.desc": "La CLI claude autonome garde sa connexion Claude, mais son trafic passe par le proxy d’interception local.",
"claude.firstParty.aria": "Activer ou désactiver le first-party de la CLI Claude Code",
"claude.firstParty.risk": "Risque pour le compte : le mode first-party fait passer le trafic de l'abonnement Claude par un proxy local d'interception. Anthropic peut y voir une violation de ses conditions et suspendre le compte.",
"claude.firstParty.shared": "Si les réglages du proxy partagé sont appliqués, l’autre client Claude traverse aussi le proxy local sans modification ; TLS se termine localement. Définissez NO_PROXY='*' dans le shell pour un terminal entièrement natif.",
Expand Down
Loading
Loading