import React, { useEffect } from 'react'; import { createPortal } from 'react-dom'; import { Tooltip, TooltipContent, TooltipTrigger, } from '@/components/ui/tooltip'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { SortableTabsStrip, type SortableTabsStripItem } from '@/components/ui/sortable-tabs-strip'; import { RiArrowLeftSLine, RiChat4Line, RiChatNewLine, RiCheckLine, RiCloseLine, RiCommandLine, RiFileTextLine, RiFolder6Line, RiGitBranchLine, RiGithubFill, RiLayoutLeftLine, RiLayoutRightLine, RiPlayListAddLine, RiRefreshLine, RiServerLine, RiStackLine, RiTerminalBoxLine, RiTimerLine, RiAlertLine, type RemixiconComponentType } from '@remixicon/react'; import { DiffIcon } from '@/components/icons/DiffIcon'; import { useUIStore, type MainTab } from '@/stores/useUIStore'; import { useConfigStore } from '@/stores/useConfigStore'; import { useSessionUIStore } from '@/sync/session-ui-store'; import { useSessionWorktreeStore } from '@/sync/session-worktree-store'; import { formatSessionWorktreeBadge } from '@/sync/session-worktree-contract'; import { useAllLiveSessions, useSession, useSessionMessagesResolved } from '@/sync/sync-context'; import { getAllSyncSessions } from '@/sync/sync-refs'; import { useProjectsStore } from '@/stores/useProjectsStore'; import { useQuotaAutoRefresh, useQuotaStore } from '@/stores/useQuotaStore'; import { useGitBranchLabel } from '@/stores/useGitStore'; import { useGlobalSessionsStore } from '@/stores/useGlobalSessionsStore'; import { useFeatureFlagsStore } from '@/stores/useFeatureFlagsStore'; import { useGitHubAuthStore } from '@/stores/useGitHubAuthStore'; import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs'; import { ContextUsageDisplay } from '@/components/ui/ContextUsageDisplay'; import { useDeviceInfo } from '@/lib/device'; import { cn, hasModifier } from '@/lib/utils'; import { McpDropdownContent } from '@/components/mcp/McpDropdown'; import { McpIcon } from '@/components/icons/McpIcon'; import { ProviderLogo } from '@/components/ui/ProviderLogo'; import { formatPercent, formatWindowLabel, QUOTA_PROVIDERS, calculatePace, calculateExpectedUsagePercent } from '@/lib/quota'; import { UsageProgressBar } from '@/components/sections/usage/UsageProgressBar'; import { PaceIndicator } from '@/components/sections/usage/PaceIndicator'; import { updateDesktopSettings } from '@/lib/persistence'; import { eventMatchesShortcut, formatShortcutForDisplay, getEffectiveShortcutCombo } from '@/lib/shortcuts'; import { getAllModelFamilies, getDisplayModelName, groupModelsByFamily, sortModelFamilies, } from '@/lib/quota/model-families'; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from '@/components/ui/collapsible'; import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react'; import type { UsageWindow } from '@/types'; import type { GitHubAuthStatus } from '@/lib/api/types'; import type { SessionContextUsage } from '@/stores/types/sessionTypes'; import { DesktopHostSwitcherDialog } from '@/components/desktop/DesktopHostSwitcher'; import { OpenInAppButton } from '@/components/desktop/OpenInAppButton'; import { forceKillTerminal } from '@/lib/terminalApi'; import { useTerminalStore } from '@/stores/useTerminalStore'; import { ProjectActionsButton } from '@/components/layout/ProjectActionsButton'; import { isDesktopShell, isVSCodeRuntime, startDesktopWindowDrag } from '@/lib/desktop'; import { desktopHostsGet, locationMatchesHost, redactSensitiveUrl } from '@/lib/desktopHosts'; import { resolveSessionDiffStats } from '@/components/session/sidebar/utils'; import { useI18n } from '@/lib/i18n'; import type { Session } from '@opencode-ai/sdk/v2/client'; const DESKTOP_HEADER_ICON_BUTTON_CLASS = 'app-region-no-drag inline-flex h-8 w-8 items-center justify-center gap-2 rounded-md typography-ui-label font-medium text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50 hover:bg-interactive-hover transition-colors'; const MOBILE_HEADER_ICON_BUTTON_CLASS = 'app-region-no-drag inline-flex h-9 w-9 items-center justify-center gap-2 p-2 rounded-md typography-ui-label font-medium text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50 hover:text-foreground hover:bg-interactive-hover transition-colors'; type HeaderIconActionButtonProps = { visible?: boolean; title: string; ariaLabel: string; onClick: () => void; className?: string; Icon: RemixiconComponentType; iconClassName?: string; }; const HeaderIconActionButton = React.memo(function HeaderIconActionButton({ visible = true, title, ariaLabel, onClick, className, Icon, iconClassName, }: HeaderIconActionButtonProps) { if (!visible) { return null; } return (

{title}

); }); type DesktopGitHubControlProps = { isMobile: boolean; githubAuthStatus: GitHubAuthStatus | null; githubAccounts: Array[number]>; githubAvatarUrl: string | null; githubLogin: string | null; isSwitchingGitHubAccount: boolean; handleGitHubAccountSwitch: (accountId: string) => Promise; }; const DesktopGitHubControl = React.memo(function DesktopGitHubControl({ isMobile, githubAuthStatus, githubAccounts, githubAvatarUrl, githubLogin, isSwitchingGitHubAccount, handleGitHubAccountSwitch, }: DesktopGitHubControlProps) { const { t } = useI18n(); if (!githubAuthStatus?.connected || isMobile) { return null; } if (githubAccounts.length > 1) { return ( {t('header.github.accountsTitle')} {githubAccounts.map((account) => { const accountUser = account.user; const isCurrent = Boolean(account.current); return ( { if (!isCurrent) { void handleGitHubAccountSwitch(account.id); } }} > {accountUser?.avatarUrl ? ( {accountUser.login ) : (
)} {accountUser?.name?.trim() || accountUser?.login || 'GitHub'} {accountUser?.login ? ( {accountUser.login} ) : null} {isCurrent ? : null}
); })}
); } return (
{githubAvatarUrl ? ( {githubLogin ) : ( )}
); }); type DesktopServicesMenuProps = { isDesktopApp: boolean; currentInstanceLabel: string; compactCurrentInstanceLabel: string; isDesktopServicesOpen: boolean; setIsDesktopServicesOpen: React.Dispatch>; refreshCurrentInstanceLabel: () => Promise; desktopServicesTab: 'instance' | 'usage' | 'mcp'; setDesktopServicesTab: React.Dispatch>; quotaResultsLength: number; fetchAllQuotas: () => Promise; servicesTabItems: SortableTabsStripItem[]; quotaLastUpdated: number | null; quotaDisplayMode: 'usage' | 'remaining'; quotaDisplayTabItems: SortableTabsStripItem[]; handleDisplayModeChange: (mode: 'usage' | 'remaining') => Promise; handleUsageRefresh: () => void; isQuotaLoading: boolean; isUsageRefreshSpinning: boolean; hasRateLimits: boolean; rateLimitGroups: RateLimitGroup[]; expandedFamilies: Record; toggleFamilyExpanded: (providerId: string, familyId: string) => void; shortcutLabel: (actionId: string) => string; showDevShutdown: boolean; isDevShutdownInFlight: boolean; onDevShutdown: () => Promise; }; const DesktopServicesMenu = React.memo(function DesktopServicesMenu({ isDesktopApp, currentInstanceLabel, compactCurrentInstanceLabel, isDesktopServicesOpen, setIsDesktopServicesOpen, refreshCurrentInstanceLabel, desktopServicesTab, setDesktopServicesTab, quotaResultsLength, fetchAllQuotas, servicesTabItems, quotaLastUpdated, quotaDisplayMode, quotaDisplayTabItems, handleDisplayModeChange, handleUsageRefresh, isQuotaLoading, isUsageRefreshSpinning, hasRateLimits, rateLimitGroups, expandedFamilies, toggleFamilyExpanded, shortcutLabel, showDevShutdown, isDevShutdownInFlight, onDevShutdown, }: DesktopServicesMenuProps) { const { t } = useI18n(); return ( { setIsDesktopServicesOpen(open); if (open) { void refreshCurrentInstanceLabel(); if (desktopServicesTab === 'usage' && quotaResultsLength === 0) { void fetchAllQuotas(); } } }} >

{isDesktopApp ? t('header.services.tooltip.currentInstanceWithShortcuts', { current: currentInstanceLabel, toggle: shortcutLabel('toggle_services_menu'), nextTab: shortcutLabel('cycle_services_tab'), }) : t('header.services.tooltip.servicesWithShortcuts', { toggle: shortcutLabel('toggle_services_menu'), nextTab: shortcutLabel('cycle_services_tab'), })}

{ const value = tabID as 'instance' | 'usage' | 'mcp'; setDesktopServicesTab(value); if (value === 'usage' && quotaResultsLength === 0) { void fetchAllQuotas(); } }} layoutMode="fit" variant="active-pill" activePillInsetClassName="gap-0.5 px-px py-0" activePillButtonClassName="h-8" className="h-full" />
{isDesktopApp && desktopServicesTab === 'instance' ? ( {}} onHostSwitched={() => setIsDesktopServicesOpen(false)} /> ) : null} {desktopServicesTab === 'mcp' ? ( ) : null} {desktopServicesTab === 'usage' ? (
{t('header.services.rateLimits')} {formatTime(quotaLastUpdated)}
void handleDisplayModeChange(tabID as 'usage' | 'remaining')} layoutMode="fit" variant="active-pill" activePillInsetClassName="gap-0.5 px-px py-0" className="h-full" />
{!hasRateLimits ? (
{t('header.services.noRateLimits')}
) : null}
{rateLimitGroups.map((group, index) => { const providerExpandedFamilies = expandedFamilies[group.providerId] ?? []; return ( {index > 0 ?
: null}
{group.providerName}
{group.entries.length === 0 && (!group.modelFamilies || group.modelFamilies.length === 0) ? (
{group.error ?? t('header.services.noRateLimitsReported')}
) : (
{group.entries.map(([label, window]) => { const displayPercent = quotaDisplayMode === 'remaining' ? window.remainingPercent : window.usedPercent; const paceInfo = calculatePace(window.usedPercent, window.resetAt, window.windowSeconds, label); const expectedMarker = paceInfo?.dailyAllocationPercent != null ? (quotaDisplayMode === 'remaining' ? 100 - calculateExpectedUsagePercent(paceInfo.elapsedRatio) : calculateExpectedUsagePercent(paceInfo.elapsedRatio)) : null; return (
{formatWindowLabel(label)} {window.resetAfterFormatted ?? window.resetAtFormatted ? ( {window.resetAfterFormatted ?? window.resetAtFormatted} ) : null}
{formatPercent(displayPercent) === '-' ? '' : formatPercent(displayPercent)}
{paceInfo ? : null}
); })} {group.modelFamilies && group.modelFamilies.length > 0 ? (
{group.modelFamilies.map((family) => { const familyKey = family.familyId ?? 'other'; const isExpanded = providerExpandedFamilies.includes(familyKey); return ( toggleFamilyExpanded(group.providerId, familyKey)} > {family.familyLabel} {isExpanded ? : }
{family.models.map(([modelName, window]) => { const displayPercent = quotaDisplayMode === 'remaining' ? window.remainingPercent : window.usedPercent; const paceInfo = calculatePace(window.usedPercent, window.resetAt, window.windowSeconds); const expectedMarker = paceInfo?.dailyAllocationPercent != null ? (quotaDisplayMode === 'remaining' ? 100 - calculateExpectedUsagePercent(paceInfo.elapsedRatio) : calculateExpectedUsagePercent(paceInfo.elapsedRatio)) : null; return (
{getDisplayModelName(modelName)} {formatPercent(displayPercent) === '-' ? '' : formatPercent(displayPercent)}
{paceInfo ? : null}
); })}
); })}
) : null}
)} ); })}
) : null} {showDevShutdown ? ( <>
{ void onDevShutdown(); }} > {t('header.services.shutdownDev')}
) : null} ); }); const isSameContextUsage = ( a: SessionContextUsage | null, b: SessionContextUsage | null, ): boolean => { if (a === b) return true; if (!a || !b) return false; return a.totalTokens === b.totalTokens && a.percentage === b.percentage && a.contextLimit === b.contextLimit && (a.outputLimit ?? 0) === (b.outputLimit ?? 0) && (a.normalizedOutput ?? 0) === (b.normalizedOutput ?? 0) && a.thresholdLimit === b.thresholdLimit && (a.lastMessageId ?? '') === (b.lastMessageId ?? ''); }; const formatCompactHeaderLabel = (value: string): string => { const trimmed = value.trim(); if (!trimmed) { return ''; } const words = trimmed.split(/\s+/).filter(Boolean); if (words.length >= 2) { const first = words[0]; const second = words[1].slice(0, 3); const shortTwoWord = `${first} ${second}`.trim(); if (words.length > 2 || shortTwoWord.length < trimmed.length) { return `${shortTwoWord}...`; } return shortTwoWord; } return trimmed.length > 12 ? `${trimmed.slice(0, 9).trimEnd()}...` : trimmed; }; const formatTime = (timestamp: number | null) => { if (!timestamp) return '-'; try { return new Date(timestamp).toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit', }); } catch { return '-'; } }; const normalize = (value: string): string => { if (!value) return ''; const replaced = value.replace(/\\/g, '/'); return replaced === '/' ? '/' : replaced.replace(/\/+$/, ''); }; const getActiveContextMode = (panelState: { isOpen: boolean; activeTabId: string | null; tabs: Array<{ id: string; mode: 'diff' | 'file' | 'context' | 'plan' | 'chat' | 'preview' }>; } | undefined): 'diff' | 'file' | 'context' | 'plan' | 'chat' | 'preview' | null => { if (!panelState?.isOpen || !Array.isArray(panelState.tabs) || panelState.tabs.length === 0) { return null; } const activeTab = panelState.tabs.find((tab) => tab.id === panelState.activeTabId) ?? panelState.tabs[panelState.tabs.length - 1]; return activeTab?.mode ?? null; }; interface TabConfig { id: MainTab; label: string; icon: RemixiconComponentType | 'diff'; badge?: number; showDot?: boolean; } interface RateLimitGroup { providerId: string; providerName: string; entries: Array<[string, UsageWindow]>; error?: string; modelFamilies?: Array<{ familyId: string | null; familyLabel: string; models: Array<[string, UsageWindow]>; }>; } interface HeaderProps { onToggleLeftDrawer?: () => void; onToggleRightDrawer?: () => void; leftDrawerOpen?: boolean; rightDrawerOpen?: boolean; desktopRightSidebarActionsHost?: HTMLElement | null; } export const Header: React.FC = ({ onToggleLeftDrawer, onToggleRightDrawer, leftDrawerOpen, rightDrawerOpen, desktopRightSidebarActionsHost = null, }) => { const { t } = useI18n(); const setSessionSwitcherOpen = useUIStore((state) => state.setSessionSwitcherOpen); const toggleSidebar = useUIStore((state) => state.toggleSidebar); const isSidebarOpen = useUIStore((state) => state.isSidebarOpen); const isRightSidebarOpen = useUIStore((state) => state.isRightSidebarOpen); const toggleBottomTerminal = useUIStore((state) => state.toggleBottomTerminal); const toggleRightSidebar = useUIStore((state) => state.toggleRightSidebar); const openContextOverview = useUIStore((state) => state.openContextOverview); const openContextPlan = useUIStore((state) => state.openContextPlan); const closeContextPanel = useUIStore((state) => state.closeContextPanel); const contextPanelByDirectory = useUIStore((state) => state.contextPanelByDirectory); const activeMainTab = useUIStore((state) => state.activeMainTab); const setActiveMainTab = useUIStore((state) => state.setActiveMainTab); const shortcutOverrides = useUIStore((state) => state.shortcutOverrides); const getCurrentModel = useConfigStore((state) => state.getCurrentModel); const runtimeApis = useRuntimeAPIs(); const [isDevShutdownInFlight, setIsDevShutdownInFlight] = React.useState(false); const getContextUsage = useSessionUIStore((state) => state.getContextUsage); const openNewSessionDraft = useSessionUIStore((state) => state.openNewSessionDraft); const isNewSessionDraftOpen = useSessionUIStore((state) => Boolean(state.newSessionDraft?.open)); const currentSessionId = useSessionUIStore((state) => state.currentSessionId); const currentSessionMessagesResolved = useSessionMessagesResolved(currentSessionId ?? ''); const currentSyncedSession = useSession(currentSessionId ?? null); const globalActiveSessions = useGlobalSessionsStore((state) => state.activeSessions); const liveSessions = useAllLiveSessions(); const activeProject = useProjectsStore((state) => { if (!state.activeProjectId) { return null; } return state.projects.find((project) => project.id === state.activeProjectId) ?? null; }); const activeProjectLabel = React.useMemo(() => { if (!activeProject) { return null; } const trimmedLabel = activeProject.label?.trim(); if (trimmedLabel) { return trimmedLabel; } const pathSegments = activeProject.path.split(/[\\/]/).filter(Boolean); return pathSegments[pathSegments.length - 1] ?? null; }, [activeProject]); const quotaResults = useQuotaStore((state) => state.results); const fetchAllQuotas = useQuotaStore((state) => state.fetchAllQuotas); const isQuotaLoading = useQuotaStore((state) => state.isLoading); const quotaLastUpdated = useQuotaStore((state) => state.lastUpdated); const quotaDisplayMode = useQuotaStore((state) => state.displayMode); const dropdownProviderIds = useQuotaStore((state) => state.dropdownProviderIds); const loadQuotaSettings = useQuotaStore((state) => state.loadSettings); const setQuotaDisplayMode = useQuotaStore((state) => state.setDisplayMode); const { isMobile } = useDeviceInfo(); const githubAuthStatus = useGitHubAuthStore((state) => state.status); const setGitHubAuthStatus = useGitHubAuthStore((state) => state.setStatus); const headerRef = React.useRef(null); const [isDesktopApp, setIsDesktopApp] = React.useState(() => { if (typeof window === 'undefined') { return false; } return isDesktopShell(); }); const [isDesktopWindowFullscreen, setIsDesktopWindowFullscreen] = React.useState(false); const isMacPlatform = React.useMemo(() => { if (typeof navigator === 'undefined') { return false; } return /Macintosh|Mac OS X/.test(navigator.userAgent || ''); }, []); const macosMajorVersion = React.useMemo(() => { if (typeof window === 'undefined') { return null; } const injected = (window as unknown as { __OPENCHAMBER_MACOS_MAJOR__?: unknown }).__OPENCHAMBER_MACOS_MAJOR__; if (typeof injected === 'number' && Number.isFinite(injected) && injected > 0) { return injected; } // Fallback: WebKit reports "Mac OS X 10_15_7" format where 10 is legacy prefix if (typeof navigator === 'undefined') { return null; } const match = (navigator.userAgent || '').match(/Mac OS X (\d+)[._](\d+)/); if (!match) { return null; } const first = Number.parseInt(match[1], 10); const second = Number.parseInt(match[2], 10); if (Number.isNaN(first)) { return null; } return first === 10 ? second : first; }, []); useEffect(() => { if (typeof window === 'undefined') { return; } setIsDesktopApp(isDesktopShell()); }, []); const currentModel = getCurrentModel(); const limit = currentModel && typeof currentModel.limit === 'object' && currentModel.limit !== null ? (currentModel.limit as Record) : null; const contextLimit = (limit && typeof limit.context === 'number' ? limit.context : 0); const outputLimit = (limit && typeof limit.output === 'number' ? limit.output : 0); const contextUsage = getContextUsage(contextLimit, outputLimit); const [stableDesktopContextUsage, setStableDesktopContextUsage] = React.useState(null); const isContextUsageResolvedForSession = !currentSessionId || currentSessionMessagesResolved; useEffect(() => { if (!currentSessionId) { setStableDesktopContextUsage((prev) => (prev === null ? prev : null)); return; } if (contextUsage && contextUsage.totalTokens > 0) { setStableDesktopContextUsage((prev) => (isSameContextUsage(prev, contextUsage) ? prev : contextUsage)); return; } if (isContextUsageResolvedForSession) { setStableDesktopContextUsage((prev) => (prev === null ? prev : null)); } }, [contextUsage, currentSessionId, isContextUsageResolvedForSession]); const isSessionSwitcherOpen = useUIStore((state) => state.isSessionSwitcherOpen); const githubAvatarUrl = githubAuthStatus?.connected ? (githubAuthStatus.user?.avatarUrl ?? null) : null; const githubLogin = githubAuthStatus?.connected ? (githubAuthStatus.user?.login ?? null) : null; const githubAccounts = githubAuthStatus?.accounts ?? []; const [isSwitchingGitHubAccount, setIsSwitchingGitHubAccount] = React.useState(false); const [isMobileRateLimitsOpen, setIsMobileRateLimitsOpen] = React.useState(false); const [isDesktopServicesOpen, setIsDesktopServicesOpen] = React.useState(false); const [isUsageRefreshSpinning, setIsUsageRefreshSpinning] = React.useState(false); const [currentInstanceLabel, setCurrentInstanceLabel] = React.useState('Local'); const compactCurrentInstanceLabel = React.useMemo(() => formatCompactHeaderLabel(currentInstanceLabel), [currentInstanceLabel]); const [desktopServicesTab, setDesktopServicesTab] = React.useState<'instance' | 'usage' | 'mcp'>( isDesktopApp ? 'instance' : 'usage' ); const [mobileServicesTab, setMobileServicesTab] = React.useState<'usage' | 'mcp'>('usage'); useEffect(() => { if (!isDesktopApp && desktopServicesTab === 'instance') { setDesktopServicesTab('usage'); } }, [desktopServicesTab, isDesktopApp]); const isVSCode = React.useMemo(() => isVSCodeRuntime(), []); const isLeftSidebarOpen = React.useMemo(() => { if (!isMobile) { return isSidebarOpen; } if (typeof onToggleLeftDrawer === 'function') { return Boolean(leftDrawerOpen); } return isSessionSwitcherOpen; }, [isMobile, isSessionSwitcherOpen, isSidebarOpen, leftDrawerOpen, onToggleLeftDrawer]); const showDesktopHeaderContextUsage = !isVSCode && activeMainTab === 'chat' && !!stableDesktopContextUsage && stableDesktopContextUsage.totalTokens > 0; const desktopHeaderDisplayPercentage = stableDesktopContextUsage && stableDesktopContextUsage.contextLimit > 0 ? Math.min(999, (stableDesktopContextUsage.totalTokens / stableDesktopContextUsage.contextLimit) * 100) : 0; const refreshCurrentInstanceLabel = React.useCallback(async () => { if (typeof window === 'undefined' || !isDesktopApp) { return; } try { const cfg = await desktopHostsGet(); const currentHref = window.location.href; const localOrigin = window.__OPENCHAMBER_LOCAL_ORIGIN__ || window.location.origin; if (locationMatchesHost(currentHref, localOrigin)) { setCurrentInstanceLabel('Local'); return; } const match = cfg.hosts.find((host) => { return locationMatchesHost(currentHref, host.url); }); if (match?.label?.trim()) { setCurrentInstanceLabel(redactSensitiveUrl(match.label.trim())); return; } setCurrentInstanceLabel('Instance'); } catch { setCurrentInstanceLabel('Local'); } }, [isDesktopApp]); useEffect(() => { void refreshCurrentInstanceLabel(); }, [refreshCurrentInstanceLabel]); useQuotaAutoRefresh(); const selectedModels = useQuotaStore((state) => state.selectedModels); const expandedFamilies = useQuotaStore((state) => state.expandedFamilies); const toggleFamilyExpanded = useQuotaStore((state) => state.toggleFamilyExpanded); const rateLimitGroups = React.useMemo(() => { const groups: RateLimitGroup[] = []; for (const provider of QUOTA_PROVIDERS) { if (!dropdownProviderIds.includes(provider.id)) { continue; } const result = quotaResults.find((entry) => entry.providerId === provider.id); const windows = (result?.usage?.windows ?? {}) as Record; const models = result?.usage?.models; const entries = Object.entries(windows); const group: RateLimitGroup = { providerId: provider.id, providerName: provider.name, entries, error: (result && !result.ok && result.configured) ? result.error : undefined, }; // Add model families if provider has per-model quotas if (models && Object.keys(models).length > 0) { const providerSelectedModels = selectedModels[provider.id] ?? []; // hasExplicitSelection = true means user has selected specific models to show // If the array exists but is empty, treat as "show all" (user cleared selection) const hasExplicitSelection = providerSelectedModels.length > 0; const modelGroups = groupModelsByFamily(models, provider.id); const families = getAllModelFamilies(provider.id); const sortedFamilies = sortModelFamilies(families); group.modelFamilies = []; // Add predefined families first for (const family of sortedFamilies) { const modelNames = modelGroups.get(family.id) ?? []; if (modelNames.length === 0) continue; // Filter to selected models only, OR show all if nothing selected const selectedModelNames = hasExplicitSelection ? modelNames.filter((m: string) => providerSelectedModels.includes(m)) : modelNames; if (selectedModelNames.length === 0) continue; const familyModels: Array<[string, UsageWindow]> = []; for (const modelName of selectedModelNames) { const modelUsage = models[modelName] as { windows?: Record } | undefined; if (modelUsage?.windows) { const windowEntries = Object.entries(modelUsage.windows); if (windowEntries.length > 0) { familyModels.push([modelName, windowEntries[0][1]]); } } } if (familyModels.length > 0) { group.modelFamilies.push({ familyId: family.id, familyLabel: family.label, models: familyModels, }); } } // Add "Other" family for remaining models const otherModelNames = modelGroups.get(null) ?? []; const selectedOtherModels = hasExplicitSelection ? otherModelNames.filter((m: string) => providerSelectedModels.includes(m)) : otherModelNames; if (selectedOtherModels.length > 0) { const otherModels: Array<[string, UsageWindow]> = []; for (const modelName of selectedOtherModels) { const modelUsage = models[modelName] as { windows?: Record } | undefined; if (modelUsage?.windows) { const windowEntries = Object.entries(modelUsage.windows); if (windowEntries.length > 0) { otherModels.push([modelName, windowEntries[0][1]]); } } } if (otherModels.length > 0) { group.modelFamilies.push({ familyId: null, familyLabel: t('header.services.modelFamily.other'), models: otherModels, }); } } } if (entries.length > 0 || (group.modelFamilies && group.modelFamilies.length > 0) || group.error) { groups.push(group); } } return groups; }, [dropdownProviderIds, quotaResults, selectedModels, t]); const hasRateLimits = rateLimitGroups.length > 0; React.useEffect(() => { void loadQuotaSettings(); }, [loadQuotaSettings]); const handleDisplayModeChange = React.useCallback(async (mode: 'usage' | 'remaining') => { setQuotaDisplayMode(mode); try { await updateDesktopSettings({ usageDisplayMode: mode }); } catch (error) { console.warn('Failed to update usage display mode:', error); } }, [setQuotaDisplayMode]); const handleUsageRefresh = React.useCallback(() => { if (isUsageRefreshSpinning) return; setIsUsageRefreshSpinning(true); const minSpinPromise = new Promise(resolve => setTimeout(resolve, 500)); Promise.all([fetchAllQuotas(), minSpinPromise]).finally(() => { setIsUsageRefreshSpinning(false); }); }, [fetchAllQuotas, isUsageRefreshSpinning]); const currentSessionLive = React.useMemo(() => { if (!currentSessionId) return null; return liveSessions.find((s) => s.id === currentSessionId) ?? globalActiveSessions.find((s) => s.id === currentSessionId) ?? currentSyncedSession ?? getAllSyncSessions().find((s) => s.id === currentSessionId) ?? null; }, [currentSessionId, currentSyncedSession, globalActiveSessions, liveSessions]); const lastResolvedSessionRef = React.useRef<{ sessionId: string; session: Session; expiresAt: number; } | null>(null); const [sessionFallbackVersion, setSessionFallbackVersion] = React.useState(0); React.useEffect(() => { if (!currentSessionId) { if (lastResolvedSessionRef.current) { lastResolvedSessionRef.current = null; setSessionFallbackVersion((value) => value + 1); } return; } if (currentSessionLive) { lastResolvedSessionRef.current = { sessionId: currentSessionId, session: currentSessionLive, expiresAt: Date.now() + 2000, }; return; } const cached = lastResolvedSessionRef.current; if (!cached || cached.sessionId !== currentSessionId) { return; } const remainingMs = cached.expiresAt - Date.now(); if (remainingMs <= 0) { lastResolvedSessionRef.current = null; setSessionFallbackVersion((value) => value + 1); return; } const timeoutId = window.setTimeout(() => { if (lastResolvedSessionRef.current?.sessionId === currentSessionId) { lastResolvedSessionRef.current = null; } setSessionFallbackVersion((value) => value + 1); }, remainingMs); return () => { window.clearTimeout(timeoutId); }; }, [currentSessionId, currentSessionLive]); void sessionFallbackVersion; const currentSession = (() => { if (currentSessionLive) { return currentSessionLive; } if (!currentSessionId) { return null; } const cached = lastResolvedSessionRef.current; if (cached && cached.sessionId === currentSessionId && cached.expiresAt > Date.now()) { return cached.session; } return null; })(); const worktreePath = useSessionUIStore((state) => { if (!currentSessionId) return ''; return state.worktreeMetadata.get(currentSessionId)?.path ?? ''; }); const currentSessionWorktreeBranch = useSessionUIStore((state) => { if (!currentSessionId) return null; return state.worktreeMetadata.get(currentSessionId)?.branch?.trim() ?? null; }); // Authoritative session↔worktree attachment from session-worktree-store const worktreeAttachment = useSessionWorktreeStore((state) => currentSessionId ? state.getAttachment(currentSessionId) : undefined ); const worktreeBadge = React.useMemo(() => { if (!worktreeAttachment) return null; return formatSessionWorktreeBadge(worktreeAttachment); }, [worktreeAttachment]); const worktreeBadgeKind = React.useMemo(() => { if (!worktreeAttachment) return null; if (worktreeAttachment.legacy) return 'legacy'; if (worktreeAttachment.degraded) return 'degraded'; if (worktreeAttachment.worktreeStatus === 'missing') return 'missing'; if (worktreeAttachment.worktreeStatus === 'invalid') return 'invalid'; if (worktreeAttachment.attentionReason) return 'attention'; return null; }, [worktreeAttachment]); const worktreeDirectory = React.useMemo(() => { return normalize(worktreePath || ''); }, [worktreePath]); const sessionDirectory = React.useMemo(() => { const raw = typeof currentSession?.directory === 'string' ? currentSession.directory : ''; return normalize(raw || ''); }, [currentSession?.directory]); const draftDirectory = useSessionUIStore((state) => { if (!state.newSessionDraft?.open) { return ''; } return normalize(state.newSessionDraft.bootstrapPendingDirectory ?? state.newSessionDraft.directoryOverride ?? ''); }); const openDirectory = React.useMemo(() => { return worktreeDirectory || sessionDirectory || draftDirectory; }, [draftDirectory, sessionDirectory, worktreeDirectory]); const catalogWorktreeBranch = useSessionUIStore((state) => { const candidateDirectory = normalize(worktreeDirectory || sessionDirectory || ''); if (!candidateDirectory) { return null; } for (const worktrees of state.availableWorktreesByProject.values()) { const match = worktrees.find((worktree) => normalize(worktree.path) === candidateDirectory); const branch = match?.branch?.trim(); if (branch) { return branch; } } return null; }); const gitBranchForDirectory = useGitBranchLabel(openDirectory || null); const currentBranchLabel = gitBranchForDirectory || currentSessionWorktreeBranch || catalogWorktreeBranch; const currentSessionTitle = React.useMemo(() => { if (!currentSessionId) { return activeProjectLabel ?? 'OpenChamber'; } const trimmedTitle = currentSession?.title?.trim(); return trimmedTitle && trimmedTitle.length > 0 ? trimmedTitle : 'Untitled Session'; }, [activeProjectLabel, currentSession?.title, currentSessionId]); const currentSessionDiffStats = React.useMemo(() => { return resolveSessionDiffStats(currentSession?.summary as Parameters[0]); }, [currentSession?.summary]); const currentSessionChanges = React.useMemo(() => { if (currentSessionDiffStats) { return currentSessionDiffStats; } return { additions: 0, deletions: 0 }; }, [currentSessionDiffStats]); const hasNonZeroSessionChanges = currentSessionChanges.additions > 0 || currentSessionChanges.deletions > 0; const actionDirectory = React.useMemo(() => { return normalize(openDirectory || activeProject?.path || ''); }, [activeProject?.path, openDirectory]); const activeProjectRef = React.useMemo(() => { if (!activeProject) { return null; } return { id: activeProject.id, path: activeProject.path }; }, [activeProject]); const lastProjectActionsContextRef = React.useRef<{ projectRef: { id: string; path: string }; directory: string; } | null>(null); React.useEffect(() => { if (!activeProjectRef || !actionDirectory) { return; } lastProjectActionsContextRef.current = { projectRef: activeProjectRef, directory: actionDirectory, }; }, [actionDirectory, activeProjectRef]); const projectActionsContext = React.useMemo(() => { if (activeProjectRef && actionDirectory) { return { projectRef: activeProjectRef, directory: actionDirectory }; } return lastProjectActionsContextRef.current; }, [actionDirectory, activeProjectRef]); const planModeEnabled = useFeatureFlagsStore((state) => state.planModeEnabled); const isSessionPlanAvailable = useSessionUIStore((state) => state.isSessionPlanAvailable); const planTabAvailable = planModeEnabled && currentSessionId ? isSessionPlanAvailable(currentSessionId) : false; const showPlanTab = planTabAvailable; const lastPlanSessionKeyRef = React.useRef(''); // Reset plan tab availability when session changes React.useEffect(() => { if (!planModeEnabled) { if (useUIStore.getState().activeMainTab === 'plan') { useUIStore.getState().setActiveMainTab('chat'); } return; } if (!currentSessionId) return; const sessionKey = `${currentSessionId || 'none'}:${sessionDirectory || 'none'}:${currentSession?.time?.created || 0}:${currentSession?.slug || 'none'}`; if (lastPlanSessionKeyRef.current !== sessionKey) { lastPlanSessionKeyRef.current = sessionKey; } // If plan is not available but user is on plan tab, switch them back to chat if (!planTabAvailable && useUIStore.getState().activeMainTab === 'plan') { useUIStore.getState().setActiveMainTab('chat'); } }, [ planModeEnabled, planTabAvailable, currentSession?.slug, currentSession?.time?.created, currentSessionId, sessionDirectory, ]); const handleGitHubAccountSwitch = React.useCallback(async (accountId: string) => { if (!accountId || isSwitchingGitHubAccount) return; setIsSwitchingGitHubAccount(true); try { const payload = runtimeApis.github ? await runtimeApis.github.authActivate(accountId) : await (async () => { const response = await fetch('/api/github/auth/activate', { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json', }, body: JSON.stringify({ accountId }), }); const body = (await response.json().catch(() => null)) as | (GitHubAuthStatus & { error?: string }) | null; if (!response.ok || !body) { throw new Error(body?.error || response.statusText); } return body; })(); setGitHubAuthStatus(payload); } catch (error) { console.error('Failed to switch GitHub account:', error); } finally { setIsSwitchingGitHubAccount(false); } }, [isSwitchingGitHubAccount, runtimeApis.github, setGitHubAuthStatus]); const blurActiveElement = React.useCallback(() => { if (typeof document === 'undefined') { return; } const active = document.activeElement as HTMLElement | null; if (!active) { return; } const tagName = active.tagName; const isInput = tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'SELECT'; if (isInput || active.isContentEditable) { active.blur(); } }, []); const handleOpenSessionSwitcher = React.useCallback(() => { if (isMobile) { blurActiveElement(); setSessionSwitcherOpen(!isSessionSwitcherOpen); return; } toggleSidebar(); }, [blurActiveElement, isMobile, isSessionSwitcherOpen, setSessionSwitcherOpen, toggleSidebar]); const handleHeaderNewSession = React.useCallback(() => { setActiveMainTab('chat'); setSessionSwitcherOpen(false); openNewSessionDraft(); }, [openNewSessionDraft, setActiveMainTab, setSessionSwitcherOpen]); const handleOpenContextPanel = React.useCallback(() => { const directory = normalize(openDirectory || ''); if (!directory) { return; } const panelState = contextPanelByDirectory[directory]; if (getActiveContextMode(panelState) === 'context') { closeContextPanel(directory); return; } openContextOverview(directory); }, [closeContextPanel, contextPanelByDirectory, openContextOverview, openDirectory]); const isContextPanelActive = React.useMemo(() => { const directory = normalize(openDirectory || ''); if (!directory) { return false; } const panelState = contextPanelByDirectory[directory]; return getActiveContextMode(panelState) === 'context'; }, [contextPanelByDirectory, openDirectory]); const handleOpenContextPlan = React.useCallback(() => { const directory = normalize(openDirectory || ''); if (!directory) { return; } const panelState = contextPanelByDirectory[directory]; if (getActiveContextMode(panelState) === 'plan') { closeContextPanel(directory); return; } openContextPlan(directory); }, [closeContextPanel, contextPanelByDirectory, openContextPlan, openDirectory]); const isContextPlanActive = React.useMemo(() => { const directory = normalize(openDirectory || ''); if (!directory) { return false; } const panelState = contextPanelByDirectory[directory]; return getActiveContextMode(panelState) === 'plan'; }, [contextPanelByDirectory, openDirectory]); const desktopHeaderIconButtonClass = DESKTOP_HEADER_ICON_BUTTON_CLASS; const mobileHeaderIconButtonClass = MOBILE_HEADER_ICON_BUTTON_CLASS; const desktopPaddingClass = React.useMemo(() => { if (!isSidebarOpen && isDesktopApp && isMacPlatform && !isDesktopWindowFullscreen) { return 'pl-[5.5rem]'; } return 'pl-3'; }, [isDesktopApp, isDesktopWindowFullscreen, isMacPlatform, isSidebarOpen]); useEffect(() => { if (!isDesktopApp || !isMacPlatform) { setIsDesktopWindowFullscreen(false); return; } let disposed = false; let unlistenResize: (() => void) | null = null; const syncFullscreenState = async () => { try { const { getCurrentWindow } = await import('@tauri-apps/api/window'); const currentWindow = getCurrentWindow(); const fullscreen = await currentWindow.isFullscreen(); if (!disposed) { setIsDesktopWindowFullscreen(fullscreen); } } catch { if (!disposed) { setIsDesktopWindowFullscreen(false); } } }; const attach = async () => { try { const { getCurrentWindow } = await import('@tauri-apps/api/window'); const currentWindow = getCurrentWindow(); unlistenResize = await currentWindow.onResized(() => { void syncFullscreenState(); }); } catch { // Ignore listener setup failures; fallback state remains false. } }; void syncFullscreenState(); void attach(); return () => { disposed = true; if (unlistenResize) { unlistenResize(); } }; }, [isDesktopApp, isMacPlatform]); const macosHeaderSizeClass = React.useMemo(() => { if (!isDesktopApp || !isMacPlatform || macosMajorVersion === null) { return ''; } if (macosMajorVersion >= 26) { return 'h-12'; } if (macosMajorVersion <= 15) { return 'h-14'; } return ''; }, [isDesktopApp, isMacPlatform, macosMajorVersion]); const webWindowControlsOverlayStyle = React.useMemo(() => { if (isDesktopApp || isVSCode) { return undefined; } return { paddingLeft: 'calc(0.75rem + var(--oc-wco-left-inset, 0px))', paddingRight: 'calc(0.75rem + var(--oc-wco-right-inset, 0px))', minHeight: 'max(3rem, var(--oc-wco-titlebar-height, 0px))', height: 'max(3rem, var(--oc-wco-titlebar-height, 0px))', }; }, [isDesktopApp, isVSCode]); const updateHeaderHeight = React.useCallback(() => { if (typeof document === 'undefined') { return; } const height = headerRef.current?.getBoundingClientRect().height; if (height) { document.documentElement.style.setProperty('--oc-header-height', `${height}px`); } }, []); useEffect(() => { if (typeof window === 'undefined') { return; } updateHeaderHeight(); const node = headerRef.current; if (!node || typeof ResizeObserver === 'undefined') { return () => { }; } let rafId = 0; const scheduleUpdate = () => { if (rafId) return; rafId = requestAnimationFrame(() => { rafId = 0; updateHeaderHeight(); }); }; const observer = new ResizeObserver(scheduleUpdate); observer.observe(node); window.addEventListener('resize', scheduleUpdate); window.addEventListener('orientationchange', scheduleUpdate); return () => { if (rafId) cancelAnimationFrame(rafId); observer.disconnect(); window.removeEventListener('resize', scheduleUpdate); window.removeEventListener('orientationchange', scheduleUpdate); }; }, [updateHeaderHeight]); useEffect(() => { updateHeaderHeight(); }, [updateHeaderHeight, isMobile, macosHeaderSizeClass]); const handleDragStart = React.useCallback(async (e: React.MouseEvent) => { const target = e.target as HTMLElement; if (target.closest('.app-region-no-drag')) { return; } if (target.closest('button, a, input, select, textarea')) { return; } if (e.button !== 0) { return; } if (isDesktopApp) { await startDesktopWindowDrag(); } }, [isDesktopApp]); const tabs: TabConfig[] = React.useMemo(() => { if (isMobile) { const base: TabConfig[] = [ { id: 'chat', label: t('layout.mainTab.chat'), icon: RiChat4Line }, ]; if (showPlanTab) { base.push({ id: 'plan', label: t('layout.mainTab.plan'), icon: RiFileTextLine }); } base.push( { id: 'diff', label: t('layout.mainTab.diff'), icon: 'diff' }, { id: 'files', label: t('layout.mainTab.files'), icon: RiFolder6Line }, { id: 'terminal', label: t('layout.mainTab.terminal'), icon: RiTerminalBoxLine }, ); return base; } // Desktop: no tabs in header return []; }, [isMobile, showPlanTab, t]); const shortcutLabel = React.useCallback((actionId: string) => { return formatShortcutForDisplay(getEffectiveShortcutCombo(actionId, shortcutOverrides)); }, [shortcutOverrides]); useEffect(() => { if (!isMobile && (activeMainTab === 'git' || activeMainTab === 'terminal' || activeMainTab === 'diff' || activeMainTab === 'files')) { setActiveMainTab('chat'); } }, [activeMainTab, isMobile, setActiveMainTab]); const servicesTabs = React.useMemo(() => { const base: Array<{ value: 'instance' | 'usage' | 'mcp'; label: string; icon: RemixiconComponentType }> = []; if (isDesktopApp) { base.push({ value: 'instance', label: t('layout.services.instance'), icon: RiServerLine }); } base.push( { value: 'usage', label: t('layout.services.usage'), icon: RiTimerLine }, { value: 'mcp', label: 'MCP', icon: McpIcon as unknown as RemixiconComponentType } ); return base; }, [isDesktopApp, t]); const servicesTabItems = React.useMemo(() => { return servicesTabs.map((tab) => ({ id: tab.value, label: tab.label, icon: , })); }, [servicesTabs]); const showDevShutdown = React.useMemo(() => { if (typeof window === 'undefined') return false; if (isDesktopApp) return false; if (isVSCode) return false; const host = window.location.hostname; return host === 'localhost' || host === '127.0.0.1' || host === '::1'; }, [isDesktopApp, isVSCode]); const handleDevShutdown = React.useCallback(async () => { if (isDevShutdownInFlight) return; setIsDevShutdownInFlight(true); setIsDesktopServicesOpen(false); const previewUrls: string[] = []; let shutdownRequested = false; try { try { for (const [, dirState] of useTerminalStore.getState().sessions.entries()) { for (const tab of dirState.tabs) { if (tab.previewUrl) { previewUrls.push(tab.previewUrl); } } } } catch { // ignore } try { // Ensure preview/dev terminals don't linger. await forceKillTerminal({}); } catch { // ignore } try { const devRes = await fetch('/api/system/dev-shutdown', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ previewUrls }), }); if (devRes.ok) { shutdownRequested = true; } else { const shutdownRes = await fetch('/api/system/shutdown', { method: 'POST' }); shutdownRequested = shutdownRes.ok; } } catch { // ignore } } finally { if (!shutdownRequested) { setIsDevShutdownInFlight(false); } } }, [isDevShutdownInFlight, setIsDesktopServicesOpen]); const quotaDisplayTabs = React.useMemo(() => { return [ { value: 'usage' as const, label: t('header.services.used') }, { value: 'remaining' as const, label: t('header.services.remaining') }, ]; }, [t]); const quotaDisplayTabItems = React.useMemo(() => { return quotaDisplayTabs.map((tab) => ({ id: tab.value, label: tab.label })); }, [quotaDisplayTabs]); const mobileServicesTabItems = React.useMemo(() => { return [ { id: 'usage', label: t('layout.services.usage'), icon: }, { id: 'mcp', label: 'MCP', icon: }, ]; }, [t]); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (hasModifier(e) && !e.shiftKey && !e.altKey) { const num = parseInt(e.key, 10); if (num >= 1 && num <= tabs.length) { e.preventDefault(); setActiveMainTab(tabs[num - 1].id); } } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [tabs, setActiveMainTab]); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { const toggleServicesCombo = getEffectiveShortcutCombo('toggle_services_menu', shortcutOverrides); if (eventMatchesShortcut(e, toggleServicesCombo)) { e.preventDefault(); if (isDesktopServicesOpen) { setIsDesktopServicesOpen(false); } else { setIsDesktopServicesOpen(true); void refreshCurrentInstanceLabel(); if (desktopServicesTab === 'usage' && quotaResults.length === 0) { void fetchAllQuotas(); } } return; } const cycleServicesCombo = getEffectiveShortcutCombo('cycle_services_tab', shortcutOverrides); if (eventMatchesShortcut(e, cycleServicesCombo)) { e.preventDefault(); const tabValues = servicesTabs.map((tab) => tab.value) as Array<'instance' | 'usage' | 'mcp'>; if (tabValues.length === 0) { return; } const currentIndex = tabValues.indexOf(desktopServicesTab); const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % tabValues.length; const nextTab = tabValues[nextIndex]; setDesktopServicesTab(nextTab); setIsDesktopServicesOpen(true); void refreshCurrentInstanceLabel(); if (nextTab === 'usage' && quotaResults.length === 0) { void fetchAllQuotas(); } return; } const toggleContextPlanCombo = getEffectiveShortcutCombo('toggle_context_plan', shortcutOverrides); if (eventMatchesShortcut(e, toggleContextPlanCombo)) { e.preventDefault(); handleOpenContextPlan(); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [ shortcutOverrides, isDesktopServicesOpen, desktopServicesTab, servicesTabs, quotaResults.length, fetchAllQuotas, refreshCurrentInstanceLabel, handleOpenContextPlan, ]); const renderTab = (tab: TabConfig) => { const isActive = activeMainTab === tab.id; const isDiffTab = tab.icon === 'diff'; const Icon = isDiffTab ? null : (tab.icon as RemixiconComponentType); const isChatTab = tab.id === 'chat'; const renderIcon = (iconSize: number) => { if (isDiffTab) { return ; } return Icon ? : null; }; const tabButton = ( ); return {tabButton}; }; const desktopSidebarActions = ( <> {showPlanTab && (

{t('header.actions.planWithShortcut', { shortcut: shortcutLabel('toggle_context_plan') })}

)} ); const desktopSidebarActionsInline = !isRightSidebarOpen || !desktopRightSidebarActionsHost; const renderDesktop = () => (
{!isLeftSidebarOpen ? (

{t('header.actions.newSessionWithShortcut', { shortcut: shortcutLabel('new_chat') })}

) : null} {projectActionsContext && ( )} {!isNewSessionDraftOpen ? (
{currentSessionTitle}
{(activeProjectLabel || currentBranchLabel || hasNonZeroSessionChanges) ? (
{activeProjectLabel ? {activeProjectLabel} : null} {currentBranchLabel ? ( {currentBranchLabel} ) : null} {hasNonZeroSessionChanges ? ( +{currentSessionChanges.additions} / -{currentSessionChanges.deletions} ) : null} {worktreeBadgeKind ? ( {worktreeBadge} ) : null}
) : null}
) : null} {tabs.length > 0 && (
{tabs.map((tab) => renderTab(tab))}
)}
{showDesktopHeaderContextUsage && stableDesktopContextUsage ? ( ) : null} {desktopSidebarActionsInline ? desktopSidebarActions : null} {!desktopSidebarActionsInline && desktopRightSidebarActionsHost ? createPortal(desktopSidebarActions, desktopRightSidebarActionsHost) : null}
); const renderMobile = () => (
{/* Use drawer toggle when onToggleLeftDrawer is provided, otherwise use legacy session switcher */} {onToggleLeftDrawer ? ( ) : isSessionSwitcherOpen ? ( ) : ( )} {isSessionSwitcherOpen && ( {t('header.sessions.title')} )}
{/* Hide tabs and right-side buttons when sessions sidebar is open */} {!isSessionSwitcherOpen && ( <>
{tabs.map((tab) => { const isActive = activeMainTab === tab.id; const isDiffTab = tab.icon === 'diff'; const Icon = isDiffTab ? null : (tab.icon as RemixiconComponentType); return (

{tab.label}

); })}
{projectActionsContext && ( )} {/* Mobile Services Menu (Usage + MCP) */} { setIsMobileRateLimitsOpen(open); if (open && quotaResults.length === 0) { fetchAllQuotas(); } }} >

{t('header.services.title')}

{ const value = tabID as 'usage' | 'mcp'; setMobileServicesTab(value); if (value === 'usage' && quotaResults.length === 0) { fetchAllQuotas(); } }} layoutMode="fit" variant="active-pill" activePillInsetClassName="gap-0.5 px-px py-0" activePillButtonClassName="h-8" className="h-full" />
{mobileServicesTab === 'mcp' && ( )} {mobileServicesTab === 'usage' && (
{/* Mobile usage header */}
{t('header.services.rateLimits')} {formatTime(quotaLastUpdated)}
·
{!hasRateLimits && (
{t('header.services.noRateLimits')}
)} {/* Mobile provider groups */}
{rateLimitGroups.map((group, index) => ( {index > 0 ? (
) : null} {/* Provider header */}
{group.providerName}
{group.entries.length === 0 && (!group.modelFamilies || group.modelFamilies.length === 0) ? (
{group.error ?? t('header.services.noRateLimitsReported')}
) : (
{/* Window-level entries */} {group.entries.map(([label, window]) => { const displayPercent = quotaDisplayMode === 'remaining' ? window.remainingPercent : window.usedPercent; const paceInfo = calculatePace(window.usedPercent, window.resetAt, window.windowSeconds, label); const expectedMarker = paceInfo?.dailyAllocationPercent != null ? (quotaDisplayMode === 'remaining' ? 100 - calculateExpectedUsagePercent(paceInfo.elapsedRatio) : calculateExpectedUsagePercent(paceInfo.elapsedRatio)) : null; return (
{formatWindowLabel(label)} {(window.resetAfterFormatted ?? window.resetAtFormatted) ? ( {window.resetAfterFormatted ?? window.resetAtFormatted} ) : null}
{formatPercent(displayPercent) === '-' ? '' : formatPercent(displayPercent)}
{paceInfo ? ( ) : null}
); })} {/* Model family collapsibles */} {group.modelFamilies && group.modelFamilies.length > 0 && (
{group.modelFamilies.map((family) => { const providerExpandedFamilies = expandedFamilies[group.providerId] ?? []; const isExpanded = providerExpandedFamilies.includes(family.familyId ?? 'other'); return ( toggleFamilyExpanded(group.providerId, family.familyId ?? 'other')} > {family.familyLabel} {isExpanded ? ( ) : ( )}
{family.models.map(([modelName, window]) => { const displayPercent = quotaDisplayMode === 'remaining' ? window.remainingPercent : window.usedPercent; const paceInfo = calculatePace(window.usedPercent, window.resetAt, window.windowSeconds); const expectedMarker = paceInfo?.dailyAllocationPercent != null ? (quotaDisplayMode === 'remaining' ? 100 - calculateExpectedUsagePercent(paceInfo.elapsedRatio) : calculateExpectedUsagePercent(paceInfo.elapsedRatio)) : null; return (
{getDisplayModelName(modelName)} {formatPercent(displayPercent) === '-' ? '' : formatPercent(displayPercent)}
{paceInfo ? ( ) : null}
); })}
); })}
)}
)} ))}
)}
{onToggleRightDrawer ? (

{rightDrawerOpen ? 'Close git sidebar' : 'Open git sidebar'}

) : null}
)}
); const headerClassName = cn( 'header-safe-area relative z-10', isMobile && 'border-b border-border/50', 'bg-background' ); return (
{isMobile ? renderMobile() : renderDesktop()}
); };