File size: 4,206 Bytes
69b897d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
<template>
  <div class="glass-strong rounded-3xl p-6">
    <div class="mb-6 flex flex-col items-start justify-between gap-4 md:flex-row md:items-center">
      <h2 class="flex items-center text-xl font-bold text-gray-800">
        <i class="fas fa-robot mr-2 text-purple-500" />
        模型使用分布
      </h2>

      <el-radio-group v-model="modelPeriod" size="small" @change="handlePeriodChange">
        <el-radio-button label="daily"> 今日 </el-radio-button>
        <el-radio-button label="total"> 累计 </el-radio-button>
      </el-radio-group>
    </div>

    <div
      v-if="dashboardStore.dashboardModelStats.length === 0"
      class="py-12 text-center text-gray-500"
    >
      <i class="fas fa-chart-pie mb-3 text-4xl opacity-30" />
      <p>暂无模型使用数据</p>
    </div>

    <div v-else class="grid grid-cols-1 gap-6 lg:grid-cols-2">
      <!-- 饼图 -->
      <div class="relative" style="height: 300px">
        <canvas ref="chartCanvas" />
      </div>

      <!-- 数据列表 -->
      <div class="space-y-3">
        <div
          v-for="(stat, index) in sortedStats"
          :key="stat.model"
          class="flex items-center justify-between rounded-lg bg-gray-50 p-3"
        >
          <div class="flex items-center gap-3">
            <div class="h-4 w-4 rounded" :style="`background-color: ${getColor(index)}`" />
            <span class="font-medium text-gray-700">{{ stat.model }}</span>
          </div>
          <div class="text-right">
            <p class="font-semibold text-gray-800">{{ formatNumber(stat.requests) }} 请求</p>
            <p class="text-sm text-gray-500">{{ formatNumber(stat.totalTokens) }} tokens</p>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
import { Chart } from 'chart.js/auto'
import { useDashboardStore } from '@/stores/dashboard'
import { useChartConfig } from '@/composables/useChartConfig'
import { formatNumber } from '@/utils/format'

const dashboardStore = useDashboardStore()
const chartCanvas = ref(null)
let chart = null

const modelPeriod = ref('daily')

const sortedStats = computed(() => {
  return [...dashboardStore.dashboardModelStats].sort((a, b) => b.requests - a.requests)
})

const getColor = (index) => {
  const { colorSchemes } = useChartConfig()
  const colors = colorSchemes.primary
  return colors[index % colors.length]
}

const createChart = () => {
  if (!chartCanvas.value || !dashboardStore.dashboardModelStats.length) return

  if (chart) {
    chart.destroy()
  }

  const { colorSchemes } = useChartConfig()
  const colors = colorSchemes.primary

  chart = new Chart(chartCanvas.value, {
    type: 'doughnut',
    data: {
      labels: sortedStats.value.map((stat) => stat.model),
      datasets: [
        {
          data: sortedStats.value.map((stat) => stat.requests),
          backgroundColor: sortedStats.value.map((_, index) => colors[index % colors.length]),
          borderWidth: 0
        }
      ]
    },
    options: {
      responsive: true,
      maintainAspectRatio: false,
      plugins: {
        legend: {
          display: false
        },
        tooltip: {
          callbacks: {
            label: function (context) {
              const stat = sortedStats.value[context.dataIndex]
              const percentage = (
                (stat.requests /
                  dashboardStore.dashboardModelStats.reduce((sum, s) => sum + s.requests, 0)) *
                100
              ).toFixed(1)
              return [
                `${stat.model}: ${percentage}%`,
                `请求: ${formatNumber(stat.requests)}`,
                `Tokens: ${formatNumber(stat.totalTokens)}`
              ]
            }
          }
        }
      }
    }
  })
}

const handlePeriodChange = async () => {
  await dashboardStore.loadModelStats(modelPeriod.value)
  createChart()
}

watch(
  () => dashboardStore.dashboardModelStats,
  () => {
    createChart()
  },
  { deep: true }
)

onMounted(() => {
  createChart()
})

onUnmounted(() => {
  if (chart) {
    chart.destroy()
  }
})
</script>

<style scoped>
/* 组件特定样式 */
</style>