| import { defineStore } from 'pinia' |
| import { ref, computed } from 'vue' |
| import { apiStatsClient } from '@/config/apiStats' |
|
|
| export const useApiStatsStore = defineStore('apistats', () => { |
| |
| const apiKey = ref('') |
| const apiId = ref(null) |
| const loading = ref(false) |
| const modelStatsLoading = ref(false) |
| const oemLoading = ref(true) |
| const error = ref('') |
| const statsPeriod = ref('daily') |
| const statsData = ref(null) |
| const modelStats = ref([]) |
| const dailyStats = ref(null) |
| const monthlyStats = ref(null) |
| const oemSettings = ref({ |
| siteName: '', |
| siteIcon: '', |
| siteIconData: '' |
| }) |
|
|
| |
| const multiKeyMode = ref(false) |
| const apiKeys = ref([]) |
| const apiIds = ref([]) |
| const aggregatedStats = ref(null) |
| const individualStats = ref([]) |
| const invalidKeys = ref([]) |
|
|
| |
| const currentPeriodData = computed(() => { |
| const defaultData = { |
| requests: 0, |
| inputTokens: 0, |
| outputTokens: 0, |
| cacheCreateTokens: 0, |
| cacheReadTokens: 0, |
| allTokens: 0, |
| cost: 0, |
| formattedCost: '$0.000000' |
| } |
|
|
| |
| if (multiKeyMode.value && aggregatedStats.value) { |
| if (statsPeriod.value === 'daily') { |
| return aggregatedStats.value.dailyUsage || defaultData |
| } else { |
| return aggregatedStats.value.monthlyUsage || defaultData |
| } |
| } |
|
|
| |
| if (statsPeriod.value === 'daily') { |
| return dailyStats.value || defaultData |
| } else { |
| return monthlyStats.value || defaultData |
| } |
| }) |
|
|
| const usagePercentages = computed(() => { |
| if (!statsData.value || !currentPeriodData.value) { |
| return { |
| tokenUsage: 0, |
| costUsage: 0, |
| requestUsage: 0 |
| } |
| } |
|
|
| const current = currentPeriodData.value |
| const limits = statsData.value.limits |
|
|
| return { |
| tokenUsage: |
| limits.tokenLimit > 0 ? Math.min((current.allTokens / limits.tokenLimit) * 100, 100) : 0, |
| costUsage: |
| limits.dailyCostLimit > 0 ? Math.min((current.cost / limits.dailyCostLimit) * 100, 100) : 0, |
| requestUsage: |
| limits.rateLimitRequests > 0 |
| ? Math.min((current.requests / limits.rateLimitRequests) * 100, 100) |
| : 0 |
| } |
| }) |
|
|
| |
|
|
| |
| async function queryStats() { |
| |
| if (multiKeyMode.value) { |
| return queryBatchStats() |
| } |
|
|
| const trimmedKey = apiKey.value.trim() |
|
|
| if (!trimmedKey) { |
| error.value = '请输入 API Key' |
| return |
| } |
|
|
| |
| if (trimmedKey.length < 10 || trimmedKey.length > 512) { |
| error.value = 'API Key 格式无效:长度应在 10-512 个字符之间' |
| return |
| } |
|
|
| loading.value = true |
| error.value = '' |
| statsData.value = null |
| modelStats.value = [] |
| apiId.value = null |
|
|
| try { |
| |
| const idResult = await apiStatsClient.getKeyId(trimmedKey) |
|
|
| if (idResult.success) { |
| apiId.value = idResult.data.id |
|
|
| |
| const statsResult = await apiStatsClient.getUserStats(apiId.value) |
|
|
| if (statsResult.success) { |
| statsData.value = statsResult.data |
|
|
| |
| await loadAllPeriodStats() |
|
|
| |
| error.value = '' |
|
|
| |
| updateURL() |
| } else { |
| throw new Error(statsResult.message || '查询失败') |
| } |
| } else { |
| throw new Error(idResult.message || '获取 API Key ID 失败') |
| } |
| } catch (err) { |
| console.error('Query stats error:', err) |
| error.value = err.message || '查询统计数据失败,请检查您的 API Key 是否正确' |
| statsData.value = null |
| modelStats.value = [] |
| apiId.value = null |
| } finally { |
| loading.value = false |
| } |
| } |
|
|
| |
| async function loadAllPeriodStats() { |
| if (!apiId.value) return |
|
|
| |
| await Promise.all([loadPeriodStats('daily'), loadPeriodStats('monthly')]) |
|
|
| |
| await loadModelStats(statsPeriod.value) |
| } |
|
|
| |
| async function loadPeriodStats(period) { |
| try { |
| const result = await apiStatsClient.getUserModelStats(apiId.value, period) |
|
|
| if (result.success) { |
| |
| const modelData = result.data || [] |
| const summary = { |
| requests: 0, |
| inputTokens: 0, |
| outputTokens: 0, |
| cacheCreateTokens: 0, |
| cacheReadTokens: 0, |
| allTokens: 0, |
| cost: 0, |
| formattedCost: '$0.000000' |
| } |
|
|
| modelData.forEach((model) => { |
| summary.requests += model.requests || 0 |
| summary.inputTokens += model.inputTokens || 0 |
| summary.outputTokens += model.outputTokens || 0 |
| summary.cacheCreateTokens += model.cacheCreateTokens || 0 |
| summary.cacheReadTokens += model.cacheReadTokens || 0 |
| summary.allTokens += model.allTokens || 0 |
| summary.cost += model.costs?.total || 0 |
| }) |
|
|
| summary.formattedCost = formatCost(summary.cost) |
|
|
| |
| if (period === 'daily') { |
| dailyStats.value = summary |
| } else { |
| monthlyStats.value = summary |
| } |
| } else { |
| console.warn(`Failed to load ${period} stats:`, result.message) |
| } |
| } catch (err) { |
| console.error(`Load ${period} stats error:`, err) |
| } |
| } |
|
|
| |
| async function loadModelStats(period = 'daily') { |
| if (!apiId.value) return |
|
|
| modelStatsLoading.value = true |
|
|
| try { |
| const result = await apiStatsClient.getUserModelStats(apiId.value, period) |
|
|
| if (result.success) { |
| modelStats.value = result.data || [] |
| } else { |
| throw new Error(result.message || '加载模型统计失败') |
| } |
| } catch (err) { |
| console.error('Load model stats error:', err) |
| modelStats.value = [] |
| } finally { |
| modelStatsLoading.value = false |
| } |
| } |
|
|
| |
| async function switchPeriod(period) { |
| if (statsPeriod.value === period || modelStatsLoading.value) { |
| return |
| } |
|
|
| statsPeriod.value = period |
|
|
| |
| if (multiKeyMode.value && apiIds.value.length > 0) { |
| await loadBatchModelStats(period) |
| return |
| } |
|
|
| |
| if ( |
| (period === 'daily' && !dailyStats.value) || |
| (period === 'monthly' && !monthlyStats.value) |
| ) { |
| await loadPeriodStats(period) |
| } |
|
|
| |
| await loadModelStats(period) |
| } |
|
|
| |
| async function loadStatsWithApiId() { |
| if (!apiId.value) return |
|
|
| loading.value = true |
| error.value = '' |
| statsData.value = null |
| modelStats.value = [] |
|
|
| try { |
| const result = await apiStatsClient.getUserStats(apiId.value) |
|
|
| if (result.success) { |
| statsData.value = result.data |
|
|
| |
| console.log('API Stats - Full response:', result.data) |
| console.log('API Stats - limits data:', result.data.limits) |
| console.log('API Stats - weeklyOpusCostLimit:', result.data.limits?.weeklyOpusCostLimit) |
| console.log('API Stats - weeklyOpusCost:', result.data.limits?.weeklyOpusCost) |
|
|
| |
| await loadAllPeriodStats() |
|
|
| |
| error.value = '' |
| } else { |
| throw new Error(result.message || '查询失败') |
| } |
| } catch (err) { |
| console.error('Load stats with apiId error:', err) |
| error.value = err.message || '查询统计数据失败' |
| statsData.value = null |
| modelStats.value = [] |
| } finally { |
| loading.value = false |
| } |
| } |
|
|
| |
| async function loadOemSettings() { |
| oemLoading.value = true |
| try { |
| const result = await apiStatsClient.getOemSettings() |
| if (result && result.success && result.data) { |
| oemSettings.value = { ...oemSettings.value, ...result.data } |
| } |
| } catch (err) { |
| console.error('Error loading OEM settings:', err) |
| |
| oemSettings.value = { |
| siteName: 'Claude Relay Service', |
| siteIcon: '', |
| siteIconData: '' |
| } |
| } finally { |
| oemLoading.value = false |
| } |
| } |
|
|
| |
|
|
| |
| function formatCost(cost) { |
| if (typeof cost !== 'number' || cost === 0) { |
| return '$0.000000' |
| } |
|
|
| |
| if (cost >= 1) { |
| return '$' + cost.toFixed(2) |
| } else if (cost >= 0.01) { |
| return '$' + cost.toFixed(4) |
| } else { |
| return '$' + cost.toFixed(6) |
| } |
| } |
|
|
| |
| function updateURL() { |
| if (apiId.value) { |
| const url = new URL(window.location) |
| url.searchParams.set('apiId', apiId.value) |
| window.history.pushState({}, '', url) |
| } |
| } |
|
|
| |
| async function queryBatchStats() { |
| const keys = parseApiKeys() |
| if (keys.length === 0) { |
| error.value = '请输入至少一个有效的 API Key' |
| return |
| } |
|
|
| loading.value = true |
| error.value = '' |
| aggregatedStats.value = null |
| individualStats.value = [] |
| invalidKeys.value = [] |
| modelStats.value = [] |
| apiKeys.value = keys |
| apiIds.value = [] |
|
|
| try { |
| |
| const idResults = await Promise.allSettled(keys.map((key) => apiStatsClient.getKeyId(key))) |
|
|
| const validIds = [] |
| const validKeys = [] |
|
|
| idResults.forEach((result, index) => { |
| if (result.status === 'fulfilled' && result.value.success) { |
| validIds.push(result.value.data.id) |
| validKeys.push(keys[index]) |
| } else { |
| invalidKeys.value.push(keys[index]) |
| } |
| }) |
|
|
| if (validIds.length === 0) { |
| throw new Error('所有 API Key 都无效') |
| } |
|
|
| apiIds.value = validIds |
| apiKeys.value = validKeys |
|
|
| |
| const batchResult = await apiStatsClient.getBatchStats(validIds) |
|
|
| if (batchResult.success) { |
| aggregatedStats.value = batchResult.data.aggregated |
| individualStats.value = batchResult.data.individual |
| statsData.value = batchResult.data.aggregated |
|
|
| |
| dailyStats.value = batchResult.data.aggregated.dailyUsage || null |
| monthlyStats.value = batchResult.data.aggregated.monthlyUsage || null |
|
|
| |
| await loadBatchModelStats(statsPeriod.value) |
|
|
| |
| updateBatchURL() |
| } else { |
| throw new Error(batchResult.message || '批量查询失败') |
| } |
| } catch (err) { |
| console.error('Batch query error:', err) |
| error.value = err.message || '批量查询统计数据失败' |
| aggregatedStats.value = null |
| individualStats.value = [] |
| } finally { |
| loading.value = false |
| } |
| } |
|
|
| |
| async function loadBatchModelStats(period = 'daily') { |
| if (apiIds.value.length === 0) return |
|
|
| modelStatsLoading.value = true |
|
|
| try { |
| const result = await apiStatsClient.getBatchModelStats(apiIds.value, period) |
|
|
| if (result.success) { |
| modelStats.value = result.data || [] |
| } else { |
| throw new Error(result.message || '加载批量模型统计失败') |
| } |
| } catch (err) { |
| console.error('Load batch model stats error:', err) |
| modelStats.value = [] |
| } finally { |
| modelStatsLoading.value = false |
| } |
| } |
|
|
| |
| function parseApiKeys() { |
| if (!apiKey.value) return [] |
|
|
| const keys = apiKey.value |
| .split(/[,\n]+/) |
| .map((key) => key.trim()) |
| .filter((key) => key.length >= 10 && key.length <= 512) |
|
|
| |
| const uniqueKeys = [...new Set(keys)] |
| return uniqueKeys.slice(0, 30) |
| } |
|
|
| |
| function updateBatchURL() { |
| if (apiIds.value.length > 0) { |
| const url = new URL(window.location) |
| url.searchParams.set('apiIds', apiIds.value.join(',')) |
| url.searchParams.set('batch', 'true') |
| window.history.pushState({}, '', url) |
| } |
| } |
|
|
| |
| function clearInput() { |
| apiKey.value = '' |
| } |
|
|
| |
| function clearData() { |
| statsData.value = null |
| modelStats.value = [] |
| dailyStats.value = null |
| monthlyStats.value = null |
| error.value = '' |
| statsPeriod.value = 'daily' |
| apiId.value = null |
| |
| apiKeys.value = [] |
| apiIds.value = [] |
| aggregatedStats.value = null |
| individualStats.value = [] |
| invalidKeys.value = [] |
| } |
|
|
| |
| function reset() { |
| apiKey.value = '' |
| multiKeyMode.value = false |
| clearData() |
| } |
|
|
| return { |
| |
| apiKey, |
| apiId, |
| loading, |
| modelStatsLoading, |
| oemLoading, |
| error, |
| statsPeriod, |
| statsData, |
| modelStats, |
| dailyStats, |
| monthlyStats, |
| oemSettings, |
| |
| multiKeyMode, |
| apiKeys, |
| apiIds, |
| aggregatedStats, |
| individualStats, |
| invalidKeys, |
|
|
| |
| currentPeriodData, |
| usagePercentages, |
|
|
| |
| queryStats, |
| queryBatchStats, |
| loadAllPeriodStats, |
| loadPeriodStats, |
| loadModelStats, |
| loadBatchModelStats, |
| switchPeriod, |
| loadStatsWithApiId, |
| loadOemSettings, |
| clearData, |
| clearInput, |
| reset |
| } |
| }) |
|
|