Spaces:
Sleeping
Sleeping
github-actions[bot] commited on
Commit ·
580dda3
1
Parent(s): e2f5438
Sync from GitHub Viciy2023/Qwen2API-A@6b6dad4f42bf8a6c60bfb5e517749b0b07ecbe7f
Browse files- .gitignore +2 -0
- public/src/views/dashboard.vue +366 -2
.gitignore
CHANGED
|
@@ -10,3 +10,5 @@ pkg_dist/*
|
|
| 10 |
pkg_dist/
|
| 11 |
.idea
|
| 12 |
/public/dist
|
|
|
|
|
|
|
|
|
| 10 |
pkg_dist/
|
| 11 |
.idea
|
| 12 |
/public/dist
|
| 13 |
+
构建日志.MD
|
| 14 |
+
运行日志.MD
|
public/src/views/dashboard.vue
CHANGED
|
@@ -46,8 +46,12 @@
|
|
| 46 |
class="action-button font-bold border border-yellow-200 bg-yellow-50 text-yellow-900 px-4 py-2 rounded-xl shadow-sm hover:bg-yellow-100 hover:border-yellow-400 transition-all duration-300 transform hover:-translate-y-1 active:translate-y-0 text-center">
|
| 47 |
导出账号
|
| 48 |
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
<router-link to="/settings"
|
| 50 |
-
|
| 51 |
系统设置
|
| 52 |
</router-link>
|
| 53 |
</div>
|
|
@@ -309,6 +313,128 @@
|
|
| 309 |
<span>{{ toast.message }}</span>
|
| 310 |
</div>
|
| 311 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
</div>
|
| 313 |
</template>
|
| 314 |
|
|
@@ -342,6 +468,11 @@ const showDeleteAllConfirm = ref(false)
|
|
| 342 |
const isRefreshingAll = ref(false)
|
| 343 |
const isForceRefreshingAll = ref(false)
|
| 344 |
const refreshingTokens = ref([])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 345 |
|
| 346 |
// Toast 通知
|
| 347 |
const toast = ref({
|
|
@@ -350,6 +481,201 @@ const toast = ref({
|
|
| 350 |
type: 'success'
|
| 351 |
})
|
| 352 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 353 |
const isSelected = (email) => {
|
| 354 |
return selectedTokens.value.includes(email)
|
| 355 |
}
|
|
@@ -678,6 +1004,44 @@ const exportAccounts = async () => {
|
|
| 678 |
}
|
| 679 |
}
|
| 680 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 681 |
onMounted(() => {
|
| 682 |
getTokens()
|
| 683 |
})
|
|
@@ -1022,4 +1386,4 @@ onMounted(() => {
|
|
| 1022 |
min-height: 44px;
|
| 1023 |
}
|
| 1024 |
}
|
| 1025 |
-
</style>
|
|
|
|
| 46 |
class="action-button font-bold border border-yellow-200 bg-yellow-50 text-yellow-900 px-4 py-2 rounded-xl shadow-sm hover:bg-yellow-100 hover:border-yellow-400 transition-all duration-300 transform hover:-translate-y-1 active:translate-y-0 text-center">
|
| 47 |
导出账号
|
| 48 |
</button>
|
| 49 |
+
<button @click="openModelsPanel"
|
| 50 |
+
class="action-button font-bold border border-violet-200 bg-violet-50 text-violet-900 px-4 py-2 rounded-xl shadow-sm hover:bg-violet-100 hover:border-violet-400 transition-all duration-300 transform hover:-translate-y-1 active:translate-y-0 text-center">
|
| 51 |
+
可用模型
|
| 52 |
+
</button>
|
| 53 |
<router-link to="/settings"
|
| 54 |
+
class="action-button col-span-2 sm:col-span-1 font-bold border border-blue-200 bg-blue-50 text-blue-900 px-4 py-2 rounded-xl shadow-sm hover:bg-blue-100 hover:border-blue-400 transition-all duration-300 transform hover:-translate-y-1 active:translate-y-0 text-center">
|
| 55 |
系统设置
|
| 56 |
</router-link>
|
| 57 |
</div>
|
|
|
|
| 313 |
<span>{{ toast.message }}</span>
|
| 314 |
</div>
|
| 315 |
</div>
|
| 316 |
+
|
| 317 |
+
<!-- 可用模型面板 -->
|
| 318 |
+
<div v-if="showModelsPanel"
|
| 319 |
+
class="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50"
|
| 320 |
+
@click.self="showModelsPanel = false">
|
| 321 |
+
<div class="relative bg-white/90 backdrop-blur-lg rounded-2xl p-6 w-11/12 max-w-5xl max-h-[85vh] overflow-hidden transform transition-all duration-300 scale-100 opacity-100">
|
| 322 |
+
<div class="flex items-center justify-between mb-4">
|
| 323 |
+
<div>
|
| 324 |
+
<h2 class="text-2xl font-bold text-slate-800">可用模型</h2>
|
| 325 |
+
<p class="text-sm text-slate-500 mt-1">展示当前实例可直接调用的全部模型</p>
|
| 326 |
+
</div>
|
| 327 |
+
<button @click="showModelsPanel = false"
|
| 328 |
+
class="px-3 py-2 rounded-xl bg-slate-100 text-slate-600 hover:bg-slate-200 transition-all duration-300">
|
| 329 |
+
关闭
|
| 330 |
+
</button>
|
| 331 |
+
</div>
|
| 332 |
+
|
| 333 |
+
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 mb-4">
|
| 334 |
+
<div class="flex flex-wrap items-center gap-2 text-sm text-slate-600">
|
| 335 |
+
<span>共 {{ availableModels.length }} 个模型</span>
|
| 336 |
+
<span class="rounded-full bg-slate-100 px-3 py-1 text-xs text-slate-600">基础 {{ modelGroups.base.length }}</span>
|
| 337 |
+
<span class="rounded-full bg-amber-100 px-3 py-1 text-xs text-amber-700">Thinking {{ modelGroups.thinking.length }}</span>
|
| 338 |
+
<span class="rounded-full bg-cyan-100 px-3 py-1 text-xs text-cyan-700">Search {{ modelGroups.search.length }}</span>
|
| 339 |
+
<span class="rounded-full bg-rose-100 px-3 py-1 text-xs text-rose-700">Image {{ modelGroups.image.length }}</span>
|
| 340 |
+
<span class="rounded-full bg-indigo-100 px-3 py-1 text-xs text-indigo-700">Video {{ modelGroups.video.length }}</span>
|
| 341 |
+
<span class="rounded-full bg-emerald-100 px-3 py-1 text-xs text-emerald-700">Image Edit {{ modelGroups.imageEdit.length }}</span>
|
| 342 |
+
</div>
|
| 343 |
+
<div class="flex w-full sm:w-auto gap-2">
|
| 344 |
+
<div class="relative w-full sm:w-72">
|
| 345 |
+
<input v-model="modelKeyword"
|
| 346 |
+
type="text"
|
| 347 |
+
placeholder="搜索模型 ID"
|
| 348 |
+
class="w-full rounded-xl border border-slate-200 bg-white px-4 py-2 shadow-sm focus:border-violet-400 focus:ring-violet-400 transition-all duration-300">
|
| 349 |
+
</div>
|
| 350 |
+
<button @click="refreshModels"
|
| 351 |
+
:disabled="isLoadingModels"
|
| 352 |
+
:class="[
|
| 353 |
+
'rounded-xl px-4 py-2 text-sm font-semibold transition-all duration-300 border',
|
| 354 |
+
isLoadingModels
|
| 355 |
+
? 'bg-violet-200 text-violet-600 border-violet-200 cursor-not-allowed'
|
| 356 |
+
: 'bg-violet-600 text-white border-violet-600 hover:bg-violet-700'
|
| 357 |
+
]">
|
| 358 |
+
{{ isLoadingModels ? '刷新中...' : '刷新模型列表' }}
|
| 359 |
+
</button>
|
| 360 |
+
</div>
|
| 361 |
+
</div>
|
| 362 |
+
|
| 363 |
+
<div v-if="isLoadingModels" class="py-12 text-center text-slate-500">
|
| 364 |
+
正在加载模型列表...
|
| 365 |
+
</div>
|
| 366 |
+
|
| 367 |
+
<div v-else-if="modelsError" class="rounded-2xl border border-red-200 bg-red-50 px-4 py-8 text-center text-red-600">
|
| 368 |
+
<div class="text-lg font-semibold">模型列表加载失败</div>
|
| 369 |
+
<div class="mt-2 text-sm whitespace-pre-line">{{ modelsError }}</div>
|
| 370 |
+
<button @click="refreshModels"
|
| 371 |
+
class="mt-4 rounded-xl bg-red-600 px-4 py-2 text-sm font-semibold text-white hover:bg-red-700 transition-all duration-300">
|
| 372 |
+
重新加载
|
| 373 |
+
</button>
|
| 374 |
+
</div>
|
| 375 |
+
|
| 376 |
+
<div v-else class="max-h-[60vh] overflow-y-auto pr-2">
|
| 377 |
+
<div class="space-y-5">
|
| 378 |
+
<div v-for="group in groupedModelSections"
|
| 379 |
+
:key="group.key"
|
| 380 |
+
v-show="group.models.length"
|
| 381 |
+
class="rounded-2xl border border-slate-200 bg-white/70 p-4 shadow-sm">
|
| 382 |
+
<div class="mb-3 flex items-center justify-between gap-3">
|
| 383 |
+
<div>
|
| 384 |
+
<h3 class="text-lg font-semibold text-slate-800">{{ group.title }}</h3>
|
| 385 |
+
<p class="text-xs text-slate-500 mt-1">{{ group.description }}</p>
|
| 386 |
+
</div>
|
| 387 |
+
<span :class="group.badgeClass" class="rounded-full px-3 py-1 text-xs font-semibold">
|
| 388 |
+
{{ group.models.length }} 个
|
| 389 |
+
</span>
|
| 390 |
+
</div>
|
| 391 |
+
|
| 392 |
+
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3">
|
| 393 |
+
<div v-for="model in group.models"
|
| 394 |
+
:key="model.id"
|
| 395 |
+
class="rounded-2xl border border-slate-200 bg-white/80 px-4 py-4 shadow-sm hover:shadow-md transition-all duration-300">
|
| 396 |
+
<div class="flex items-start justify-between gap-3">
|
| 397 |
+
<div class="min-w-0 flex-1">
|
| 398 |
+
<div class="font-semibold text-slate-800 break-all">{{ model.id }}</div>
|
| 399 |
+
<div class="mt-2 text-xs text-slate-500 break-all">模型名称:{{ getModelDisplayName(model) }}</div>
|
| 400 |
+
<div class="mt-2 flex flex-wrap gap-2">
|
| 401 |
+
<span class="rounded-full bg-slate-100 px-2 py-1 text-xs text-slate-600">{{ model.owned_by || 'unknown' }}</span>
|
| 402 |
+
<span v-if="modelTagSummary(model).length"
|
| 403 |
+
class="rounded-full bg-violet-100 px-2 py-1 text-xs text-violet-700">
|
| 404 |
+
{{ modelTagSummary(model).join(' / ') }}
|
| 405 |
+
</span>
|
| 406 |
+
<span class="rounded-full bg-emerald-100 px-2 py-1 text-xs text-emerald-700">
|
| 407 |
+
推荐:{{ getModelUseCase(model) }}
|
| 408 |
+
</span>
|
| 409 |
+
</div>
|
| 410 |
+
</div>
|
| 411 |
+
</div>
|
| 412 |
+
|
| 413 |
+
<div class="mt-4 grid grid-cols-2 gap-2 sm:grid-cols-3">
|
| 414 |
+
<button @click="copyToClipboard(model.id)"
|
| 415 |
+
class="rounded-lg bg-violet-100 px-2 py-2 text-sm text-violet-700 hover:bg-violet-200 transition-all duration-300">
|
| 416 |
+
复制 ID
|
| 417 |
+
</button>
|
| 418 |
+
<button @click="copyToClipboard(getModelDisplayName(model))"
|
| 419 |
+
class="rounded-lg bg-sky-100 px-2 py-2 text-sm text-sky-700 hover:bg-sky-200 transition-all duration-300">
|
| 420 |
+
复制名字
|
| 421 |
+
</button>
|
| 422 |
+
<button @click="copyModelRequestExample(model)"
|
| 423 |
+
class="col-span-2 sm:col-span-1 rounded-lg bg-amber-100 px-2 py-2 text-sm text-amber-700 hover:bg-amber-200 transition-all duration-300">
|
| 424 |
+
复制示例
|
| 425 |
+
</button>
|
| 426 |
+
</div>
|
| 427 |
+
</div>
|
| 428 |
+
</div>
|
| 429 |
+
</div>
|
| 430 |
+
</div>
|
| 431 |
+
|
| 432 |
+
<div v-if="!filteredModels.length" class="py-12 text-center text-slate-500">
|
| 433 |
+
没有匹配的模型
|
| 434 |
+
</div>
|
| 435 |
+
</div>
|
| 436 |
+
</div>
|
| 437 |
+
</div>
|
| 438 |
</div>
|
| 439 |
</template>
|
| 440 |
|
|
|
|
| 468 |
const isRefreshingAll = ref(false)
|
| 469 |
const isForceRefreshingAll = ref(false)
|
| 470 |
const refreshingTokens = ref([])
|
| 471 |
+
const showModelsPanel = ref(false)
|
| 472 |
+
const availableModels = ref([])
|
| 473 |
+
const isLoadingModels = ref(false)
|
| 474 |
+
const modelsError = ref('')
|
| 475 |
+
const modelKeyword = ref('')
|
| 476 |
|
| 477 |
// Toast 通知
|
| 478 |
const toast = ref({
|
|
|
|
| 481 |
type: 'success'
|
| 482 |
})
|
| 483 |
|
| 484 |
+
const filteredModels = computed(() => {
|
| 485 |
+
const keyword = modelKeyword.value.trim().toLowerCase()
|
| 486 |
+
if (!keyword) {
|
| 487 |
+
return availableModels.value
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
return availableModels.value.filter(model =>
|
| 491 |
+
model.id.toLowerCase().includes(keyword)
|
| 492 |
+
)
|
| 493 |
+
})
|
| 494 |
+
|
| 495 |
+
const createModelGroupMap = (models) => {
|
| 496 |
+
const groups = {
|
| 497 |
+
base: [],
|
| 498 |
+
thinking: [],
|
| 499 |
+
search: [],
|
| 500 |
+
image: [],
|
| 501 |
+
video: [],
|
| 502 |
+
imageEdit: []
|
| 503 |
+
}
|
| 504 |
+
|
| 505 |
+
models.forEach(model => {
|
| 506 |
+
const id = model.id.toLowerCase()
|
| 507 |
+
|
| 508 |
+
if (id.includes('image-edit')) {
|
| 509 |
+
groups.imageEdit.push(model)
|
| 510 |
+
} else if (id.includes('video')) {
|
| 511 |
+
groups.video.push(model)
|
| 512 |
+
} else if (id.includes('image')) {
|
| 513 |
+
groups.image.push(model)
|
| 514 |
+
} else if (id.includes('thinking') && id.includes('search')) {
|
| 515 |
+
groups.thinking.push(model)
|
| 516 |
+
groups.search.push(model)
|
| 517 |
+
} else if (id.includes('thinking')) {
|
| 518 |
+
groups.thinking.push(model)
|
| 519 |
+
} else if (id.includes('search')) {
|
| 520 |
+
groups.search.push(model)
|
| 521 |
+
} else {
|
| 522 |
+
groups.base.push(model)
|
| 523 |
+
}
|
| 524 |
+
})
|
| 525 |
+
|
| 526 |
+
return groups
|
| 527 |
+
}
|
| 528 |
+
|
| 529 |
+
const modelGroups = computed(() => createModelGroupMap(filteredModels.value))
|
| 530 |
+
|
| 531 |
+
const groupedModelSections = computed(() => [
|
| 532 |
+
{
|
| 533 |
+
key: 'base',
|
| 534 |
+
title: '基础模型',
|
| 535 |
+
description: '适合普通对话、通用推理和默认场景',
|
| 536 |
+
badgeClass: 'bg-slate-100 text-slate-700',
|
| 537 |
+
models: modelGroups.value.base
|
| 538 |
+
},
|
| 539 |
+
{
|
| 540 |
+
key: 'thinking',
|
| 541 |
+
title: 'Thinking 模型',
|
| 542 |
+
description: '带推理输出能力,适合复杂思考场景',
|
| 543 |
+
badgeClass: 'bg-amber-100 text-amber-700',
|
| 544 |
+
models: modelGroups.value.thinking
|
| 545 |
+
},
|
| 546 |
+
{
|
| 547 |
+
key: 'search',
|
| 548 |
+
title: 'Search 模型',
|
| 549 |
+
description: '带搜索能力,适合联网或检索场景',
|
| 550 |
+
badgeClass: 'bg-cyan-100 text-cyan-700',
|
| 551 |
+
models: modelGroups.value.search
|
| 552 |
+
},
|
| 553 |
+
{
|
| 554 |
+
key: 'image',
|
| 555 |
+
title: 'Image 模型',
|
| 556 |
+
description: '适合图片理解或生图相关能力',
|
| 557 |
+
badgeClass: 'bg-rose-100 text-rose-700',
|
| 558 |
+
models: modelGroups.value.image
|
| 559 |
+
},
|
| 560 |
+
{
|
| 561 |
+
key: 'video',
|
| 562 |
+
title: 'Video 模型',
|
| 563 |
+
description: '适合视频相关能力或多模态视频处理',
|
| 564 |
+
badgeClass: 'bg-indigo-100 text-indigo-700',
|
| 565 |
+
models: modelGroups.value.video
|
| 566 |
+
},
|
| 567 |
+
{
|
| 568 |
+
key: 'imageEdit',
|
| 569 |
+
title: 'Image Edit 模型',
|
| 570 |
+
description: '适合图像编辑与改图类能力',
|
| 571 |
+
badgeClass: 'bg-emerald-100 text-emerald-700',
|
| 572 |
+
models: modelGroups.value.imageEdit
|
| 573 |
+
}
|
| 574 |
+
])
|
| 575 |
+
|
| 576 |
+
const modelTagSummary = (model) => {
|
| 577 |
+
const id = model.id.toLowerCase()
|
| 578 |
+
const tags = []
|
| 579 |
+
|
| 580 |
+
if (id.includes('thinking')) tags.push('thinking')
|
| 581 |
+
if (id.includes('search')) tags.push('search')
|
| 582 |
+
if (id.includes('image-edit')) tags.push('image-edit')
|
| 583 |
+
else if (id.includes('image')) tags.push('image')
|
| 584 |
+
if (id.includes('video')) tags.push('video')
|
| 585 |
+
|
| 586 |
+
return tags
|
| 587 |
+
}
|
| 588 |
+
|
| 589 |
+
const getModelDisplayName = (model) => {
|
| 590 |
+
return model.id
|
| 591 |
+
}
|
| 592 |
+
|
| 593 |
+
const getModelUseCase = (model) => {
|
| 594 |
+
const id = model.id.toLowerCase()
|
| 595 |
+
|
| 596 |
+
if (id.includes('coder')) {
|
| 597 |
+
return '代码生成/编程'
|
| 598 |
+
}
|
| 599 |
+
|
| 600 |
+
if (id.includes('vl') || id.includes('omni')) {
|
| 601 |
+
return '多模态理解'
|
| 602 |
+
}
|
| 603 |
+
|
| 604 |
+
if (id.includes('image-edit')) {
|
| 605 |
+
return '图片编辑'
|
| 606 |
+
}
|
| 607 |
+
|
| 608 |
+
if (id.includes('image')) {
|
| 609 |
+
return '图像生成/识图'
|
| 610 |
+
}
|
| 611 |
+
|
| 612 |
+
if (id.includes('video')) {
|
| 613 |
+
return '视频理解'
|
| 614 |
+
}
|
| 615 |
+
|
| 616 |
+
if (id.includes('thinking')) {
|
| 617 |
+
return '复杂推理'
|
| 618 |
+
}
|
| 619 |
+
|
| 620 |
+
if (id.includes('search')) {
|
| 621 |
+
return '联网搜索'
|
| 622 |
+
}
|
| 623 |
+
|
| 624 |
+
if (id.includes('flash')) {
|
| 625 |
+
return '快速响应'
|
| 626 |
+
}
|
| 627 |
+
|
| 628 |
+
return '通用对话'
|
| 629 |
+
}
|
| 630 |
+
|
| 631 |
+
const copyModelRequestExample = async (model) => {
|
| 632 |
+
const example = {
|
| 633 |
+
model: model.id,
|
| 634 |
+
messages: [
|
| 635 |
+
{
|
| 636 |
+
role: 'user',
|
| 637 |
+
content: '你好,请介绍一下这个模型的适用场景。'
|
| 638 |
+
}
|
| 639 |
+
],
|
| 640 |
+
stream: false
|
| 641 |
+
}
|
| 642 |
+
|
| 643 |
+
try {
|
| 644 |
+
await navigator.clipboard.writeText(JSON.stringify(example, null, 2))
|
| 645 |
+
showToast(`已复制 ${model.id} 的请求示例`)
|
| 646 |
+
} catch (error) {
|
| 647 |
+
console.error('复制模型请求示例失败:', error)
|
| 648 |
+
showToast('复制请求示例失败', 'error')
|
| 649 |
+
}
|
| 650 |
+
}
|
| 651 |
+
|
| 652 |
+
const getModelsErrorMessage = (error) => {
|
| 653 |
+
if (error.response) {
|
| 654 |
+
const status = error.response.status
|
| 655 |
+
const backendMessage = error.response.data?.error || error.response.data?.message
|
| 656 |
+
|
| 657 |
+
if (status === 401 || status === 403) {
|
| 658 |
+
return '当前 API Key 没有访问模型列表的权限,请检查管理员密钥或重新登录。'
|
| 659 |
+
}
|
| 660 |
+
|
| 661 |
+
if (status === 404) {
|
| 662 |
+
return '模型列表接口不存在,请确认当前服务版本是否已正确部署。'
|
| 663 |
+
}
|
| 664 |
+
|
| 665 |
+
if (status >= 500) {
|
| 666 |
+
return `服务端返回 ${status} 错误。${backendMessage ? `\n后端信息:${backendMessage}` : ''}`
|
| 667 |
+
}
|
| 668 |
+
|
| 669 |
+
return `请求失败,状态码 ${status}。${backendMessage ? `\n后端信息:${backendMessage}` : ''}`
|
| 670 |
+
}
|
| 671 |
+
|
| 672 |
+
if (error.request) {
|
| 673 |
+
return '请求已发出,但没有收到服务响应。请检查当前 Space 是否正在重启或网络是否可用。'
|
| 674 |
+
}
|
| 675 |
+
|
| 676 |
+
return `请求构造失败:${error.message}`
|
| 677 |
+
}
|
| 678 |
+
|
| 679 |
const isSelected = (email) => {
|
| 680 |
return selectedTokens.value.includes(email)
|
| 681 |
}
|
|
|
|
| 1004 |
}
|
| 1005 |
}
|
| 1006 |
|
| 1007 |
+
const getAvailableModels = async () => {
|
| 1008 |
+
isLoadingModels.value = true
|
| 1009 |
+
modelsError.value = ''
|
| 1010 |
+
|
| 1011 |
+
try {
|
| 1012 |
+
const response = await axios.get('/v1/models', {
|
| 1013 |
+
headers: {
|
| 1014 |
+
'Authorization': `Bearer ${localStorage.getItem('apiKey') || ''}`
|
| 1015 |
+
}
|
| 1016 |
+
})
|
| 1017 |
+
|
| 1018 |
+
const models = Array.isArray(response.data?.data) ? response.data.data : []
|
| 1019 |
+
availableModels.value = [...models].sort((a, b) => a.id.localeCompare(b.id))
|
| 1020 |
+
} catch (error) {
|
| 1021 |
+
console.error('获取模型列表失败:', error)
|
| 1022 |
+
modelsError.value = getModelsErrorMessage(error)
|
| 1023 |
+
} finally {
|
| 1024 |
+
isLoadingModels.value = false
|
| 1025 |
+
}
|
| 1026 |
+
}
|
| 1027 |
+
|
| 1028 |
+
const refreshModels = async () => {
|
| 1029 |
+
await getAvailableModels()
|
| 1030 |
+
|
| 1031 |
+
if (!modelsError.value) {
|
| 1032 |
+
showToast('模型列表已刷新')
|
| 1033 |
+
}
|
| 1034 |
+
}
|
| 1035 |
+
|
| 1036 |
+
const openModelsPanel = async () => {
|
| 1037 |
+
showModelsPanel.value = true
|
| 1038 |
+
modelKeyword.value = ''
|
| 1039 |
+
|
| 1040 |
+
if (!availableModels.value.length) {
|
| 1041 |
+
await getAvailableModels()
|
| 1042 |
+
}
|
| 1043 |
+
}
|
| 1044 |
+
|
| 1045 |
onMounted(() => {
|
| 1046 |
getTokens()
|
| 1047 |
})
|
|
|
|
| 1386 |
min-height: 44px;
|
| 1387 |
}
|
| 1388 |
}
|
| 1389 |
+
</style>
|