mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-03 11:20:50 +08:00
154 lines
4.6 KiB
TypeScript
154 lines
4.6 KiB
TypeScript
import { useEffect, useState, useCallback, useRef } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { useNotificationStore } from '@/stores';
|
|
import { usageApi } from '@/services/api/usage';
|
|
import { loadModelPrices, saveModelPrices, type ModelPrice } from '@/utils/usage';
|
|
|
|
export interface UsagePayload {
|
|
total_requests?: number;
|
|
success_count?: number;
|
|
failure_count?: number;
|
|
total_tokens?: number;
|
|
apis?: Record<string, unknown>;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
export interface UseUsageDataReturn {
|
|
usage: UsagePayload | null;
|
|
loading: boolean;
|
|
error: string;
|
|
modelPrices: Record<string, ModelPrice>;
|
|
setModelPrices: (prices: Record<string, ModelPrice>) => void;
|
|
loadUsage: () => Promise<void>;
|
|
handleExport: () => Promise<void>;
|
|
handleImport: () => void;
|
|
handleImportChange: (event: React.ChangeEvent<HTMLInputElement>) => Promise<void>;
|
|
importInputRef: React.RefObject<HTMLInputElement | null>;
|
|
exporting: boolean;
|
|
importing: boolean;
|
|
}
|
|
|
|
export function useUsageData(): UseUsageDataReturn {
|
|
const { t } = useTranslation();
|
|
const { showNotification } = useNotificationStore();
|
|
|
|
const [usage, setUsage] = useState<UsagePayload | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState('');
|
|
const [modelPrices, setModelPrices] = useState<Record<string, ModelPrice>>({});
|
|
const [exporting, setExporting] = useState(false);
|
|
const [importing, setImporting] = useState(false);
|
|
const importInputRef = useRef<HTMLInputElement | null>(null);
|
|
|
|
const loadUsage = useCallback(async () => {
|
|
setLoading(true);
|
|
setError('');
|
|
try {
|
|
const data = await usageApi.getUsage();
|
|
const payload = data?.usage ?? data;
|
|
setUsage(payload);
|
|
} catch (err: unknown) {
|
|
const message = err instanceof Error ? err.message : t('usage_stats.loading_error');
|
|
setError(message);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [t]);
|
|
|
|
useEffect(() => {
|
|
loadUsage();
|
|
setModelPrices(loadModelPrices());
|
|
}, [loadUsage]);
|
|
|
|
const handleExport = async () => {
|
|
setExporting(true);
|
|
try {
|
|
const data = await usageApi.exportUsage();
|
|
const exportedAt =
|
|
typeof data?.exported_at === 'string' ? new Date(data.exported_at) : new Date();
|
|
const safeTimestamp = Number.isNaN(exportedAt.getTime())
|
|
? new Date().toISOString()
|
|
: exportedAt.toISOString();
|
|
const filename = `usage-export-${safeTimestamp.replace(/[:.]/g, '-')}.json`;
|
|
const blob = new Blob([JSON.stringify(data ?? {}, null, 2)], { type: 'application/json' });
|
|
const url = window.URL.createObjectURL(blob);
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.download = filename;
|
|
link.click();
|
|
window.URL.revokeObjectURL(url);
|
|
showNotification(t('usage_stats.export_success'), 'success');
|
|
} catch (err: unknown) {
|
|
const message = err instanceof Error ? err.message : '';
|
|
showNotification(
|
|
`${t('notification.download_failed')}${message ? `: ${message}` : ''}`,
|
|
'error'
|
|
);
|
|
} finally {
|
|
setExporting(false);
|
|
}
|
|
};
|
|
|
|
const handleImport = () => {
|
|
importInputRef.current?.click();
|
|
};
|
|
|
|
const handleImportChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = event.target.files?.[0];
|
|
event.target.value = '';
|
|
if (!file) return;
|
|
|
|
setImporting(true);
|
|
try {
|
|
const text = await file.text();
|
|
let payload: unknown;
|
|
try {
|
|
payload = JSON.parse(text);
|
|
} catch {
|
|
showNotification(t('usage_stats.import_invalid'), 'error');
|
|
return;
|
|
}
|
|
|
|
const result = await usageApi.importUsage(payload);
|
|
showNotification(
|
|
t('usage_stats.import_success', {
|
|
added: result?.added ?? 0,
|
|
skipped: result?.skipped ?? 0,
|
|
total: result?.total_requests ?? 0,
|
|
failed: result?.failed_requests ?? 0
|
|
}),
|
|
'success'
|
|
);
|
|
await loadUsage();
|
|
} catch (err: unknown) {
|
|
const message = err instanceof Error ? err.message : '';
|
|
showNotification(
|
|
`${t('notification.upload_failed')}${message ? `: ${message}` : ''}`,
|
|
'error'
|
|
);
|
|
} finally {
|
|
setImporting(false);
|
|
}
|
|
};
|
|
|
|
const handleSetModelPrices = useCallback((prices: Record<string, ModelPrice>) => {
|
|
setModelPrices(prices);
|
|
saveModelPrices(prices);
|
|
}, []);
|
|
|
|
return {
|
|
usage,
|
|
loading,
|
|
error,
|
|
modelPrices,
|
|
setModelPrices: handleSetModelPrices,
|
|
loadUsage,
|
|
handleExport,
|
|
handleImport,
|
|
handleImportChange,
|
|
importInputRef,
|
|
exporting,
|
|
importing
|
|
};
|
|
}
|