feat: extend quota providers and add usage dropdown (#254) (#284)

* feat: extend quota providers and add usage dropdown

Add new quota providers:
- Claude (Anthropic API)
- Codex (OpenAI/ChatGPT)
- GitHub Copilot (base + add-on)
- Kimi for Coding
- OpenRouter

UI enhancements:
- Add rate limits dropdown in header with timer icon
- Desktop: sticky header with Used/Remaining toggle, refresh button
- Mobile: full-screen dropdown with same controls
- Per-provider sticky headers with provider logos
- Auto-refresh on open if no data exists

Usage settings page:
- Provider icon next to header
- Show in dropdown toggle per provider
- Global display selector (Usage vs Quota remaining)
- Auto-refresh controls in sidebar

Settings persistence:
- Add usageDisplayMode and usageDropdownProviders settings
- Server-side sanitization for new settings

Provider logo aliases:
- codex -> openai, claude -> anthropic

Bug fixes:
- Fix React error #310 by calling hooks before early returns
- Add aria-describedby to SettingsWindow dialog
- Remove horizontal scroll from dropdown

* fix: Remove duplicate github copilot declarations
This commit is contained in:
gsxdsm
2026-02-04 11:13:23 +02:00
committed by GitHub
parent 2785f4d9ad
commit a264393f1b
16 changed files with 1600 additions and 319 deletions
+11 -5
View File
@@ -15,6 +15,11 @@ const localLogoModules = import.meta.glob<string>('../assets/provider-logos/*.sv
const LOCAL_PROVIDER_LOGO_MAP = new Map<string, string>();
const LOGO_ALIAS = new Map<string, string>([
['codex', 'openai'],
['claude', 'anthropic'],
]);
for (const [path, url] of Object.entries(localLogoModules)) {
const match = path.match(/provider-logos\/([^/]+)\.svg$/i);
if (match?.[1] && url) {
@@ -24,20 +29,21 @@ for (const [path, url] of Object.entries(localLogoModules)) {
export function useProviderLogo(providerId: string | null | undefined): UseProviderLogoReturn {
const normalizedId = providerId?.toLowerCase() ?? null;
const hasLocalLogo = normalizedId ? LOCAL_PROVIDER_LOGO_MAP.has(normalizedId) : false;
const localLogoSrc = normalizedId ? LOCAL_PROVIDER_LOGO_MAP.get(normalizedId) ?? null : null;
const resolvedId = normalizedId ? LOGO_ALIAS.get(normalizedId) ?? normalizedId : null;
const hasLocalLogo = resolvedId ? LOCAL_PROVIDER_LOGO_MAP.has(resolvedId) : false;
const localLogoSrc = resolvedId ? LOCAL_PROVIDER_LOGO_MAP.get(resolvedId) ?? null : null;
const [source, setSource] = useState<LogoSource>(hasLocalLogo ? 'local' : 'remote');
useEffect(() => {
setSource(hasLocalLogo ? 'local' : 'remote');
}, [hasLocalLogo, normalizedId]);
}, [hasLocalLogo, resolvedId]);
const handleError = useCallback(() => {
setSource((current) => (current === 'local' && hasLocalLogo ? 'remote' : 'none'));
}, [hasLocalLogo]);
if (!normalizedId) {
if (!resolvedId) {
return { src: null, onError: handleError, hasLogo: false };
}
@@ -51,7 +57,7 @@ export function useProviderLogo(providerId: string | null | undefined): UseProvi
if (source === 'remote') {
return {
src: `https://models.dev/logos/${normalizedId}.svg`,
src: `https://models.dev/logos/${resolvedId}.svg`,
onError: handleError,
hasLogo: true,
};