Spaces:
Runtime error
Runtime error
| <script setup lang='ts'> | |
| import { nextTick, onMounted, reactive, ref } from 'vue' | |
| import { NCol, NDatePicker, NIcon, NNumberAnimation, NRow, NSpin, NStatistic } from 'naive-ui' | |
| import type { ChartData, ChartOptions } from 'chart.js' | |
| import { BarElement, CategoryScale, Chart as ChartJS, Legend, LinearScale, Title, Tooltip } from 'chart.js' | |
| import { Bar } from 'vue-chartjs' | |
| import dayjs from 'dayjs' | |
| import { t } from '@/locales' | |
| import { fetchUserStatistics } from '@/api' | |
| import { SvgIcon } from '@/components/common' | |
| ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale) | |
| const chartData: ChartData<'bar'> = reactive({ | |
| labels: [], | |
| datasets: [ | |
| { | |
| label: t('setting.statisticsPrompt'), | |
| data: [], | |
| borderColor: '#a1dc95', | |
| backgroundColor: '#a1dc95', | |
| stack: 'Usage', | |
| }, | |
| { | |
| label: t('setting.statisticsCompletion'), | |
| data: [], | |
| borderColor: '#6d6e7e', | |
| backgroundColor: '#6d6e7e', | |
| stack: 'Usage', | |
| }, | |
| ], | |
| }) | |
| const chartOptions: ChartOptions<'bar'> = { | |
| responsive: true, | |
| } | |
| const summary = ref({ | |
| promptTokens: 0, | |
| completionTokens: 0, | |
| totalTokens: 0, | |
| }) | |
| const loading = ref(false) | |
| const range: any = ref([ | |
| dayjs().subtract(30, 'day').startOf('day').valueOf(), | |
| dayjs().endOf('day').valueOf(), | |
| ]) | |
| const rangeShortcuts: { [index: string]: [number, number] } = {} | |
| // last month | |
| rangeShortcuts[t('setting.statisticsPeriodLastMonth')] = [ | |
| dayjs().subtract(1, 'month').startOf('month').valueOf(), | |
| dayjs().subtract(1, 'month').endOf('month').valueOf(), | |
| ] | |
| // current month | |
| rangeShortcuts[t('setting.statisticsPeriodCurrentMonth')] = [ | |
| dayjs().startOf('month').valueOf(), | |
| dayjs().endOf('month').valueOf(), | |
| ] | |
| // last 30 days | |
| rangeShortcuts[t('setting.statisticsPeriodLast30Days')] = [ | |
| dayjs().subtract(30, 'day').startOf('day').valueOf(), | |
| dayjs().endOf('day').valueOf(), | |
| ] | |
| const showChart = ref(true) | |
| async function fetchStatistics() { | |
| try { | |
| loading.value = true | |
| const { data } = await fetchUserStatistics( | |
| dayjs(range.value[0]).startOf('day').valueOf(), | |
| dayjs(range.value[1]).endOf('day').valueOf(), | |
| ) | |
| if (Object.keys(data.chartData).length) { | |
| summary.value.promptTokens = data.promptTokens | |
| summary.value.completionTokens = data.completionTokens | |
| summary.value.totalTokens = data.totalTokens | |
| chartData.labels = data.chartData.map((item: any) => item._id) | |
| chartData.datasets[0].data = data.chartData.map((item: any) => item.promptTokens) | |
| chartData.datasets[1].data = data.chartData.map((item: any) => item.completionTokens) | |
| // todo: don't know why data change won't trigger chart re-render, dirty hack | |
| showChart.value = false | |
| nextTick(() => { | |
| showChart.value = true | |
| }) | |
| } | |
| } | |
| finally { | |
| loading.value = false | |
| } | |
| } | |
| onMounted(() => { | |
| fetchStatistics() | |
| }) | |
| </script> | |
| <template> | |
| <NSpin :show="loading"> | |
| <div class="p-4 space-y-5 min-h-[200px]"> | |
| <div class="space-y-6"> | |
| <div class="flex items-center space-x-4"> | |
| <span class="flex-shrink-0 w-[100px]">{{ $t('setting.statisticsPeriod') }}</span> | |
| <div class="flex-1"> | |
| <NDatePicker | |
| v-model:value="range" | |
| type="daterange" | |
| :shortcuts="rangeShortcuts" | |
| @update:value="fetchStatistics" | |
| /> | |
| </div> | |
| </div> | |
| <div class="flex items-center space-x-4"> | |
| <NRow> | |
| <NCol :span="8" class="text-center"> | |
| <NStatistic :label="t('setting.statisticsPrompt')"> | |
| <template #prefix> | |
| <NIcon> | |
| <SvgIcon class="text-lg" icon="ri-chat-upload-line" /> | |
| </NIcon> | |
| </template> | |
| <NNumberAnimation :duration="1000" :to="summary.promptTokens" /> | |
| </NStatistic> | |
| </NCol> | |
| <NCol :span="8" class="text-center"> | |
| <NStatistic :label="t('setting.statisticsCompletion')"> | |
| <template #prefix> | |
| <NIcon> | |
| <SvgIcon class="text-lg" icon="ri-chat-download-line" /> | |
| </NIcon> | |
| </template> | |
| <NNumberAnimation :duration="1000" :to="summary.completionTokens" /> | |
| </NStatistic> | |
| </NCol> | |
| <NCol :span="8" class="text-center"> | |
| <NStatistic :label="t('setting.statisticsTotal')"> | |
| <template #prefix> | |
| <NIcon> | |
| <SvgIcon class="text-lg" icon="ri-question-answer-line" /> | |
| </NIcon> | |
| </template> | |
| <NNumberAnimation :duration="1000" :to="summary.totalTokens" /> | |
| </NStatistic> | |
| </NCol> | |
| </NRow> | |
| </div> | |
| <Bar | |
| v-if="showChart && chartData.labels?.length" | |
| ref="statisticsChart" | |
| style="aspect-ratio: 3/2;" | |
| :options="chartOptions" | |
| :data="chartData" | |
| /> | |
| </div> | |
| </div> | |
| </NSpin> | |
| </template> | |
| <style> | |
| </style> | |