2424 </div >
2525 <div class =" grid grid-cols-1 gap-6 lg:grid-cols-2" >
2626 <ModelDistributionChart
27+ v-model :source =" modelDistributionSource "
2728 v-model :metric =" modelDistributionMetric "
28- :model-stats =" modelStats "
29- :loading =" chartsLoading "
29+ :model-stats =" requestedModelStats "
30+ :upstream-model-stats =" upstreamModelStats "
31+ :mapping-model-stats =" mappingModelStats "
32+ :loading =" modelStatsLoading "
33+ :show-source-toggle =" true "
3034 :show-metric-toggle =" true "
3135 :start-date =" startDate "
3236 :end-date =" endDate "
115119</template >
116120
117121<script setup lang="ts">
118- import { ref , reactive , computed , onMounted , onUnmounted } from ' vue'
122+ import { ref , reactive , computed , onMounted , onUnmounted , watch } from ' vue'
119123import { useI18n } from ' vue-i18n'
120124import { saveAs } from ' file-saver'
121125import { useRoute } from ' vue-router'
@@ -136,10 +140,17 @@ const { t } = useI18n()
136140const appStore = useAppStore ()
137141type DistributionMetric = ' tokens' | ' actual_cost'
138142type EndpointSource = ' inbound' | ' upstream' | ' path'
143+ type ModelDistributionSource = ' requested' | ' upstream' | ' mapping'
139144const route = useRoute ()
140145const usageStats = ref <AdminUsageStatsResponse | null >(null ); const usageLogs = ref <AdminUsageLog []>([]); const loading = ref (false ); const exporting = ref (false )
141- const trendData = ref <TrendDataPoint []>([]); const modelStats = ref <ModelStat []>([]); const groupStats = ref <GroupStat []>([]); const chartsLoading = ref (false ); const granularity = ref <' day' | ' hour' >(' hour' )
146+ const trendData = ref <TrendDataPoint []>([]); const requestedModelStats = ref <ModelStat []>([]); const upstreamModelStats = ref < ModelStat []>([]); const mappingModelStats = ref < ModelStat []>([]); const groupStats = ref <GroupStat []>([]); const chartsLoading = ref ( false ); const modelStatsLoading = ref (false ); const granularity = ref <' day' | ' hour' >(' hour' )
142147const modelDistributionMetric = ref <DistributionMetric >(' tokens' )
148+ const modelDistributionSource = ref <ModelDistributionSource >(' requested' )
149+ const loadedModelSources = reactive <Record <ModelDistributionSource , boolean >>({
150+ requested: false ,
151+ upstream: false ,
152+ mapping: false ,
153+ })
143154const groupDistributionMetric = ref <DistributionMetric >(' tokens' )
144155const endpointDistributionMetric = ref <DistributionMetric >(' tokens' )
145156const endpointDistributionSource = ref <EndpointSource >(' inbound' )
@@ -150,6 +161,7 @@ const endpointStatsLoading = ref(false)
150161let abortController: AbortController | null = null ; let exportAbortController: AbortController | null = null
151162let chartReqSeq = 0
152163let statsReqSeq = 0
164+ let modelStatsReqSeq = 0
153165const exportProgress = reactive ({ show: false , progress: 0 , current: 0 , total: 0 , estimatedTime: ' ' })
154166const cleanupDialogVisible = ref (false )
155167// Balance history modal state
@@ -269,6 +281,68 @@ const loadStats = async () => {
269281 if (seq === statsReqSeq ) endpointStatsLoading .value = false
270282 }
271283}
284+
285+ const resetModelStatsCache = () => {
286+ requestedModelStats .value = []
287+ upstreamModelStats .value = []
288+ mappingModelStats .value = []
289+ loadedModelSources .requested = false
290+ loadedModelSources .upstream = false
291+ loadedModelSources .mapping = false
292+ }
293+
294+ const loadModelStats = async (source : ModelDistributionSource , force = false ) => {
295+ if (! force && loadedModelSources [source ]) {
296+ return
297+ }
298+
299+ const seq = ++ modelStatsReqSeq
300+ modelStatsLoading .value = true
301+ try {
302+ const requestType = filters .value .request_type
303+ const legacyStream = requestType ? requestTypeToLegacyStream (requestType ) : filters .value .stream
304+ const baseParams = {
305+ start_date: filters .value .start_date || startDate .value ,
306+ end_date: filters .value .end_date || endDate .value ,
307+ user_id: filters .value .user_id ,
308+ model: filters .value .model ,
309+ api_key_id: filters .value .api_key_id ,
310+ account_id: filters .value .account_id ,
311+ group_id: filters .value .group_id ,
312+ request_type: requestType ,
313+ stream: legacyStream === null ? undefined : legacyStream ,
314+ billing_type: filters .value .billing_type ,
315+ }
316+
317+ const response = await adminAPI .dashboard .getModelStats ({ ... baseParams , model_source: source })
318+
319+ if (seq !== modelStatsReqSeq ) return
320+
321+ const models = response .models || []
322+ if (source === ' requested' ) {
323+ requestedModelStats .value = models
324+ } else if (source === ' upstream' ) {
325+ upstreamModelStats .value = models
326+ } else {
327+ mappingModelStats .value = models
328+ }
329+ loadedModelSources [source ] = true
330+ } catch (error ) {
331+ if (seq !== modelStatsReqSeq ) return
332+ console .error (' Failed to load model stats:' , error )
333+ if (source === ' requested' ) {
334+ requestedModelStats .value = []
335+ } else if (source === ' upstream' ) {
336+ upstreamModelStats .value = []
337+ } else {
338+ mappingModelStats .value = []
339+ }
340+ loadedModelSources [source ] = false
341+ } finally {
342+ if (seq === modelStatsReqSeq ) modelStatsLoading .value = false
343+ }
344+ }
345+
272346const loadChartData = async () => {
273347 const seq = ++ chartReqSeq
274348 chartsLoading .value = true
@@ -289,18 +363,30 @@ const loadChartData = async () => {
289363 billing_type: filters .value .billing_type ,
290364 include_stats: false ,
291365 include_trend: true ,
292- include_model_stats: true ,
366+ include_model_stats: false ,
293367 include_group_stats: true ,
294368 include_users_trend: false
295369 })
296370 if (seq !== chartReqSeq ) return
297371 trendData .value = snapshot .trend || []
298- modelStats .value = snapshot .models || []
299372 groupStats .value = snapshot .groups || []
300373 } catch (error ) { console .error (' Failed to load chart data:' , error ) } finally { if (seq === chartReqSeq ) chartsLoading .value = false }
301374}
302- const applyFilters = () => { pagination .page = 1 ; loadLogs (); loadStats (); loadChartData () }
303- const refreshData = () => { loadLogs (); loadStats (); loadChartData () }
375+ const applyFilters = () => {
376+ pagination .page = 1
377+ resetModelStatsCache ()
378+ loadLogs ()
379+ loadStats ()
380+ loadModelStats (modelDistributionSource .value , true )
381+ loadChartData ()
382+ }
383+ const refreshData = () => {
384+ resetModelStatsCache ()
385+ loadLogs ()
386+ loadStats ()
387+ loadModelStats (modelDistributionSource .value , true )
388+ loadChartData ()
389+ }
304390const resetFilters = () => {
305391 const range = getLast24HoursRangeDates ()
306392 startDate .value = range .start
@@ -329,7 +415,7 @@ const exportToExcel = async () => {
329415 const XLSX = await import (' xlsx' )
330416 const headers = [
331417 t (' usage.time' ), t (' admin.usage.user' ), t (' usage.apiKeyFilter' ),
332- t (' admin.usage.account' ), t (' usage.model' ), t (' usage.reasoningEffort' ), t (' admin.usage.group' ),
418+ t (' admin.usage.account' ), t (' usage.model' ), t (' usage.upstreamModel ' ), t ( ' usage. reasoningEffort' ), t (' admin.usage.group' ),
333419 t (' usage.inboundEndpoint' ), t (' usage.upstreamEndpoint' ),
334420 t (' usage.type' ),
335421 t (' admin.usage.inputTokens' ), t (' admin.usage.outputTokens' ),
@@ -348,7 +434,7 @@ const exportToExcel = async () => {
348434 if (c .signal .aborted ) break ; if (p === 1 ) { total = res .total ; exportProgress .total = total }
349435 const rows = (res .items || []).map ((log : AdminUsageLog ) => [
350436 log .created_at , log .user ?.email || ' ' , log .api_key ?.name || ' ' , log .account ?.name || ' ' , log .model ,
351- formatReasoningEffort (log .reasoning_effort ), log .group ?.name || ' ' ,
437+ log . upstream_model || ' ' , formatReasoningEffort (log .reasoning_effort ), log .group ?.name || ' ' ,
352438 log .inbound_endpoint || ' ' , log .upstream_endpoint || ' ' , getRequestTypeLabel (log ),
353439 log .input_tokens , log .output_tokens , log .cache_read_tokens , log .cache_creation_tokens ,
354440 log .input_cost ?.toFixed (6 ) || ' 0.000000' , log .output_cost ?.toFixed (6 ) || ' 0.000000' ,
@@ -458,11 +544,16 @@ onMounted(() => {
458544 applyRouteQueryFilters ()
459545 loadLogs ()
460546 loadStats ()
547+ loadModelStats (modelDistributionSource .value , true )
461548 window .setTimeout (() => {
462549 void loadChartData ()
463550 }, 120 )
464551 loadSavedColumns ()
465552 document .addEventListener (' click' , handleColumnClickOutside )
466553})
467554onUnmounted (() => { abortController ?.abort (); exportAbortController ?.abort (); document .removeEventListener (' click' , handleColumnClickOutside ) })
555+
556+ watch (modelDistributionSource , (source ) => {
557+ void loadModelStats (source )
558+ })
468559 </script >
0 commit comments