Feat: Show Gemini Usage in usage dropdown and mobile sidebar

This commit is contained in:
eclipxe
2026-01-25 09:44:03 -08:00
committed by gsxdsm
parent 7765a12868
commit 7d5bc722fa
17 changed files with 1374 additions and 61 deletions

View File

@@ -23,7 +23,7 @@ export {
} from './use-github';
// Usage
export { useClaudeUsage, useCodexUsage, useZaiUsage } from './use-usage';
export { useClaudeUsage, useCodexUsage, useZaiUsage, useGeminiUsage } from './use-usage';
// Running Agents
export { useRunningAgents, useRunningAgentsCount } from './use-running-agents';

View File

@@ -1,7 +1,7 @@
/**
* Usage Query Hooks
*
* React Query hooks for fetching Claude, Codex, and z.ai API usage data.
* React Query hooks for fetching Claude, Codex, z.ai, and Gemini API usage data.
* These hooks include automatic polling for real-time usage updates.
*/
@@ -9,7 +9,7 @@ import { useQuery } from '@tanstack/react-query';
import { getElectronAPI } from '@/lib/electron';
import { queryKeys } from '@/lib/query-keys';
import { STALE_TIMES } from '@/lib/query-client';
import type { ClaudeUsage, CodexUsage, ZaiUsage } from '@/store/app-store';
import type { ClaudeUsage, CodexUsage, ZaiUsage, GeminiUsage } from '@/store/app-store';
/** Polling interval for usage data (60 seconds) */
const USAGE_POLLING_INTERVAL = 60 * 1000;
@@ -33,7 +33,7 @@ export function useClaudeUsage(enabled = true) {
queryFn: async (): Promise<ClaudeUsage> => {
const api = getElectronAPI();
if (!api.claude) {
throw new Error('Claude API not available');
throw new Error('Claude API bridge unavailable');
}
const result = await api.claude.getUsage();
// Check if result is an error response
@@ -69,7 +69,7 @@ export function useCodexUsage(enabled = true) {
queryFn: async (): Promise<CodexUsage> => {
const api = getElectronAPI();
if (!api.codex) {
throw new Error('Codex API not available');
throw new Error('Codex API bridge unavailable');
}
const result = await api.codex.getUsage();
// Check if result is an error response
@@ -104,6 +104,9 @@ export function useZaiUsage(enabled = true) {
queryKey: queryKeys.usage.zai(),
queryFn: async (): Promise<ZaiUsage> => {
const api = getElectronAPI();
if (!api.zai) {
throw new Error('z.ai API bridge unavailable');
}
const result = await api.zai.getUsage();
// Check if result is an error response
if ('error' in result) {
@@ -120,3 +123,37 @@ export function useZaiUsage(enabled = true) {
refetchOnReconnect: USAGE_REFETCH_ON_RECONNECT,
});
}
/**
* Fetch Gemini API usage/status data
*
* @param enabled - Whether the query should run (default: true)
* @returns Query result with Gemini usage data
*
* @example
* ```tsx
* const { data: usage, isLoading } = useGeminiUsage(isPopoverOpen);
* ```
*/
export function useGeminiUsage(enabled = true) {
return useQuery({
queryKey: queryKeys.usage.gemini(),
queryFn: async (): Promise<GeminiUsage> => {
const api = getElectronAPI();
if (!api.gemini) {
throw new Error('Gemini API bridge unavailable');
}
const result = await api.gemini.getUsage();
// Server always returns a response with 'authenticated' field, even on error
// So we can safely cast to GeminiUsage
return result as GeminiUsage;
},
enabled,
staleTime: STALE_TIMES.USAGE,
refetchInterval: enabled ? USAGE_POLLING_INTERVAL : false,
// Keep previous data while refetching
placeholderData: (previousData) => previousData,
refetchOnWindowFocus: USAGE_REFETCH_ON_FOCUS,
refetchOnReconnect: USAGE_REFETCH_ON_RECONNECT,
});
}