feat(api): add frontend usage API and query hooks

Add TypeScript types for usage statistics.
Implement usage API with Tauri invoke calls.
Add TanStack Query hooks for usage data fetching.
This commit is contained in:
YoVinchen
2025-12-03 00:21:29 +08:00
parent 96ec6c561e
commit 38e8949152
3 changed files with 130 additions and 62 deletions
+97 -47
View File
@@ -1,33 +1,24 @@
import { invoke } from "@tauri-apps/api/core";
import type {
UsageSummary,
DailyStats,
ProviderStats,
ModelStats,
RequestLog,
LogFilters,
ModelPricing,
ProviderLimitStatus,
} from "@/types/usage";
import type { UsageResult } from "@/types";
import type { AppId } from "./types";
import i18n from "@/i18n";
export const usageApi = {
async query(providerId: string, appId: AppId): Promise<UsageResult> {
try {
return await invoke("queryProviderUsage", {
providerId: providerId,
app: appId,
});
} catch (error: unknown) {
// 提取错误消息:优先使用后端返回的错误信息
const message =
typeof error === "string"
? error
: error instanceof Error
? error.message
: "";
// 如果没有错误消息,使用国际化的默认提示
return {
success: false,
error: message || i18n.t("errors.usage_query_failed"),
};
}
// Provider usage script methods
query: async (providerId: string, appId: AppId): Promise<UsageResult> => {
return invoke("queryProviderUsage", { providerId, app: appId });
},
async testScript(
testScript: async (
providerId: string,
appId: AppId,
scriptCode: string,
@@ -36,30 +27,89 @@ export const usageApi = {
baseUrl?: string,
accessToken?: string,
userId?: string,
): Promise<UsageResult> {
try {
return await invoke("testUsageScript", {
providerId: providerId,
app: appId,
scriptCode: scriptCode,
timeout: timeout,
apiKey: apiKey,
baseUrl: baseUrl,
accessToken: accessToken,
userId: userId,
});
} catch (error: unknown) {
const message =
typeof error === "string"
? error
: error instanceof Error
? error.message
: "";
): Promise<UsageResult> => {
return invoke("testUsageScript", {
providerId,
app: appId,
scriptCode,
timeout,
apiKey,
baseUrl,
accessToken,
userId,
});
},
return {
success: false,
error: message || i18n.t("errors.usage_query_failed"),
};
}
// Proxy usage statistics methods
getUsageSummary: async (
startDate?: number,
endDate?: number,
): Promise<UsageSummary> => {
return invoke("get_usage_summary", { startDate, endDate });
},
getUsageTrends: async (days: number): Promise<DailyStats[]> => {
return invoke("get_usage_trends", { days });
},
getProviderStats: async (): Promise<ProviderStats[]> => {
return invoke("get_provider_stats");
},
getModelStats: async (): Promise<ModelStats[]> => {
return invoke("get_model_stats");
},
getRequestLogs: async (
filters: LogFilters,
limit: number = 50,
offset: number = 0,
): Promise<RequestLog[]> => {
return invoke("get_request_logs", {
providerId: filters.providerId,
model: filters.model,
statusCode: filters.statusCode,
startDate: filters.startDate,
endDate: filters.endDate,
limit,
offset,
});
},
getRequestDetail: async (requestId: string): Promise<RequestLog | null> => {
return invoke("get_request_detail", { requestId });
},
getModelPricing: async (): Promise<ModelPricing[]> => {
return invoke("get_model_pricing");
},
updateModelPricing: async (
modelId: string,
displayName: string,
inputCost: string,
outputCost: string,
cacheReadCost: string,
cacheCreationCost: string,
): Promise<void> => {
return invoke("update_model_pricing", {
modelId,
displayName,
inputCost,
outputCost,
cacheReadCost,
cacheCreationCost,
});
},
deleteModelPricing: async (modelId: string): Promise<void> => {
return invoke("delete_model_pricing", { modelId });
},
checkProviderLimits: async (
providerId: string,
appType: string,
): Promise<ProviderLimitStatus> => {
return invoke("check_provider_limits", { providerId, appType });
},
};
+25 -14
View File
@@ -1,22 +1,22 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { usageApi } from '@/lib/api/usage';
import type { LogFilters } from '@/types/usage';
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { usageApi } from "@/lib/api/usage";
import type { LogFilters } from "@/types/usage";
// Query keys
export const usageKeys = {
all: ['usage'] as const,
all: ["usage"] as const,
summary: (startDate?: number, endDate?: number) =>
[...usageKeys.all, 'summary', startDate, endDate] as const,
trends: (days: number) => [...usageKeys.all, 'trends', days] as const,
providerStats: () => [...usageKeys.all, 'provider-stats'] as const,
modelStats: () => [...usageKeys.all, 'model-stats'] as const,
[...usageKeys.all, "summary", startDate, endDate] as const,
trends: (days: number) => [...usageKeys.all, "trends", days] as const,
providerStats: () => [...usageKeys.all, "provider-stats"] as const,
modelStats: () => [...usageKeys.all, "model-stats"] as const,
logs: (filters: LogFilters, limit: number, offset: number) =>
[...usageKeys.all, 'logs', filters, limit, offset] as const,
[...usageKeys.all, "logs", filters, limit, offset] as const,
detail: (requestId: string) =>
[...usageKeys.all, 'detail', requestId] as const,
pricing: () => [...usageKeys.all, 'pricing'] as const,
[...usageKeys.all, "detail", requestId] as const,
pricing: () => [...usageKeys.all, "pricing"] as const,
limits: (providerId: string, appType: string) =>
[...usageKeys.all, 'limits', providerId, appType] as const,
[...usageKeys.all, "limits", providerId, appType] as const,
};
// Hooks
@@ -51,7 +51,7 @@ export function useModelStats() {
export function useRequestLogs(
filters: LogFilters,
limit: number = 50,
offset: number = 0
offset: number = 0,
) {
return useQuery({
queryKey: usageKeys.logs(filters, limit, offset),
@@ -100,10 +100,21 @@ export function useUpdateModelPricing() {
params.inputCost,
params.outputCost,
params.cacheReadCost,
params.cacheCreationCost
params.cacheCreationCost,
),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: usageKeys.pricing() });
},
});
}
export function useDeleteModelPricing() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (modelId: string) => usageApi.deleteModelPricing(modelId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: usageKeys.pricing() });
},
});
}
+8 -1
View File
@@ -10,6 +10,7 @@ export interface TokenUsage {
export interface RequestLog {
requestId: string;
providerId: string;
providerName?: string;
appType: string;
model: string;
inputTokens: number;
@@ -41,6 +42,8 @@ export interface UsageSummary {
totalCost: string;
totalInputTokens: number;
totalOutputTokens: number;
totalCacheCreationTokens: number;
totalCacheReadTokens: number;
successRate: number;
}
@@ -49,6 +52,10 @@ export interface DailyStats {
requestCount: number;
totalCost: string;
totalTokens: number;
totalInputTokens: number;
totalOutputTokens: number;
totalCacheCreationTokens: number;
totalCacheReadTokens: number;
}
export interface ProviderStats {
@@ -87,7 +94,7 @@ export interface ProviderLimitStatus {
monthlyExceeded: boolean;
}
export type TimeRange = '7d' | '30d' | '90d';
export type TimeRange = "1d" | "7d" | "30d";
export interface StatsFilters {
timeRange: TimeRange;