mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-03-18 10:23:07 +00:00
Feat: Show Gemini Usage in usage dropdown and mobile sidebar
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
type ClaudeAuthMethod,
|
||||
type CodexAuthMethod,
|
||||
type ZaiAuthMethod,
|
||||
type GeminiAuthMethod,
|
||||
} from '@/store/setup-store';
|
||||
import { getHttpApiClient } from '@/lib/http-api-client';
|
||||
import { createLogger } from '@automaker/utils/logger';
|
||||
@@ -11,7 +12,7 @@ import { createLogger } from '@automaker/utils/logger';
|
||||
const logger = createLogger('ProviderAuthInit');
|
||||
|
||||
/**
|
||||
* Hook to initialize Claude, Codex, and z.ai authentication statuses on app startup.
|
||||
* Hook to initialize Claude, Codex, z.ai, and Gemini authentication statuses on app startup.
|
||||
* This ensures that usage tracking information is available in the board header
|
||||
* without needing to visit the settings page first.
|
||||
*/
|
||||
@@ -20,9 +21,12 @@ export function useProviderAuthInit() {
|
||||
setClaudeAuthStatus,
|
||||
setCodexAuthStatus,
|
||||
setZaiAuthStatus,
|
||||
setGeminiCliStatus,
|
||||
setGeminiAuthStatus,
|
||||
claudeAuthStatus,
|
||||
codexAuthStatus,
|
||||
zaiAuthStatus,
|
||||
geminiAuthStatus,
|
||||
} = useSetupStore();
|
||||
const initialized = useRef(false);
|
||||
|
||||
@@ -121,18 +125,74 @@ export function useProviderAuthInit() {
|
||||
} catch (error) {
|
||||
logger.error('Failed to init z.ai auth status:', error);
|
||||
}
|
||||
}, [setClaudeAuthStatus, setCodexAuthStatus, setZaiAuthStatus]);
|
||||
|
||||
// 4. Gemini Auth Status
|
||||
try {
|
||||
const result = await api.setup.getGeminiStatus();
|
||||
if (result.success) {
|
||||
// Set CLI status
|
||||
setGeminiCliStatus({
|
||||
installed: result.installed ?? false,
|
||||
version: result.version,
|
||||
path: result.status,
|
||||
});
|
||||
|
||||
// Set Auth status - always set a status to mark initialization as complete
|
||||
if (result.auth) {
|
||||
const auth = result.auth;
|
||||
const validMethods: GeminiAuthMethod[] = ['cli_login', 'api_key_env', 'api_key', 'none'];
|
||||
|
||||
const method = validMethods.includes(auth.method as GeminiAuthMethod)
|
||||
? (auth.method as GeminiAuthMethod)
|
||||
: ((auth.authenticated ? 'cli_login' : 'none') as GeminiAuthMethod);
|
||||
|
||||
setGeminiAuthStatus({
|
||||
authenticated: auth.authenticated,
|
||||
method,
|
||||
hasApiKey: auth.hasApiKey ?? false,
|
||||
hasEnvApiKey: auth.hasEnvApiKey ?? false,
|
||||
});
|
||||
} else {
|
||||
// No auth info available, set default unauthenticated status
|
||||
setGeminiAuthStatus({
|
||||
authenticated: false,
|
||||
method: 'none',
|
||||
hasApiKey: false,
|
||||
hasEnvApiKey: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to init Gemini auth status:', error);
|
||||
// Set default status on error to prevent infinite retries
|
||||
setGeminiAuthStatus({
|
||||
authenticated: false,
|
||||
method: 'none',
|
||||
hasApiKey: false,
|
||||
hasEnvApiKey: false,
|
||||
});
|
||||
}
|
||||
}, [
|
||||
setClaudeAuthStatus,
|
||||
setCodexAuthStatus,
|
||||
setZaiAuthStatus,
|
||||
setGeminiCliStatus,
|
||||
setGeminiAuthStatus,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
// Only initialize once per session if not already set
|
||||
if (
|
||||
initialized.current ||
|
||||
(claudeAuthStatus !== null && codexAuthStatus !== null && zaiAuthStatus !== null)
|
||||
(claudeAuthStatus !== null &&
|
||||
codexAuthStatus !== null &&
|
||||
zaiAuthStatus !== null &&
|
||||
geminiAuthStatus !== null)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
initialized.current = true;
|
||||
|
||||
void refreshStatuses();
|
||||
}, [refreshStatuses, claudeAuthStatus, codexAuthStatus, zaiAuthStatus]);
|
||||
}, [refreshStatuses, claudeAuthStatus, codexAuthStatus, zaiAuthStatus, geminiAuthStatus]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user