Merge branch 'fawney19:main' into main

This commit is contained in:
ZheFox
2026-05-18 13:13:37 +08:00
committed by GitHub
12 changed files with 321 additions and 286 deletions

View File

@@ -58,6 +58,11 @@ ADMIN_USERNAME=admin
# AETHER_GATEWAY_AUTO_PREPARE_DATABASE=true
# PostgreSQL 连接池配置(默认适合单实例/小型部署;高并发可按需调大)
# 推荐计算方式(单实例):
# MAX = CPU 核数 × 10AI 网关偏 IO 等待,可激进些;纯 OLTP 用 × 4
# MIN = MAX × 0.2(保留常驻连接应对突发流量,避免冷启动握手开销)
# 多实例部署时请按 实例数 × MAX 控制总和PG 端 max_connections 至少为该总和 + 20 余量
# AETHER_GATEWAY_DATA_POSTGRES_MIN_CONNECTIONS=1
# AETHER_GATEWAY_DATA_POSTGRES_MAX_CONNECTIONS=20
# AETHER_GATEWAY_DATA_POSTGRES_IDLE_TIMEOUT_MS=30000
# AETHER_GATEWAY_DATA_POSTGRES_STATEMENT_CACHE_CAPACITY=100
# AETHER_GATEWAY_DATA_POSTGRES_ACQUIRE_TIMEOUT_MS=3000

View File

@@ -2,10 +2,6 @@
import { PopoverTrigger } from 'radix-vue'
import { useAttrs } from 'vue'
defineOptions({
inheritAttrs: false,
})
withDefaults(defineProps<{
asChild?: boolean
as?: string
@@ -14,6 +10,10 @@ withDefaults(defineProps<{
as: 'button',
})
defineOptions({
inheritAttrs: false,
})
const attrs = useAttrs()
</script>

View File

@@ -211,7 +211,6 @@
两次输入的密码不一致
</p>
</div>
</form>
<!-- 登录链接 -->

View File

@@ -17,6 +17,22 @@
<script setup lang="ts">
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
const props = withDefaults(defineProps<{
modelValue?: string
siteKey: string
action?: string
disabled?: boolean
}>(), {
modelValue: '',
action: undefined,
disabled: false,
})
const emit = defineEmits<{
'update:modelValue': [value: string]
error: [message: string]
}>()
const TURNSTILE_SCRIPT_URL = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit'
type TurnstileWidgetId = string
@@ -46,22 +62,6 @@ declare global {
}
}
const props = withDefaults(defineProps<{
modelValue?: string
siteKey: string
action?: string
disabled?: boolean
}>(), {
modelValue: '',
action: undefined,
disabled: false,
})
const emit = defineEmits<{
'update:modelValue': [value: string]
error: [message: string]
}>()
const containerRef = ref<HTMLElement | null>(null)
const widgetId = ref<TurnstileWidgetId | null>(null)
const errorMessage = ref('')

View File

@@ -28,7 +28,10 @@
{{ manualGlobalModelMode ? '选择已有模型' : '手动添加' }}
</Button>
</div>
<div v-if="!manualGlobalModelMode" class="space-y-2">
<div
v-if="!manualGlobalModelMode"
class="space-y-2"
>
<Select
:model-value="form.global_model_id"
:disabled="loadingGlobalModels"
@@ -48,10 +51,16 @@
</SelectContent>
</Select>
</div>
<div v-else class="rounded-lg border border-border/60 bg-muted/20 p-3 space-y-3">
<div
v-else
class="rounded-lg border border-border/60 bg-muted/20 p-3 space-y-3"
>
<div class="grid grid-cols-2 gap-3">
<div class="space-y-1.5">
<Label for="manual-global-model-name" class="text-xs">模型ID *</Label>
<Label
for="manual-global-model-name"
class="text-xs"
>模型ID *</Label>
<Input
id="manual-global-model-name"
v-model="form.manual_global_model_name"
@@ -60,7 +69,10 @@
/>
</div>
<div class="space-y-1.5">
<Label for="manual-global-model-display-name" class="text-xs">显示名称</Label>
<Label
for="manual-global-model-display-name"
class="text-xs"
>显示名称</Label>
<Input
id="manual-global-model-display-name"
v-model="form.manual_global_model_display_name"
@@ -79,7 +91,10 @@
没有可选择的本地全局模型可以切换到手动添加继续保存
</p>
<div class="space-y-1.5">
<Label for="provider-model-name" class="text-xs">Provider 模型名 *</Label>
<Label
for="provider-model-name"
class="text-xs"
>Provider 模型名 *</Label>
<Input
id="provider-model-name"
v-model="form.provider_model_name"

View File

@@ -293,7 +293,10 @@
</div>
<!-- 桌面端表格视图 -->
<Table :class="['hidden md:table table-fixed w-full', isAdmin ? 'min-w-[1120px]' : 'min-w-[960px]']">
<Table
class="hidden md:table table-fixed w-full"
:class="[isAdmin ? 'min-w-[1120px]' : 'min-w-[960px]']"
>
<colgroup v-if="isAdmin">
<col class="w-[8%]">
<col class="w-[12%]">
@@ -346,7 +349,8 @@
密钥
</TableHead>
<SortableTableHead
:class="['h-12 font-semibold', isAdmin ? 'w-[14%]' : 'w-[22%]']"
class="h-12 font-semibold"
:class="[isAdmin ? 'w-[14%]' : 'w-[22%]']"
column-key="model"
:sortable="false"
:filter-active="filterModel !== '__all__'"
@@ -383,7 +387,8 @@
</template>
</SortableTableHead>
<SortableTableHead
:class="['h-12 font-semibold', isAdmin ? 'w-[15%]' : 'w-[14%]']"
class="h-12 font-semibold"
:class="[isAdmin ? 'w-[15%]' : 'w-[14%]']"
column-key="api_format"
:sortable="false"
:filter-active="filterApiFormat !== '__all__'"
@@ -495,7 +500,8 @@
</div>
</TableCell>
<TableCell
:class="['font-medium py-4', isAdmin ? 'w-[14%]' : 'w-[22%]']"
class="font-medium py-4"
:class="[isAdmin ? 'w-[14%]' : 'w-[22%]']"
:title="getModelTooltip(record)"
>
<div
@@ -582,7 +588,8 @@
</div>
</TableCell>
<TableCell
:class="['py-4', isAdmin ? 'w-[15%]' : 'w-[14%]']"
class="py-4"
:class="[isAdmin ? 'w-[15%]' : 'w-[14%]']"
:title="getApiFormatTooltip(record)"
>
<!-- 有格式转换或同族格式差异两行显示 -->
@@ -682,8 +689,8 @@
</div>
<div class="mt-0.5 grid w-full min-w-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] gap-x-1 text-xs leading-tight tabular-nums text-muted-foreground">
<span
class="justify-self-end whitespace-nowrap text-right"
:class="[
'justify-self-end whitespace-nowrap text-right',
hasPositiveTokens(getRecordCacheReadTokens(record)) ? 'text-foreground/70' : ''
]"
>
@@ -693,8 +700,8 @@
/
</span>
<span
class="justify-self-start whitespace-nowrap text-left"
:class="[
'justify-self-start whitespace-nowrap text-left',
hasPositiveTokens(getRecordCacheCreationTokens(record)) ? 'text-foreground/70' : ''
]"
>

View File

@@ -25,11 +25,11 @@ describe('parseDateLike', () => {
describe('datetime-local conversion', () => {
it('formats RFC3339 instants for datetime-local inputs using local clock fields', () => {
const date = new Date('2026-04-12T15:30:00Z')
const expected = [
const expected = `${[
date.getFullYear(),
padDatePart(date.getMonth() + 1),
padDatePart(date.getDate()),
].join('-') + `T${padDatePart(date.getHours())}:${padDatePart(date.getMinutes())}`
].join('-') }T${padDatePart(date.getHours())}:${padDatePart(date.getMinutes())}`
expect(formatDateTimeLocalInput('2026-04-12T15:30:00Z')).toBe(expected)
})

View File

@@ -511,7 +511,7 @@
<span>用户: {{ selectedTask.username }}</span>
</template>
<span class="opacity-40">|</span>
<span>{{ displayTaskSource(selectedTask) }}</span>
<span>{{ displayTaskSource(selectedTask) }}</span>
</div>
<!-- 进度条 -->
<div

View File

@@ -41,12 +41,19 @@
:class="selectedType === '__new__' ? 'bg-primary/10 text-primary' : 'text-foreground hover:bg-muted'"
@click="selectNewConfig()"
>
<div class="w-7 h-7 rounded-md flex items-center justify-center text-xs font-semibold shrink-0"
<div
class="w-7 h-7 rounded-md flex items-center justify-center text-xs font-semibold shrink-0"
:class="selectedType === '__new__' ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground'"
>+</div>
>
+
</div>
<div class="flex-1 min-w-0 text-left">
<div class="truncate font-medium text-sm">新配置</div>
<div class="text-[10px] text-muted-foreground">未保存</div>
<div class="truncate font-medium text-sm">
新配置
</div>
<div class="text-[10px] text-muted-foreground">
未保存
</div>
</div>
</button>
@@ -70,10 +77,12 @@
src="https://cdn.linux.do/uploads/default/optimized/3X/9/d/9dd49731091ce8656243f3c2b6e5d5e5a7e3e3e3_2_32x32.png"
class="absolute inset-0 w-full h-full object-cover"
@error="($event.target as HTMLImageElement).remove()"
/>
>
</div>
<div class="flex-1 min-w-0 text-left">
<div class="truncate font-medium text-sm">{{ item.display_name }}</div>
<div class="truncate font-medium text-sm">
{{ item.display_name }}
</div>
<div class="text-[10px] text-muted-foreground">
{{ item.configured ? (item.is_enabled ? '已启用' : '已禁用') : '未配置' }}
</div>
@@ -96,262 +105,261 @@
<!-- 右侧内容区 -->
<div class="flex-1 min-w-0">
<!-- 配置表单 -->
<CardSection
v-if="selectedType"
:title="selectedType === '__new__' ? '新建配置' : (selectedTypeMeta?.display_name || selectedType)"
:description="selectedType === '__new__' ? '填写后点击保存' : (configs[selectedType]?.is_enabled ? '已启用' : '已禁用')"
>
<template #actions>
<div class="flex gap-2">
<Button
size="sm"
variant="outline"
:disabled="saving || testing"
@click="handleTest"
>
{{ testing ? '测试中...' : '测试' }}
</Button>
<Button
size="sm"
:disabled="saving"
@click="handleSave"
>
{{ saving ? '保存中...' : '保存' }}
</Button>
</div>
</template>
<!-- 配置表单 -->
<CardSection
v-if="selectedType"
:title="selectedType === '__new__' ? '新建配置' : (selectedTypeMeta?.display_name || selectedType)"
:description="selectedType === '__new__' ? '填写后点击保存' : (configs[selectedType]?.is_enabled ? '已启用' : '已禁用')"
>
<template #actions>
<div class="flex gap-2">
<Button
size="sm"
variant="outline"
:disabled="saving || testing"
@click="handleTest"
<div class="space-y-6">
<!-- 新建时的 Display Name -->
<div
v-if="selectedType === '__new__'"
class="grid grid-cols-1 md:grid-cols-2 gap-4"
>
{{ testing ? '测试中...' : '测试' }}
</Button>
<Button
size="sm"
:disabled="saving"
@click="handleSave"
>
{{ saving ? '保存中...' : '保存' }}
</Button>
</div>
</template>
<div class="space-y-6">
<!-- 新建时的 Display Name -->
<div
v-if="selectedType === '__new__'"
class="grid grid-cols-1 md:grid-cols-2 gap-4"
>
<div>
<Label class="block text-sm font-medium">显示名称</Label>
<Input
v-model="form.new_display_name"
class="mt-1"
placeholder="例如My OIDC Provider"
autocomplete="off"
/>
</div>
<div>
<Label class="block text-sm font-medium">配置标识</Label>
<Input
v-model="form.new_provider_type"
class="mt-1"
placeholder="custom_oidc_work"
autocomplete="off"
@blur="normalizeNewProviderType"
/>
</div>
</div>
<!-- 凭证配置 -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<Label class="block text-sm font-medium">Client ID</Label>
<Input
v-model="form.client_id"
class="mt-1"
placeholder="client_id"
autocomplete="off"
/>
</div>
<div>
<Label class="block text-sm font-medium">Client Secret</Label>
<Input
v-model="form.client_secret"
masked
class="mt-1"
:placeholder="hasSecret ? '已设置(留空保持不变)' : '请输入 secret'"
/>
</div>
</div>
<!-- 回调地址 -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<Label class="block text-sm font-medium">Redirect URI后端回调</Label>
<Input
v-model="form.redirect_uri"
class="mt-1"
placeholder="http://localhost:8084/api/oauth/xxx/callback"
autocomplete="off"
/>
</div>
<div>
<Label class="block text-sm font-medium">前端回调页</Label>
<Input
v-model="form.frontend_callback_url"
class="mt-1"
placeholder="http://localhost:5173/auth/callback"
autocomplete="off"
/>
</div>
</div>
<!-- custom_oidc 必填端点 -->
<div
v-if="isSelectedCustomProvider"
class="grid grid-cols-1 md:grid-cols-3 gap-4"
>
<div>
<Label class="block text-sm font-medium">Authorization URL</Label>
<Input
v-model="form.authorization_url_override"
class="mt-1"
placeholder="https://example.com/oauth/authorize"
autocomplete="off"
/>
</div>
<div>
<Label class="block text-sm font-medium">Token URL</Label>
<Input
v-model="form.token_url_override"
class="mt-1"
placeholder="https://example.com/oauth/token"
autocomplete="off"
/>
</div>
<div>
<Label class="block text-sm font-medium">Userinfo URL</Label>
<Input
v-model="form.userinfo_url_override"
class="mt-1"
placeholder="https://example.com/api/user"
autocomplete="off"
/>
</div>
</div>
<!-- 高级选项折叠 -->
<details class="group">
<summary class="cursor-pointer text-sm font-medium text-muted-foreground hover:text-foreground transition-colors">
高级选项
</summary>
<div class="mt-4 space-y-4 pl-4 border-l-2 border-border">
<div>
<Label class="block text-sm font-medium">Scopes</Label>
<Label class="block text-sm font-medium">显示名称</Label>
<Input
v-model="form.scopes_input"
v-model="form.new_display_name"
class="mt-1"
:placeholder="selectedTypeMeta?.default_scopes?.join(' ') || '留空使用默认值'"
placeholder="例如My OIDC Provider"
autocomplete="off"
/>
<p class="mt-1 text-xs text-muted-foreground">
空格/逗号分隔留空使用默认值
</p>
</div>
<!-- linuxdo 的可选端点覆盖 -->
<div
v-if="!isSelectedCustomProvider"
class="grid grid-cols-1 md:grid-cols-3 gap-4"
>
<div>
<Label class="block text-sm font-medium">Authorization URL</Label>
<Input
v-model="form.authorization_url_override"
class="mt-1"
:placeholder="selectedTypeMeta?.default_authorization_url || '默认'"
autocomplete="off"
/>
</div>
<div>
<Label class="block text-sm font-medium">Token URL</Label>
<Input
v-model="form.token_url_override"
class="mt-1"
:placeholder="selectedTypeMeta?.default_token_url || '默认'"
autocomplete="off"
/>
</div>
<div>
<Label class="block text-sm font-medium">Userinfo URL</Label>
<Input
v-model="form.userinfo_url_override"
class="mt-1"
:placeholder="selectedTypeMeta?.default_userinfo_url || '默认'"
autocomplete="off"
/>
</div>
<div>
<Label class="block text-sm font-medium">配置标识</Label>
<Input
v-model="form.new_provider_type"
class="mt-1"
placeholder="custom_oidc_work"
autocomplete="off"
@blur="normalizeNewProviderType"
/>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- 凭证配置 -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<Label class="block text-sm font-medium">Client ID</Label>
<Input
v-model="form.client_id"
class="mt-1"
placeholder="client_id"
autocomplete="off"
/>
</div>
<div>
<Label class="block text-sm font-medium">Client Secret</Label>
<Input
v-model="form.client_secret"
masked
class="mt-1"
:placeholder="hasSecret ? '已设置(留空保持不变)' : '请输入 secret'"
/>
</div>
</div>
<!-- 回调地址 -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<Label class="block text-sm font-medium">Redirect URI后端回调</Label>
<Input
v-model="form.redirect_uri"
class="mt-1"
placeholder="http://localhost:8084/api/oauth/xxx/callback"
autocomplete="off"
/>
</div>
<div>
<Label class="block text-sm font-medium">前端回调页</Label>
<Input
v-model="form.frontend_callback_url"
class="mt-1"
placeholder="http://localhost:5173/auth/callback"
autocomplete="off"
/>
</div>
</div>
<!-- custom_oidc 必填端点 -->
<div
v-if="isSelectedCustomProvider"
class="grid grid-cols-1 md:grid-cols-3 gap-4"
>
<div>
<Label class="block text-sm font-medium">Authorization URL</Label>
<Input
v-model="form.authorization_url_override"
class="mt-1"
placeholder="https://example.com/oauth/authorize"
autocomplete="off"
/>
</div>
<div>
<Label class="block text-sm font-medium">Token URL</Label>
<Input
v-model="form.token_url_override"
class="mt-1"
placeholder="https://example.com/oauth/token"
autocomplete="off"
/>
</div>
<div>
<Label class="block text-sm font-medium">Userinfo URL</Label>
<Input
v-model="form.userinfo_url_override"
class="mt-1"
placeholder="https://example.com/api/user"
autocomplete="off"
/>
</div>
</div>
<!-- 高级选项折叠 -->
<details class="group">
<summary class="cursor-pointer text-sm font-medium text-muted-foreground hover:text-foreground transition-colors">
高级选项
</summary>
<div class="mt-4 space-y-4 pl-4 border-l-2 border-border">
<div>
<Label class="block text-sm font-medium">Attribute Mapping</Label>
<Textarea
v-model="form.attribute_mapping_json"
class="mt-1 font-mono text-xs"
rows="3"
placeholder="{&quot;id&quot;: &quot;user_id&quot;, &quot;username&quot;: &quot;login&quot;}"
<Label class="block text-sm font-medium">Scopes</Label>
<Input
v-model="form.scopes_input"
class="mt-1"
:placeholder="selectedTypeMeta?.default_scopes?.join(' ') || '留空使用默认值'"
autocomplete="off"
/>
</div>
<div>
<Label class="block text-sm font-medium">
{{ isSelectedCustomProvider ? 'Allowed Domains / Extra Config' : 'Extra Config' }}
</Label>
<Textarea
v-model="form.extra_config_json"
class="mt-1 font-mono text-xs"
rows="3"
:placeholder="extraConfigPlaceholder"
/>
<p
v-if="isSelectedCustomProvider"
class="mt-1 text-xs text-muted-foreground"
>
自定义 OIDC 必填填写 Authorization / Token / Userinfo URL 所属域名
<p class="mt-1 text-xs text-muted-foreground">
空格/逗号分隔留空使用默认值
</p>
</div>
</div>
</div>
</details>
</div>
<!-- 测试结果 -->
<div
v-if="lastTestResult"
class="mt-6 rounded-lg border border-border p-4 text-sm"
>
<div class="font-medium mb-2">
测试结果
<!-- linuxdo 的可选端点覆盖 -->
<div
v-if="!isSelectedCustomProvider"
class="grid grid-cols-1 md:grid-cols-3 gap-4"
>
<div>
<Label class="block text-sm font-medium">Authorization URL</Label>
<Input
v-model="form.authorization_url_override"
class="mt-1"
:placeholder="selectedTypeMeta?.default_authorization_url || '默认'"
autocomplete="off"
/>
</div>
<div>
<Label class="block text-sm font-medium">Token URL</Label>
<Input
v-model="form.token_url_override"
class="mt-1"
:placeholder="selectedTypeMeta?.default_token_url || '默认'"
autocomplete="off"
/>
</div>
<div>
<Label class="block text-sm font-medium">Userinfo URL</Label>
<Input
v-model="form.userinfo_url_override"
class="mt-1"
:placeholder="selectedTypeMeta?.default_userinfo_url || '默认'"
autocomplete="off"
/>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<Label class="block text-sm font-medium">Attribute Mapping</Label>
<Textarea
v-model="form.attribute_mapping_json"
class="mt-1 font-mono text-xs"
rows="3"
placeholder="{&quot;id&quot;: &quot;user_id&quot;, &quot;username&quot;: &quot;login&quot;}"
/>
</div>
<div>
<Label class="block text-sm font-medium">
{{ isSelectedCustomProvider ? 'Allowed Domains / Extra Config' : 'Extra Config' }}
</Label>
<Textarea
v-model="form.extra_config_json"
class="mt-1 font-mono text-xs"
rows="3"
:placeholder="extraConfigPlaceholder"
/>
<p
v-if="isSelectedCustomProvider"
class="mt-1 text-xs text-muted-foreground"
>
自定义 OIDC 必填填写 Authorization / Token / Userinfo URL 所属域名
</p>
</div>
</div>
</div>
</details>
</div>
<div class="flex flex-wrap gap-4 text-xs">
<div class="flex items-center gap-2">
<span
class="w-2 h-2 rounded-full"
:class="lastTestResult.authorization_url_reachable ? 'bg-green-500' : 'bg-red-500'"
/>
<span class="text-muted-foreground">Authorization URL</span>
<!-- 测试结果 -->
<div
v-if="lastTestResult"
class="mt-6 rounded-lg border border-border p-4 text-sm"
>
<div class="font-medium mb-2">
测试结果
</div>
<div class="flex items-center gap-2">
<div class="flex flex-wrap gap-4 text-xs">
<div class="flex items-center gap-2">
<span
class="w-2 h-2 rounded-full"
:class="lastTestResult.authorization_url_reachable ? 'bg-green-500' : 'bg-red-500'"
/>
<span class="text-muted-foreground">Authorization URL</span>
</div>
<div class="flex items-center gap-2">
<span
class="w-2 h-2 rounded-full"
:class="lastTestResult.token_url_reachable ? 'bg-green-500' : 'bg-red-500'"
/>
<span class="text-muted-foreground">Token URL</span>
</div>
<div class="flex items-center gap-2">
<span
class="w-2 h-2 rounded-full"
:class="lastTestResult.secret_status === 'likely_valid' ? 'bg-green-500' : lastTestResult.secret_status === 'invalid' ? 'bg-red-500' : 'bg-yellow-500'"
/>
<span class="text-muted-foreground">Secret: {{ lastTestResult.secret_status }}</span>
</div>
<span
class="w-2 h-2 rounded-full"
:class="lastTestResult.token_url_reachable ? 'bg-green-500' : 'bg-red-500'"
/>
<span class="text-muted-foreground">Token URL</span>
v-if="lastTestResult.details"
class="text-muted-foreground"
>
{{ lastTestResult.details }}
</span>
</div>
<div class="flex items-center gap-2">
<span
class="w-2 h-2 rounded-full"
:class="lastTestResult.secret_status === 'likely_valid' ? 'bg-green-500' : lastTestResult.secret_status === 'invalid' ? 'bg-red-500' : 'bg-yellow-500'"
/>
<span class="text-muted-foreground">Secret: {{ lastTestResult.secret_status }}</span>
</div>
<span
v-if="lastTestResult.details"
class="text-muted-foreground"
>
{{ lastTestResult.details }}
</span>
</div>
</div>
</CardSection>
</CardSection>
</div>
</div>
</PageContainer>

View File

@@ -822,7 +822,6 @@
</div>
</Card>
</div>
</div>
</template>

View File

@@ -23,7 +23,9 @@
<div>API 端点</div>
<div>推理程度</div>
<div>映射参数</div>
<div class="md:text-right">状态</div>
<div class="md:text-right">
状态
</div>
</div>
<div class="divide-y">
<div

View File

@@ -50,15 +50,15 @@
>
<UsageModelTable
:data="enhancedModelStats"
:is-admin="authStore.canAccessAdmin"
:is-admin="authStore.canAccessAdmin"
/>
<UsageProviderTable
:data="providerStats"
:is-admin="authStore.canAccessAdmin"
:is-admin="authStore.canAccessAdmin"
/>
<UsageApiFormatTable
:data="apiFormatStats"
:is-admin="authStore.canAccessAdmin"
:is-admin="authStore.canAccessAdmin"
/>
</div>
<!-- 用户模型 + API格式2 -->
@@ -68,7 +68,7 @@
>
<UsageModelTable
:data="enhancedModelStats"
:is-admin="authStore.canAccessAdmin"
:is-admin="authStore.canAccessAdmin"
/>
<UsageApiFormatTable
:data="apiFormatStats"
@@ -81,7 +81,7 @@
<UsageRecordsTable
:records="displayRecords"
:is-admin="isAdminPage"
:show-actual-cost="authStore.canAccessAdmin"
:show-actual-cost="authStore.canAccessAdmin"
:loading="isLoadingRecords"
:time-range="timeRange"
:filter-search="filterSearch"