style(frontend): 统一 Views 模块代码风格
- 移除语句末尾分号,规范代码格式 - 优化组件结构和类型定义 - 改进视图文档和示例 - 提升代码一致性
This commit is contained in:
@@ -12,14 +12,28 @@
|
||||
<!-- Balance -->
|
||||
<div class="card p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-emerald-100 dark:bg-emerald-900/30">
|
||||
<svg class="w-5 h-5 text-emerald-600 dark:text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.25 18.75a60.07 60.07 0 0115.797 2.101c.727.198 1.453-.342 1.453-1.096V18.75M3.75 4.5v.75A.75.75 0 013 6h-.75m0 0v-.375c0-.621.504-1.125 1.125-1.125H20.25M2.25 6v9m18-10.5v.75c0 .414.336.75.75.75h.75m-1.5-1.5h.375c.621 0 1.125.504 1.125 1.125v9.75c0 .621-.504 1.125-1.125 1.125h-.375m1.5-1.5H21a.75.75 0 00-.75.75v.75m0 0H3.75m0 0h-.375a1.125 1.125 0 01-1.125-1.125V15m1.5 1.5v-.75A.75.75 0 003 15h-.75M15 10.5a3 3 0 11-6 0 3 3 0 016 0zm3 0h.008v.008H18V10.5zm-12 0h.008v.008H6V10.5z" />
|
||||
<div class="rounded-lg bg-emerald-100 p-2 dark:bg-emerald-900/30">
|
||||
<svg
|
||||
class="h-5 w-5 text-emerald-600 dark:text-emerald-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M2.25 18.75a60.07 60.07 0 0115.797 2.101c.727.198 1.453-.342 1.453-1.096V18.75M3.75 4.5v.75A.75.75 0 013 6h-.75m0 0v-.375c0-.621.504-1.125 1.125-1.125H20.25M2.25 6v9m18-10.5v.75c0 .414.336.75.75.75h.75m-1.5-1.5h.375c.621 0 1.125.504 1.125 1.125v9.75c0 .621-.504 1.125-1.125 1.125h-.375m1.5-1.5H21a.75.75 0 00-.75.75v.75m0 0H3.75m0 0h-.375a1.125 1.125 0 01-1.125-1.125V15m1.5 1.5v-.75A.75.75 0 003 15h-.75M15 10.5a3 3 0 11-6 0 3 3 0 016 0zm3 0h.008v.008H18V10.5zm-12 0h.008v.008H6V10.5z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">{{ t('dashboard.balance') }}</p>
|
||||
<p class="text-xl font-bold text-emerald-600 dark:text-emerald-400">${{ formatBalance(user?.balance || 0) }}</p>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('dashboard.balance') }}
|
||||
</p>
|
||||
<p class="text-xl font-bold text-emerald-600 dark:text-emerald-400">
|
||||
${{ formatBalance(user?.balance || 0) }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">{{ t('common.available') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -28,15 +42,31 @@
|
||||
<!-- API Keys -->
|
||||
<div class="card p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-blue-100 dark:bg-blue-900/30">
|
||||
<svg class="w-5 h-5 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z" />
|
||||
<div class="rounded-lg bg-blue-100 p-2 dark:bg-blue-900/30">
|
||||
<svg
|
||||
class="h-5 w-5 text-blue-600 dark:text-blue-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">{{ t('dashboard.apiKeys') }}</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">{{ stats.total_api_keys }}</p>
|
||||
<p class="text-xs text-green-600 dark:text-green-400">{{ stats.active_api_keys }} {{ t('common.active') }}</p>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('dashboard.apiKeys') }}
|
||||
</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">
|
||||
{{ stats.total_api_keys }}
|
||||
</p>
|
||||
<p class="text-xs text-green-600 dark:text-green-400">
|
||||
{{ stats.active_api_keys }} {{ t('common.active') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -44,15 +74,31 @@
|
||||
<!-- Today Requests -->
|
||||
<div class="card p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-green-100 dark:bg-green-900/30">
|
||||
<svg class="w-5 h-5 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z" />
|
||||
<div class="rounded-lg bg-green-100 p-2 dark:bg-green-900/30">
|
||||
<svg
|
||||
class="h-5 w-5 text-green-600 dark:text-green-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">{{ t('dashboard.todayRequests') }}</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">{{ stats.today_requests }}</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">{{ t('common.total') }}: {{ formatNumber(stats.total_requests) }}</p>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('dashboard.todayRequests') }}
|
||||
</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">
|
||||
{{ stats.today_requests }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('common.total') }}: {{ formatNumber(stats.total_requests) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -60,21 +106,44 @@
|
||||
<!-- Today Cost -->
|
||||
<div class="card p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-purple-100 dark:bg-purple-900/30">
|
||||
<svg class="w-5 h-5 text-purple-600 dark:text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v12m-3-2.818l.879.659c1.171.879 3.07.879 4.242 0 1.172-.879 1.172-2.303 0-3.182C13.536 12.219 12.768 12 12 12c-.725 0-1.45-.22-2.003-.659-1.106-.879-1.106-2.303 0-3.182s2.9-.879 4.006 0l.415.33M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<div class="rounded-lg bg-purple-100 p-2 dark:bg-purple-900/30">
|
||||
<svg
|
||||
class="h-5 w-5 text-purple-600 dark:text-purple-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 6v12m-3-2.818l.879.659c1.171.879 3.07.879 4.242 0 1.172-.879 1.172-2.303 0-3.182C13.536 12.219 12.768 12 12 12c-.725 0-1.45-.22-2.003-.659-1.106-.879-1.106-2.303 0-3.182s2.9-.879 4.006 0l.415.33M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">{{ t('dashboard.todayCost') }}</p>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('dashboard.todayCost') }}
|
||||
</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">
|
||||
<span class="text-purple-600 dark:text-purple-400" :title="t('dashboard.actual')">${{ formatCost(stats.today_actual_cost) }}</span>
|
||||
<span class="text-sm font-normal text-gray-400 dark:text-gray-500" :title="t('dashboard.standard')"> / ${{ formatCost(stats.today_cost) }}</span>
|
||||
<span class="text-purple-600 dark:text-purple-400" :title="t('dashboard.actual')"
|
||||
>${{ formatCost(stats.today_actual_cost) }}</span
|
||||
>
|
||||
<span
|
||||
class="text-sm font-normal text-gray-400 dark:text-gray-500"
|
||||
:title="t('dashboard.standard')"
|
||||
>
|
||||
/ ${{ formatCost(stats.today_cost) }}</span
|
||||
>
|
||||
</p>
|
||||
<p class="text-xs">
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ t('common.total') }}: </span>
|
||||
<span class="text-purple-600 dark:text-purple-400" :title="t('dashboard.actual')">${{ formatCost(stats.total_actual_cost) }}</span>
|
||||
<span class="text-gray-400 dark:text-gray-500" :title="t('dashboard.standard')"> / ${{ formatCost(stats.total_cost) }}</span>
|
||||
<span class="text-purple-600 dark:text-purple-400" :title="t('dashboard.actual')"
|
||||
>${{ formatCost(stats.total_actual_cost) }}</span
|
||||
>
|
||||
<span class="text-gray-400 dark:text-gray-500" :title="t('dashboard.standard')">
|
||||
/ ${{ formatCost(stats.total_cost) }}</span
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -86,16 +155,31 @@
|
||||
<!-- Today Tokens -->
|
||||
<div class="card p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-amber-100 dark:bg-amber-900/30">
|
||||
<svg class="w-5 h-5 text-amber-600 dark:text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m21 7.5-9-5.25L3 7.5m18 0-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9" />
|
||||
<div class="rounded-lg bg-amber-100 p-2 dark:bg-amber-900/30">
|
||||
<svg
|
||||
class="h-5 w-5 text-amber-600 dark:text-amber-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m21 7.5-9-5.25L3 7.5m18 0-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">{{ t('dashboard.todayTokens') }}</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">{{ formatTokens(stats.today_tokens) }}</p>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('dashboard.todayTokens') }}
|
||||
</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">
|
||||
{{ formatTokens(stats.today_tokens) }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('dashboard.input') }}: {{ formatTokens(stats.today_input_tokens) }} / {{ t('dashboard.output') }}: {{ formatTokens(stats.today_output_tokens) }}
|
||||
{{ t('dashboard.input') }}: {{ formatTokens(stats.today_input_tokens) }} /
|
||||
{{ t('dashboard.output') }}: {{ formatTokens(stats.today_output_tokens) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -104,16 +188,31 @@
|
||||
<!-- Total Tokens -->
|
||||
<div class="card p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-indigo-100 dark:bg-indigo-900/30">
|
||||
<svg class="w-5 h-5 text-indigo-600 dark:text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125" />
|
||||
<div class="rounded-lg bg-indigo-100 p-2 dark:bg-indigo-900/30">
|
||||
<svg
|
||||
class="h-5 w-5 text-indigo-600 dark:text-indigo-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">{{ t('dashboard.totalTokens') }}</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">{{ formatTokens(stats.total_tokens) }}</p>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('dashboard.totalTokens') }}
|
||||
</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">
|
||||
{{ formatTokens(stats.total_tokens) }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('dashboard.input') }}: {{ formatTokens(stats.total_input_tokens) }} / {{ t('dashboard.output') }}: {{ formatTokens(stats.total_output_tokens) }}
|
||||
{{ t('dashboard.input') }}: {{ formatTokens(stats.total_input_tokens) }} /
|
||||
{{ t('dashboard.output') }}: {{ formatTokens(stats.total_output_tokens) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -122,19 +221,35 @@
|
||||
<!-- Performance (RPM/TPM) -->
|
||||
<div class="card p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-violet-100 dark:bg-violet-900/30">
|
||||
<svg class="w-5 h-5 text-violet-600 dark:text-violet-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
<div class="rounded-lg bg-violet-100 p-2 dark:bg-violet-900/30">
|
||||
<svg
|
||||
class="h-5 w-5 text-violet-600 dark:text-violet-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 10V3L4 14h7v7l9-11h-7z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">{{ t('dashboard.performance') }}</p>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('dashboard.performance') }}
|
||||
</p>
|
||||
<div class="flex items-baseline gap-2">
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">{{ formatTokens(stats.rpm) }}</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">
|
||||
{{ formatTokens(stats.rpm) }}
|
||||
</p>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">RPM</span>
|
||||
</div>
|
||||
<div class="flex items-baseline gap-2">
|
||||
<p class="text-sm font-semibold text-violet-600 dark:text-violet-400">{{ formatTokens(stats.tpm) }}</p>
|
||||
<p class="text-sm font-semibold text-violet-600 dark:text-violet-400">
|
||||
{{ formatTokens(stats.tpm) }}
|
||||
</p>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">TPM</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -144,15 +259,31 @@
|
||||
<!-- Avg Response Time -->
|
||||
<div class="card p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-rose-100 dark:bg-rose-900/30">
|
||||
<svg class="w-5 h-5 text-rose-600 dark:text-rose-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<div class="rounded-lg bg-rose-100 p-2 dark:bg-rose-900/30">
|
||||
<svg
|
||||
class="h-5 w-5 text-rose-600 dark:text-rose-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">{{ t('dashboard.avgResponse') }}</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">{{ formatDuration(stats.average_duration_ms) }}</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">{{ t('dashboard.averageTime') }}</p>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('dashboard.avgResponse') }}
|
||||
</p>
|
||||
<p class="text-xl font-bold text-gray-900 dark:text-white">
|
||||
{{ formatDuration(stats.average_duration_ms) }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('dashboard.averageTime') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -164,15 +295,19 @@
|
||||
<div class="card p-4">
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('dashboard.timeRange') }}:</span>
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>{{ t('dashboard.timeRange') }}:</span
|
||||
>
|
||||
<DateRangePicker
|
||||
v-model:start-date="startDate"
|
||||
v-model:end-date="endDate"
|
||||
@change="onDateRangeChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 ml-auto">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('dashboard.granularity') }}:</span>
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>{{ t('dashboard.granularity') }}:</span
|
||||
>
|
||||
<div class="w-28">
|
||||
<Select
|
||||
v-model="granularity"
|
||||
@@ -188,32 +323,58 @@
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
<!-- Model Distribution Chart -->
|
||||
<div class="card p-4">
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white mb-4">{{ t('dashboard.modelDistribution') }}</h3>
|
||||
<h3 class="mb-4 text-sm font-semibold text-gray-900 dark:text-white">
|
||||
{{ t('dashboard.modelDistribution') }}
|
||||
</h3>
|
||||
<div class="flex items-center gap-6">
|
||||
<div class="w-48 h-48">
|
||||
<Doughnut v-if="modelChartData" :data="modelChartData" :options="doughnutOptions" />
|
||||
<div v-else class="flex items-center justify-center h-full text-gray-500 dark:text-gray-400 text-sm">
|
||||
<div class="h-48 w-48">
|
||||
<Doughnut
|
||||
v-if="modelChartData"
|
||||
:data="modelChartData"
|
||||
:options="doughnutOptions"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="flex h-full items-center justify-center text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
{{ t('dashboard.noDataAvailable') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 max-h-48 overflow-y-auto">
|
||||
<div class="max-h-48 flex-1 overflow-y-auto">
|
||||
<table class="w-full text-xs">
|
||||
<thead>
|
||||
<tr class="text-gray-500 dark:text-gray-400">
|
||||
<th class="text-left pb-2">{{ t('dashboard.model') }}</th>
|
||||
<th class="text-right pb-2">{{ t('dashboard.requests') }}</th>
|
||||
<th class="text-right pb-2">{{ t('dashboard.tokens') }}</th>
|
||||
<th class="text-right pb-2">{{ t('dashboard.actual') }}</th>
|
||||
<th class="text-right pb-2">{{ t('dashboard.standard') }}</th>
|
||||
<th class="pb-2 text-left">{{ t('dashboard.model') }}</th>
|
||||
<th class="pb-2 text-right">{{ t('dashboard.requests') }}</th>
|
||||
<th class="pb-2 text-right">{{ t('dashboard.tokens') }}</th>
|
||||
<th class="pb-2 text-right">{{ t('dashboard.actual') }}</th>
|
||||
<th class="pb-2 text-right">{{ t('dashboard.standard') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="model in modelStats" :key="model.model" class="border-t border-gray-100 dark:border-gray-700">
|
||||
<td class="py-1.5 text-gray-900 dark:text-white font-medium truncate max-w-[100px]" :title="model.model">{{ model.model }}</td>
|
||||
<td class="py-1.5 text-right text-gray-600 dark:text-gray-400">{{ formatNumber(model.requests) }}</td>
|
||||
<td class="py-1.5 text-right text-gray-600 dark:text-gray-400">{{ formatTokens(model.total_tokens) }}</td>
|
||||
<td class="py-1.5 text-right text-green-600 dark:text-green-400">${{ formatCost(model.actual_cost) }}</td>
|
||||
<td class="py-1.5 text-right text-gray-400 dark:text-gray-500">${{ formatCost(model.cost) }}</td>
|
||||
<tr
|
||||
v-for="model in modelStats"
|
||||
:key="model.model"
|
||||
class="border-t border-gray-100 dark:border-gray-700"
|
||||
>
|
||||
<td
|
||||
class="max-w-[100px] truncate py-1.5 font-medium text-gray-900 dark:text-white"
|
||||
:title="model.model"
|
||||
>
|
||||
{{ model.model }}
|
||||
</td>
|
||||
<td class="py-1.5 text-right text-gray-600 dark:text-gray-400">
|
||||
{{ formatNumber(model.requests) }}
|
||||
</td>
|
||||
<td class="py-1.5 text-right text-gray-600 dark:text-gray-400">
|
||||
{{ formatTokens(model.total_tokens) }}
|
||||
</td>
|
||||
<td class="py-1.5 text-right text-green-600 dark:text-green-400">
|
||||
${{ formatCost(model.actual_cost) }}
|
||||
</td>
|
||||
<td class="py-1.5 text-right text-gray-400 dark:text-gray-500">
|
||||
${{ formatCost(model.cost) }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -223,10 +384,15 @@
|
||||
|
||||
<!-- Token Usage Trend Chart -->
|
||||
<div class="card p-4">
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white mb-4">{{ t('dashboard.tokenUsageTrend') }}</h3>
|
||||
<h3 class="mb-4 text-sm font-semibold text-gray-900 dark:text-white">
|
||||
{{ t('dashboard.tokenUsageTrend') }}
|
||||
</h3>
|
||||
<div class="h-48">
|
||||
<Line v-if="trendChartData" :data="trendChartData" :options="lineOptions" />
|
||||
<div v-else class="flex items-center justify-center h-full text-gray-500 dark:text-gray-400 text-sm">
|
||||
<div
|
||||
v-else
|
||||
class="flex h-full items-center justify-center text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
{{ t('dashboard.noDataAvailable') }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -239,8 +405,12 @@
|
||||
<!-- Recent Usage - Takes 2 columns -->
|
||||
<div class="lg:col-span-2">
|
||||
<div class="card">
|
||||
<div class="px-6 py-4 border-b border-gray-100 dark:border-dark-700 flex items-center justify-between">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">{{ t('dashboard.recentUsage') }}</h2>
|
||||
<div
|
||||
class="flex items-center justify-between border-b border-gray-100 px-6 py-4 dark:border-dark-700"
|
||||
>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{{ t('dashboard.recentUsage') }}
|
||||
</h2>
|
||||
<span class="badge badge-gray">{{ t('dashboard.last7Days') }}</span>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
@@ -257,16 +427,30 @@
|
||||
<div
|
||||
v-for="log in recentUsage"
|
||||
:key="log.id"
|
||||
class="flex items-center justify-between p-4 rounded-xl bg-gray-50 dark:bg-dark-800/50 hover:bg-gray-100 dark:hover:bg-dark-800 transition-colors"
|
||||
class="flex items-center justify-between rounded-xl bg-gray-50 p-4 transition-colors hover:bg-gray-100 dark:bg-dark-800/50 dark:hover:bg-dark-800"
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-10 h-10 rounded-xl bg-primary-100 dark:bg-primary-900/30 flex items-center justify-center">
|
||||
<svg class="w-5 h-5 text-primary-600 dark:text-primary-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0112 15a9.065 9.065 0 00-6.23.693L5 14.5m14.8.8l1.402 1.402c1.232 1.232.65 3.318-1.067 3.611A48.309 48.309 0 0112 21c-2.773 0-5.491-.235-8.135-.687-1.718-.293-2.3-2.379-1.067-3.61L5 14.5" />
|
||||
<div
|
||||
class="flex h-10 w-10 items-center justify-center rounded-xl bg-primary-100 dark:bg-primary-900/30"
|
||||
>
|
||||
<svg
|
||||
class="h-5 w-5 text-primary-600 dark:text-primary-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0112 15a9.065 9.065 0 00-6.23.693L5 14.5m14.8.8l1.402 1.402c1.232 1.232.65 3.318-1.067 3.611A48.309 48.309 0 0112 21c-2.773 0-5.491-.235-8.135-.687-1.718-.293-2.3-2.379-1.067-3.61L5 14.5"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white">{{ log.model }}</p>
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{ log.model }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 dark:text-dark-400">
|
||||
{{ formatDate(log.created_at) }}
|
||||
</p>
|
||||
@@ -274,8 +458,12 @@
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<p class="text-sm font-semibold">
|
||||
<span class="text-green-600 dark:text-green-400" title="实际扣除">${{ formatCost(log.actual_cost) }}</span>
|
||||
<span class="text-gray-400 dark:text-gray-500 font-normal" title="标准计费"> / ${{ formatCost(log.total_cost) }}</span>
|
||||
<span class="text-green-600 dark:text-green-400" title="实际扣除"
|
||||
>${{ formatCost(log.actual_cost) }}</span
|
||||
>
|
||||
<span class="font-normal text-gray-400 dark:text-gray-500" title="标准计费">
|
||||
/ ${{ formatCost(log.total_cost) }}</span
|
||||
>
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 dark:text-dark-400">
|
||||
{{ (log.input_tokens + log.output_tokens).toLocaleString() }} tokens
|
||||
@@ -285,11 +473,21 @@
|
||||
|
||||
<router-link
|
||||
to="/usage"
|
||||
class="flex items-center justify-center gap-2 py-3 text-sm font-medium text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 transition-colors"
|
||||
class="flex items-center justify-center gap-2 py-3 text-sm font-medium text-primary-600 transition-colors hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300"
|
||||
>
|
||||
{{ t('dashboard.viewAllUsage') }}
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3" />
|
||||
<svg
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"
|
||||
/>
|
||||
</svg>
|
||||
</router-link>
|
||||
</div>
|
||||
@@ -300,61 +498,141 @@
|
||||
<!-- Quick Actions - Takes 1 column -->
|
||||
<div class="lg:col-span-1">
|
||||
<div class="card">
|
||||
<div class="px-6 py-4 border-b border-gray-100 dark:border-dark-700">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">{{ t('dashboard.quickActions') }}</h2>
|
||||
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{{ t('dashboard.quickActions') }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="p-4 space-y-3">
|
||||
<div class="space-y-3 p-4">
|
||||
<button
|
||||
@click="navigateTo('/keys')"
|
||||
class="w-full flex items-center gap-4 p-4 rounded-xl bg-gray-50 dark:bg-dark-800/50 hover:bg-gray-100 dark:hover:bg-dark-800 transition-all duration-200 group text-left"
|
||||
class="group flex w-full items-center gap-4 rounded-xl bg-gray-50 p-4 text-left transition-all duration-200 hover:bg-gray-100 dark:bg-dark-800/50 dark:hover:bg-dark-800"
|
||||
>
|
||||
<div class="flex-shrink-0 w-12 h-12 rounded-xl bg-primary-100 dark:bg-primary-900/30 flex items-center justify-center group-hover:scale-105 transition-transform">
|
||||
<svg class="w-6 h-6 text-primary-600 dark:text-primary-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z" />
|
||||
<div
|
||||
class="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-xl bg-primary-100 transition-transform group-hover:scale-105 dark:bg-primary-900/30"
|
||||
>
|
||||
<svg
|
||||
class="h-6 w-6 text-primary-600 dark:text-primary-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white">{{ t('dashboard.createApiKey') }}</p>
|
||||
<p class="text-xs text-gray-500 dark:text-dark-400">{{ t('dashboard.generateNewKey') }}</p>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{ t('dashboard.createApiKey') }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 dark:text-dark-400">
|
||||
{{ t('dashboard.generateNewKey') }}
|
||||
</p>
|
||||
</div>
|
||||
<svg class="w-5 h-5 text-gray-400 dark:text-dark-500 group-hover:text-primary-500 transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
|
||||
<svg
|
||||
class="h-5 w-5 text-gray-400 transition-colors group-hover:text-primary-500 dark:text-dark-500"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M8.25 4.5l7.5 7.5-7.5 7.5"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="navigateTo('/usage')"
|
||||
class="w-full flex items-center gap-4 p-4 rounded-xl bg-gray-50 dark:bg-dark-800/50 hover:bg-gray-100 dark:hover:bg-dark-800 transition-all duration-200 group text-left"
|
||||
class="group flex w-full items-center gap-4 rounded-xl bg-gray-50 p-4 text-left transition-all duration-200 hover:bg-gray-100 dark:bg-dark-800/50 dark:hover:bg-dark-800"
|
||||
>
|
||||
<div class="flex-shrink-0 w-12 h-12 rounded-xl bg-emerald-100 dark:bg-emerald-900/30 flex items-center justify-center group-hover:scale-105 transition-transform">
|
||||
<svg class="w-6 h-6 text-emerald-600 dark:text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z" />
|
||||
<div
|
||||
class="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-xl bg-emerald-100 transition-transform group-hover:scale-105 dark:bg-emerald-900/30"
|
||||
>
|
||||
<svg
|
||||
class="h-6 w-6 text-emerald-600 dark:text-emerald-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white">{{ t('dashboard.viewUsage') }}</p>
|
||||
<p class="text-xs text-gray-500 dark:text-dark-400">{{ t('dashboard.checkDetailedLogs') }}</p>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{ t('dashboard.viewUsage') }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 dark:text-dark-400">
|
||||
{{ t('dashboard.checkDetailedLogs') }}
|
||||
</p>
|
||||
</div>
|
||||
<svg class="w-5 h-5 text-gray-400 dark:text-dark-500 group-hover:text-emerald-500 transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
|
||||
<svg
|
||||
class="h-5 w-5 text-gray-400 transition-colors group-hover:text-emerald-500 dark:text-dark-500"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M8.25 4.5l7.5 7.5-7.5 7.5"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="navigateTo('/redeem')"
|
||||
class="w-full flex items-center gap-4 p-4 rounded-xl bg-gray-50 dark:bg-dark-800/50 hover:bg-gray-100 dark:hover:bg-dark-800 transition-all duration-200 group text-left"
|
||||
class="group flex w-full items-center gap-4 rounded-xl bg-gray-50 p-4 text-left transition-all duration-200 hover:bg-gray-100 dark:bg-dark-800/50 dark:hover:bg-dark-800"
|
||||
>
|
||||
<div class="flex-shrink-0 w-12 h-12 rounded-xl bg-amber-100 dark:bg-amber-900/30 flex items-center justify-center group-hover:scale-105 transition-transform">
|
||||
<svg class="w-6 h-6 text-amber-600 dark:text-amber-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 11.25v8.25a1.5 1.5 0 01-1.5 1.5H5.25a1.5 1.5 0 01-1.5-1.5v-8.25M12 4.875A2.625 2.625 0 109.375 7.5H12m0-2.625V7.5m0-2.625A2.625 2.625 0 1114.625 7.5H12m0 0V21m-8.625-9.75h18c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125h-18c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z" />
|
||||
<div
|
||||
class="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-xl bg-amber-100 transition-transform group-hover:scale-105 dark:bg-amber-900/30"
|
||||
>
|
||||
<svg
|
||||
class="h-6 w-6 text-amber-600 dark:text-amber-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M21 11.25v8.25a1.5 1.5 0 01-1.5 1.5H5.25a1.5 1.5 0 01-1.5-1.5v-8.25M12 4.875A2.625 2.625 0 109.375 7.5H12m0-2.625V7.5m0-2.625A2.625 2.625 0 1114.625 7.5H12m0 0V21m-8.625-9.75h18c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125h-18c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white">{{ t('dashboard.redeemCode') }}</p>
|
||||
<p class="text-xs text-gray-500 dark:text-dark-400">{{ t('dashboard.addBalanceWithCode') }}</p>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{ t('dashboard.redeemCode') }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 dark:text-dark-400">
|
||||
{{ t('dashboard.addBalanceWithCode') }}
|
||||
</p>
|
||||
</div>
|
||||
<svg class="w-5 h-5 text-gray-400 dark:text-dark-500 group-hover:text-amber-500 transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
|
||||
<svg
|
||||
class="h-5 w-5 text-gray-400 transition-colors group-hover:text-amber-500 dark:text-dark-500"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M8.25 4.5l7.5 7.5-7.5 7.5"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
@@ -431,7 +709,7 @@ const endDate = ref('')
|
||||
// Granularity options for Select component
|
||||
const granularityOptions = computed(() => [
|
||||
{ value: 'day', label: t('dashboard.day') },
|
||||
{ value: 'hour', label: t('dashboard.hour') },
|
||||
{ value: 'hour', label: t('dashboard.hour') }
|
||||
])
|
||||
|
||||
// Dark mode detection
|
||||
@@ -445,7 +723,7 @@ const chartColors = computed(() => ({
|
||||
grid: isDarkMode.value ? '#374151' : '#e5e7eb',
|
||||
input: '#3b82f6',
|
||||
output: '#10b981',
|
||||
cache: '#f59e0b',
|
||||
cache: '#f59e0b'
|
||||
}))
|
||||
|
||||
// Doughnut chart options
|
||||
@@ -454,7 +732,7 @@ const doughnutOptions = computed(() => ({
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
@@ -463,10 +741,10 @@ const doughnutOptions = computed(() => ({
|
||||
const total = context.dataset.data.reduce((a: number, b: number) => a + b, 0)
|
||||
const percentage = ((value / total) * 100).toFixed(1)
|
||||
return `${context.label}: ${formatTokens(value)} (${percentage}%)`
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
// Line chart options
|
||||
@@ -475,7 +753,7 @@ const lineOptions = computed(() => ({
|
||||
maintainAspectRatio: false,
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'index' as const,
|
||||
mode: 'index' as const
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
@@ -486,9 +764,9 @@ const lineOptions = computed(() => ({
|
||||
pointStyle: 'circle',
|
||||
padding: 15,
|
||||
font: {
|
||||
size: 11,
|
||||
},
|
||||
},
|
||||
size: 11
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
@@ -502,35 +780,35 @@ const lineOptions = computed(() => ({
|
||||
return `Actual: $${formatCost(data.actual_cost)} | Standard: $${formatCost(data.cost)}`
|
||||
}
|
||||
return ''
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
grid: {
|
||||
color: chartColors.value.grid,
|
||||
color: chartColors.value.grid
|
||||
},
|
||||
ticks: {
|
||||
color: chartColors.value.text,
|
||||
font: {
|
||||
size: 10,
|
||||
},
|
||||
},
|
||||
size: 10
|
||||
}
|
||||
}
|
||||
},
|
||||
y: {
|
||||
grid: {
|
||||
color: chartColors.value.grid,
|
||||
color: chartColors.value.grid
|
||||
},
|
||||
ticks: {
|
||||
color: chartColors.value.text,
|
||||
font: {
|
||||
size: 10,
|
||||
size: 10
|
||||
},
|
||||
callback: (value: string | number) => formatTokens(Number(value)),
|
||||
},
|
||||
},
|
||||
},
|
||||
callback: (value: string | number) => formatTokens(Number(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
// Model chart data
|
||||
@@ -538,17 +816,27 @@ const modelChartData = computed(() => {
|
||||
if (!modelStats.value?.length) return null
|
||||
|
||||
const colors = [
|
||||
'#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6',
|
||||
'#ec4899', '#14b8a6', '#f97316', '#6366f1', '#84cc16'
|
||||
'#3b82f6',
|
||||
'#10b981',
|
||||
'#f59e0b',
|
||||
'#ef4444',
|
||||
'#8b5cf6',
|
||||
'#ec4899',
|
||||
'#14b8a6',
|
||||
'#f97316',
|
||||
'#6366f1',
|
||||
'#84cc16'
|
||||
]
|
||||
|
||||
return {
|
||||
labels: modelStats.value.map(m => m.model),
|
||||
datasets: [{
|
||||
data: modelStats.value.map(m => m.total_tokens),
|
||||
backgroundColor: colors.slice(0, modelStats.value.length),
|
||||
borderWidth: 0,
|
||||
}],
|
||||
labels: modelStats.value.map((m) => m.model),
|
||||
datasets: [
|
||||
{
|
||||
data: modelStats.value.map((m) => m.total_tokens),
|
||||
backgroundColor: colors.slice(0, modelStats.value.length),
|
||||
borderWidth: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
@@ -557,33 +845,33 @@ const trendChartData = computed(() => {
|
||||
if (!trendData.value?.length) return null
|
||||
|
||||
return {
|
||||
labels: trendData.value.map(d => d.date),
|
||||
labels: trendData.value.map((d) => d.date),
|
||||
datasets: [
|
||||
{
|
||||
label: 'Input',
|
||||
data: trendData.value.map(d => d.input_tokens),
|
||||
data: trendData.value.map((d) => d.input_tokens),
|
||||
borderColor: chartColors.value.input,
|
||||
backgroundColor: `${chartColors.value.input}20`,
|
||||
fill: true,
|
||||
tension: 0.3,
|
||||
tension: 0.3
|
||||
},
|
||||
{
|
||||
label: 'Output',
|
||||
data: trendData.value.map(d => d.output_tokens),
|
||||
data: trendData.value.map((d) => d.output_tokens),
|
||||
borderColor: chartColors.value.output,
|
||||
backgroundColor: `${chartColors.value.output}20`,
|
||||
fill: true,
|
||||
tension: 0.3,
|
||||
tension: 0.3
|
||||
},
|
||||
{
|
||||
label: 'Cache',
|
||||
data: trendData.value.map(d => d.cache_tokens),
|
||||
data: trendData.value.map((d) => d.cache_tokens),
|
||||
borderColor: chartColors.value.cache,
|
||||
backgroundColor: `${chartColors.value.cache}20`,
|
||||
fill: true,
|
||||
tension: 0.3,
|
||||
},
|
||||
],
|
||||
tension: 0.3
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
@@ -641,7 +929,11 @@ const navigateTo = (path: string) => {
|
||||
}
|
||||
|
||||
// Date range change handler
|
||||
const onDateRangeChange = (range: { startDate: string; endDate: string; preset: string | null }) => {
|
||||
const onDateRangeChange = (range: {
|
||||
startDate: string
|
||||
endDate: string
|
||||
preset: string | null
|
||||
}) => {
|
||||
const start = new Date(range.startDate)
|
||||
const end = new Date(range.endDate)
|
||||
const daysDiff = Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24))
|
||||
@@ -685,12 +977,12 @@ const loadChartData = async () => {
|
||||
const params = {
|
||||
start_date: startDate.value,
|
||||
end_date: endDate.value,
|
||||
granularity: granularity.value,
|
||||
granularity: granularity.value
|
||||
}
|
||||
|
||||
const [trendResponse, modelResponse] = await Promise.all([
|
||||
usageAPI.getDashboardTrend(params),
|
||||
usageAPI.getDashboardModels({ start_date: startDate.value, end_date: endDate.value }),
|
||||
usageAPI.getDashboardModels({ start_date: startDate.value, end_date: endDate.value })
|
||||
])
|
||||
|
||||
// Ensure we always have arrays, even if API returns null
|
||||
@@ -731,7 +1023,7 @@ watch(isDarkMode, () => {
|
||||
<style scoped>
|
||||
/* Compact Select styling for dashboard */
|
||||
:deep(.select-trigger) {
|
||||
@apply px-3 py-1.5 text-sm rounded-lg;
|
||||
@apply rounded-lg px-3 py-1.5 text-sm;
|
||||
}
|
||||
|
||||
:deep(.select-dropdown) {
|
||||
|
||||
Reference in New Issue
Block a user