Spaces:
Paused
Paused
File size: 10,001 Bytes
4a940a5 bdd4755 4a940a5 b7d4394 bdd4755 d712fd9 bdd4755 d712fd9 b7d4394 06a5304 1b6fb15 06a5304 3cf831b 0c2aed2 f878eff 1b6fb15 91ee702 b7d4394 91ee702 b7d4394 f878eff b7d4394 0c2aed2 b7d4394 bdd4755 b7d4394 06a5304 1b6fb15 06a5304 bdd4755 06a5304 1b6fb15 06a5304 bdd4755 06a5304 3cf831b 1b6fb15 6220911 3cf831b 06a5304 3cf831b 06a5304 91ee702 b7d4394 1b6fb15 b7d4394 f878eff 0c2aed2 f878eff bdd4755 f878eff bdd4755 f878eff b7d4394 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | import { useI18n } from "../../../shared/i18n/context";
import { translations, type TranslationKey } from "../../../shared/i18n/translations";
import { useTheme } from "../../../shared/theme/context";
const SVG_MOON = (
<svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z" />
</svg>
);
const SVG_SUN = (
<svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z" />
</svg>
);
/**
* Stable-width text: two invisible references (en + zh) set min-width via grid overlap.
* The visible text overlays them, so the button never changes width on language switch.
*/
function StableText({ tKey, children, class: cls }: { tKey: TranslationKey; children: string; class?: string }) {
return (
<span class={`inline-grid ${cls ?? ""}`}>
<span class="invisible col-start-1 row-start-1 whitespace-nowrap">{translations.en[tKey]}</span>
<span class="invisible col-start-1 row-start-1 whitespace-nowrap">{translations.zh[tKey]}</span>
<span class="col-start-1 row-start-1 whitespace-nowrap">{children}</span>
</span>
);
}
interface HeaderProps {
onAddAccount: () => void;
onCheckUpdate: () => void;
onOpenUpdateModal?: () => void;
checking: boolean;
updateStatusMsg: string | null;
updateStatusColor: string;
version: string | null;
commit?: string | null;
isProxySettings?: boolean;
hasUpdate?: boolean;
onLogout?: () => void;
}
export function Header({ onAddAccount, onCheckUpdate, onOpenUpdateModal, checking, updateStatusMsg, updateStatusColor, version, commit, isProxySettings, hasUpdate, onLogout }: HeaderProps) {
const { lang, toggleLang, t } = useI18n();
const { isDark, toggle: toggleTheme } = useTheme();
return (
<header class="sticky top-0 z-50 w-full bg-white dark:bg-card-dark border-b border-gray-200 dark:border-border-dark shadow-sm transition-colors">
<div class="px-4 md:px-8 lg:px-40 flex h-14 items-center justify-center">
<div class="flex w-full max-w-[960px] items-center justify-between">
{/* Logo & Title */}
<div class="flex items-center gap-3">
{isProxySettings ? (
<a
href="#"
class="flex items-center gap-1.5 text-sm text-slate-500 dark:text-text-dim hover:text-primary transition-colors"
>
<svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18" />
</svg>
<span class="font-medium">{t("backToDashboard")}</span>
</a>
) : (
<>
<div class="flex items-center justify-center size-8 rounded-full bg-primary/10 text-primary border border-primary/20">
<svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h1 class="text-[0.9rem] font-bold tracking-tight">Codex Proxy</h1>
</>
)}
</div>
{/* Actions */}
<div class="flex items-center gap-2">
<div class="hidden sm:flex items-center gap-2 px-3 py-1.5 rounded-full bg-primary/10 border border-primary/20">
<span class="relative flex h-2.5 w-2.5">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75" />
<span class="relative inline-flex rounded-full h-2.5 w-2.5 bg-primary" />
</span>
<StableText tKey="serverOnline" class="text-xs font-semibold text-primary">{t("serverOnline")}</StableText>
</div>
{/* Star on GitHub */}
<a
href="https://github.com/icebear0828/codex-proxy"
target="_blank"
rel="noopener noreferrer"
class="hidden lg:flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-amber-50 border border-amber-200 text-amber-700 dark:bg-amber-900/20 dark:border-amber-700/30 dark:text-amber-400 hover:bg-amber-100 dark:hover:bg-amber-900/40 transition-colors"
>
<svg class="size-3.5" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg>
<StableText tKey="starOnGithub" class="text-xs font-semibold">{t("starOnGithub")}</StableText>
</a>
{/* Check for Updates */}
<button
onClick={onCheckUpdate}
disabled={checking}
class="hidden lg:flex items-center gap-1.5 px-3 py-1.5 rounded-full border border-gray-200 dark:border-border-dark text-slate-600 dark:text-text-dim hover:bg-slate-50 dark:hover:bg-border-dark disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
<svg class={`size-3.5 ${checking ? "animate-spin" : ""}`} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.992 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182M20.985 4.356v4.992" />
</svg>
<StableText tKey="checkForUpdates" class="text-xs font-semibold">{checking ? t("checkingUpdates") : t("checkForUpdates")}</StableText>
</button>
{/* Update status message */}
{updateStatusMsg && !checking && (
<button
onClick={hasUpdate && onOpenUpdateModal ? onOpenUpdateModal : onCheckUpdate}
class={`hidden lg:inline whitespace-nowrap text-xs font-medium ${updateStatusColor} hover:underline`}
>
{updateStatusMsg}
</button>
)}
{/* Logout (remote sessions only) */}
{onLogout && (
<button
onClick={onLogout}
class="flex items-center gap-1.5 px-3 py-1.5 rounded-full border border-gray-200 dark:border-border-dark text-slate-600 dark:text-text-dim hover:bg-slate-50 dark:hover:bg-border-dark transition-colors"
>
<svg class="size-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" />
</svg>
<span class="hidden sm:inline"><StableText tKey="dashboardLogout" class="text-xs font-semibold">{t("dashboardLogout")}</StableText></span>
</button>
)}
{/* Language Toggle */}
<button
onClick={toggleLang}
class="p-2 rounded-lg text-slate-500 dark:text-text-dim hover:bg-slate-100 dark:hover:bg-border-dark transition-colors"
title={"\u4e2d/EN"}
>
<span class="text-xs font-bold inline-flex items-center justify-center w-5">{lang === "en" ? "EN" : "\u4e2d"}</span>
</button>
{/* Theme Toggle */}
<button
onClick={toggleTheme}
class="p-2 rounded-lg text-slate-500 dark:text-text-dim hover:bg-slate-100 dark:hover:bg-border-dark transition-colors"
title={t("toggleTheme")}
>
{isDark ? SVG_SUN : SVG_MOON}
</button>
{/* Proxy Settings / Add Account */}
{isProxySettings ? null : (
<>
<a
href="#/proxy-settings"
class="hidden md:flex items-center gap-1.5 px-3 py-1.5 rounded-full border border-gray-200 dark:border-border-dark text-slate-600 dark:text-text-dim hover:bg-slate-50 dark:hover:bg-border-dark transition-colors"
>
<svg class="size-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 6h9.75M10.5 6a1.5 1.5 0 11-3 0m3 0a1.5 1.5 0 10-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-9.75 0h9.75" />
</svg>
<StableText tKey="proxySettings" class="text-xs font-semibold">{t("proxySettings")}</StableText>
</a>
<button
onClick={onAddAccount}
class="flex items-center gap-2 px-4 py-2 bg-primary hover:bg-primary-hover text-white text-xs font-semibold rounded-lg transition-colors shadow-sm active:scale-95"
>
<svg class="size-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
<StableText tKey="addAccount">{t("addAccount")}</StableText>
</button>
</>
)}
</div>
</div>
</div>
</header>
);
}
|