Feat: Add z.ai usage tracking

This commit is contained in:
eclipxe
2026-01-20 14:34:15 -08:00
committed by gsxdsm
parent 1662c6bf0b
commit 7765a12868
23 changed files with 1331 additions and 55 deletions

View File

@@ -23,7 +23,7 @@ export {
} from './use-github';
// Usage
export { useClaudeUsage, useCodexUsage } from './use-usage';
export { useClaudeUsage, useCodexUsage, useZaiUsage } 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 and Codex API usage data.
* React Query hooks for fetching Claude, Codex, and z.ai 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 } from '@/store/app-store';
import type { ClaudeUsage, CodexUsage, ZaiUsage } from '@/store/app-store';
/** Polling interval for usage data (60 seconds) */
const USAGE_POLLING_INTERVAL = 60 * 1000;
@@ -87,3 +87,36 @@ export function useCodexUsage(enabled = true) {
refetchOnReconnect: USAGE_REFETCH_ON_RECONNECT,
});
}
/**
* Fetch z.ai API usage data
*
* @param enabled - Whether the query should run (default: true)
* @returns Query result with z.ai usage data
*
* @example
* ```tsx
* const { data: usage, isLoading } = useZaiUsage(isPopoverOpen);
* ```
*/
export function useZaiUsage(enabled = true) {
return useQuery({
queryKey: queryKeys.usage.zai(),
queryFn: async (): Promise<ZaiUsage> => {
const api = getElectronAPI();
const result = await api.zai.getUsage();
// Check if result is an error response
if ('error' in result) {
throw new Error(result.message || result.error);
}
return result;
},
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,
});
}

View File

@@ -1,18 +1,29 @@
import { useEffect, useRef, useCallback } from 'react';
import { useSetupStore, type ClaudeAuthMethod, type CodexAuthMethod } from '@/store/setup-store';
import {
useSetupStore,
type ClaudeAuthMethod,
type CodexAuthMethod,
type ZaiAuthMethod,
} from '@/store/setup-store';
import { getHttpApiClient } from '@/lib/http-api-client';
import { createLogger } from '@automaker/utils/logger';
const logger = createLogger('ProviderAuthInit');
/**
* Hook to initialize Claude and Codex authentication statuses on app startup.
* Hook to initialize Claude, Codex, and z.ai 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.
*/
export function useProviderAuthInit() {
const { setClaudeAuthStatus, setCodexAuthStatus, claudeAuthStatus, codexAuthStatus } =
useSetupStore();
const {
setClaudeAuthStatus,
setCodexAuthStatus,
setZaiAuthStatus,
claudeAuthStatus,
codexAuthStatus,
zaiAuthStatus,
} = useSetupStore();
const initialized = useRef(false);
const refreshStatuses = useCallback(async () => {
@@ -88,15 +99,40 @@ export function useProviderAuthInit() {
} catch (error) {
logger.error('Failed to init Codex auth status:', error);
}
}, [setClaudeAuthStatus, setCodexAuthStatus]);
// 3. z.ai Auth Status
try {
const result = await api.zai.getStatus();
if (result.success || result.available !== undefined) {
let method: ZaiAuthMethod = 'none';
if (result.hasEnvApiKey) {
method = 'api_key_env';
} else if (result.hasApiKey || result.available) {
method = 'api_key';
}
setZaiAuthStatus({
authenticated: result.available,
method,
hasApiKey: result.hasApiKey ?? result.available,
hasEnvApiKey: result.hasEnvApiKey ?? false,
});
}
} catch (error) {
logger.error('Failed to init z.ai auth status:', error);
}
}, [setClaudeAuthStatus, setCodexAuthStatus, setZaiAuthStatus]);
useEffect(() => {
// Only initialize once per session if not already set
if (initialized.current || (claudeAuthStatus !== null && codexAuthStatus !== null)) {
if (
initialized.current ||
(claudeAuthStatus !== null && codexAuthStatus !== null && zaiAuthStatus !== null)
) {
return;
}
initialized.current = true;
void refreshStatuses();
}, [refreshStatuses, claudeAuthStatus, codexAuthStatus]);
}, [refreshStatuses, claudeAuthStatus, codexAuthStatus, zaiAuthStatus]);
}