feat: 新增 OpenAI-Responses 账户管理功能和独立自动停止标记机制

## 功能新增
- 实现 OpenAI-Responses 账户服务(openaiResponsesAccountService.js)
  - 支持使用账户内置 API Key 进行请求转发
  - 实现每日额度管理和重置机制
  - 支持代理配置和优先级设置
- 实现 OpenAI-Responses 中继服务(openaiResponsesRelayService.js)
  - 处理请求转发和响应流处理
  - 自动记录使用统计信息
  - 支持流式和非流式响应
- 新增管理界面的 OpenAI-Responses 账户管理功能
  - 完整的 CRUD 操作支持
  - 实时额度监控和状态管理
  - 支持手动重置限流和每日额度

## 架构改进
- 引入独立的自动停止标记机制,区分不同原因的自动停止
  - rateLimitAutoStopped: 限流自动停止
  - fiveHourAutoStopped: 5小时限制自动停止
  - tempErrorAutoStopped: 临时错误自动停止
  - quotaAutoStopped: 额度耗尽自动停止
- 修复手动修改调度状态时自动恢复的问题
- 统一清理逻辑,防止状态冲突

## 其他优化
- getAccountUsageStats 支持不同账户类型参数
- 统一调度器支持 OpenAI-Responses 账户类型
- WebHook 通知增强,支持新账户类型的事件

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
shaw
2025-09-10 15:41:52 +08:00
parent 1c3b74f45b
commit 08946c67ea
17 changed files with 3061 additions and 238 deletions

View File

@@ -66,63 +66,359 @@
<div class="space-y-6">
<div v-if="!isEdit">
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
>平台</label
>选择平台</label
>
<div class="flex gap-4">
<label class="flex cursor-pointer items-center">
<input
v-model="form.platform"
class="mr-2 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
type="radio"
value="claude"
/>
<span class="text-sm text-gray-700 dark:text-gray-300">Claude</span>
</label>
<label class="flex cursor-pointer items-center">
<input
v-model="form.platform"
class="mr-2 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
type="radio"
value="claude-console"
/>
<span class="text-sm text-gray-700 dark:text-gray-300">Claude Console</span>
</label>
<label class="flex cursor-pointer items-center">
<input
v-model="form.platform"
class="mr-2 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
type="radio"
value="gemini"
/>
<span class="text-sm text-gray-700 dark:text-gray-300">Gemini</span>
</label>
<label class="flex cursor-pointer items-center">
<input
v-model="form.platform"
class="mr-2 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
type="radio"
value="openai"
/>
<span class="text-sm text-gray-700 dark:text-gray-300">OpenAI</span>
</label>
<label class="flex cursor-pointer items-center">
<input
v-model="form.platform"
class="mr-2 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
type="radio"
value="azure_openai"
/>
<span class="text-sm text-gray-700 dark:text-gray-300">Azure OpenAI</span>
</label>
<label class="flex cursor-pointer items-center">
<input
v-model="form.platform"
class="mr-2 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
type="radio"
value="bedrock"
/>
<span class="text-sm text-gray-700 dark:text-gray-300">Bedrock</span>
</label>
<!-- 平台分组选择器 -->
<div class="space-y-3">
<!-- 分组选择器 -->
<div class="grid grid-cols-3 gap-2">
<!-- Claude 分组 -->
<div
class="group relative cursor-pointer overflow-hidden rounded-lg border-2 transition-all duration-200"
:class="[
platformGroup === 'claude'
? 'border-indigo-500 bg-gradient-to-br from-indigo-50 to-purple-50 shadow-md dark:from-indigo-900/20 dark:to-purple-900/20'
: 'border-gray-200 bg-white hover:border-indigo-300 hover:shadow dark:border-gray-700 dark:bg-gray-800 dark:hover:border-indigo-600'
]"
@click="selectPlatformGroup('claude')"
>
<div class="p-3">
<div class="flex items-center justify-between">
<div
class="flex h-8 w-8 items-center justify-center rounded-md bg-gradient-to-br from-indigo-500 to-purple-600"
>
<i class="fas fa-brain text-sm text-white"></i>
</div>
<div
v-if="platformGroup === 'claude'"
class="flex h-5 w-5 items-center justify-center rounded-full bg-indigo-500"
>
<i class="fas fa-check text-xs text-white"></i>
</div>
</div>
<h4 class="mt-2 text-sm font-semibold text-gray-900 dark:text-gray-100">
Claude
</h4>
<p class="text-xs text-gray-600 dark:text-gray-400">Anthropic</p>
</div>
</div>
<!-- OpenAI 分组 -->
<div
class="group relative cursor-pointer overflow-hidden rounded-lg border-2 transition-all duration-200"
:class="[
platformGroup === 'openai'
? 'border-emerald-500 bg-gradient-to-br from-emerald-50 to-teal-50 shadow-md dark:from-emerald-900/20 dark:to-teal-900/20'
: 'border-gray-200 bg-white hover:border-emerald-300 hover:shadow dark:border-gray-700 dark:bg-gray-800 dark:hover:border-emerald-600'
]"
@click="selectPlatformGroup('openai')"
>
<div class="p-3">
<div class="flex items-center justify-between">
<div
class="flex h-8 w-8 items-center justify-center rounded-md bg-gradient-to-br from-emerald-500 to-teal-600"
>
<svg
class="h-5 w-5 text-white"
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.8956zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.4069-.6813zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"
/>
</svg>
</div>
<div
v-if="platformGroup === 'openai'"
class="flex h-5 w-5 items-center justify-center rounded-full bg-emerald-500"
>
<i class="fas fa-check text-xs text-white"></i>
</div>
</div>
<h4 class="mt-2 text-sm font-semibold text-gray-900 dark:text-gray-100">
OpenAI
</h4>
<p class="text-xs text-gray-600 dark:text-gray-400">GPT 系列</p>
</div>
</div>
<!-- Gemini 分组 -->
<div
class="group relative cursor-pointer overflow-hidden rounded-lg border-2 transition-all duration-200"
:class="[
platformGroup === 'gemini'
? 'border-blue-500 bg-gradient-to-br from-blue-50 to-indigo-50 shadow-md dark:from-blue-900/20 dark:to-indigo-900/20'
: 'border-gray-200 bg-white hover:border-blue-300 hover:shadow dark:border-gray-700 dark:bg-gray-800 dark:hover:border-blue-600'
]"
@click="selectPlatformGroup('gemini')"
>
<div class="p-3">
<div class="flex items-center justify-between">
<div
class="flex h-8 w-8 items-center justify-center rounded-md bg-gradient-to-br from-blue-500 to-indigo-600"
>
<i class="fab fa-google text-sm text-white"></i>
</div>
<div
v-if="platformGroup === 'gemini'"
class="flex h-5 w-5 items-center justify-center rounded-full bg-blue-500"
>
<i class="fas fa-check text-xs text-white"></i>
</div>
</div>
<h4 class="mt-2 text-sm font-semibold text-gray-900 dark:text-gray-100">
Gemini
</h4>
<p class="text-xs text-gray-600 dark:text-gray-400">Google AI</p>
</div>
</div>
</div>
<!-- 子平台选择器 -->
<div
v-if="platformGroup"
class="animate-fadeIn rounded-lg border border-gray-200 bg-gray-50 p-3 dark:border-gray-700 dark:bg-gray-800/50"
>
<p class="mb-2 text-xs font-medium text-gray-700 dark:text-gray-300">
选择具体平台类型
</p>
<div class="grid grid-cols-2 gap-2 sm:grid-cols-3">
<!-- Claude 子选项 -->
<template v-if="platformGroup === 'claude'">
<label
class="group relative flex cursor-pointer items-center rounded-md border p-2 transition-all"
:class="[
form.platform === 'claude'
? 'border-indigo-500 bg-indigo-50 dark:border-indigo-400 dark:bg-indigo-900/30'
: 'border-gray-300 bg-white hover:border-indigo-400 hover:bg-indigo-50/50 dark:border-gray-600 dark:bg-gray-700 dark:hover:border-indigo-500 dark:hover:bg-indigo-900/20'
]"
>
<input
v-model="form.platform"
class="sr-only"
type="radio"
value="claude"
/>
<div class="flex items-center gap-2">
<i class="fas fa-brain text-sm text-indigo-600 dark:text-indigo-400"></i>
<div>
<span class="block text-xs font-medium text-gray-900 dark:text-gray-100"
>Claude Code</span
>
<span class="text-xs text-gray-500 dark:text-gray-400">官方</span>
</div>
</div>
<div
v-if="form.platform === 'claude'"
class="absolute right-1 top-1 flex h-4 w-4 items-center justify-center rounded-full bg-indigo-500"
>
<i class="fas fa-check text-xs text-white"></i>
</div>
</label>
<label
class="group relative flex cursor-pointer items-center rounded-md border p-2 transition-all"
:class="[
form.platform === 'claude-console'
? 'border-purple-500 bg-purple-50 dark:border-purple-400 dark:bg-purple-900/30'
: 'border-gray-300 bg-white hover:border-purple-400 hover:bg-purple-50/50 dark:border-gray-600 dark:bg-gray-700 dark:hover:border-purple-500 dark:hover:bg-purple-900/20'
]"
>
<input
v-model="form.platform"
class="sr-only"
type="radio"
value="claude-console"
/>
<div class="flex items-center gap-2">
<i
class="fas fa-terminal text-sm text-purple-600 dark:text-purple-400"
></i>
<div>
<span class="block text-xs font-medium text-gray-900 dark:text-gray-100"
>Claude Console</span
>
<span class="text-xs text-gray-500 dark:text-gray-400">标准API</span>
</div>
</div>
<div
v-if="form.platform === 'claude-console'"
class="absolute right-1 top-1 flex h-4 w-4 items-center justify-center rounded-full bg-purple-500"
>
<i class="fas fa-check text-xs text-white"></i>
</div>
</label>
<label
class="group relative flex cursor-pointer items-center rounded-md border p-2 transition-all"
:class="[
form.platform === 'bedrock'
? 'border-orange-500 bg-orange-50 dark:border-orange-400 dark:bg-orange-900/30'
: 'border-gray-300 bg-white hover:border-orange-400 hover:bg-orange-50/50 dark:border-gray-600 dark:bg-gray-700 dark:hover:border-orange-500 dark:hover:bg-orange-900/20'
]"
>
<input
v-model="form.platform"
class="sr-only"
type="radio"
value="bedrock"
/>
<div class="flex items-center gap-2">
<i class="fab fa-aws text-sm text-orange-600 dark:text-orange-400"></i>
<div>
<span class="block text-xs font-medium text-gray-900 dark:text-gray-100"
>Bedrock</span
>
<span class="text-xs text-gray-500 dark:text-gray-400">AWS</span>
</div>
</div>
<div
v-if="form.platform === 'bedrock'"
class="absolute right-1 top-1 flex h-4 w-4 items-center justify-center rounded-full bg-orange-500"
>
<i class="fas fa-check text-xs text-white"></i>
</div>
</label>
</template>
<!-- OpenAI 子选项 -->
<template v-if="platformGroup === 'openai'">
<label
class="group relative flex cursor-pointer items-center rounded-md border p-2 transition-all"
:class="[
form.platform === 'openai'
? 'border-emerald-500 bg-emerald-50 dark:border-emerald-400 dark:bg-emerald-900/30'
: 'border-gray-300 bg-white hover:border-emerald-400 hover:bg-emerald-50/50 dark:border-gray-600 dark:bg-gray-700 dark:hover:border-emerald-500 dark:hover:bg-emerald-900/20'
]"
>
<input
v-model="form.platform"
class="sr-only"
type="radio"
value="openai"
/>
<div class="flex items-center gap-2">
<i
class="fas fa-robot text-sm text-emerald-600 dark:text-emerald-400"
></i>
<div>
<span class="block text-xs font-medium text-gray-900 dark:text-gray-100"
>Codex Cli</span
>
<span class="text-xs text-gray-500 dark:text-gray-400">官方</span>
</div>
</div>
<div
v-if="form.platform === 'openai'"
class="absolute right-1 top-1 flex h-4 w-4 items-center justify-center rounded-full bg-emerald-500"
>
<i class="fas fa-check text-xs text-white"></i>
</div>
</label>
<label
class="group relative flex cursor-pointer items-center rounded-md border p-2 transition-all"
:class="[
form.platform === 'openai-responses'
? 'border-teal-500 bg-teal-50 dark:border-teal-400 dark:bg-teal-900/30'
: 'border-gray-300 bg-white hover:border-teal-400 hover:bg-teal-50/50 dark:border-gray-600 dark:bg-gray-700 dark:hover:border-teal-500 dark:hover:bg-teal-900/20'
]"
>
<input
v-model="form.platform"
class="sr-only"
type="radio"
value="openai-responses"
/>
<div class="flex items-center gap-2">
<i class="fas fa-server text-sm text-teal-600 dark:text-teal-400"></i>
<div>
<span class="block text-xs font-medium text-gray-900 dark:text-gray-100"
>Responses</span
>
<span class="text-xs text-gray-500 dark:text-gray-400"
>Openai-Responses</span
>
</div>
</div>
<div
v-if="form.platform === 'openai-responses'"
class="absolute right-1 top-1 flex h-4 w-4 items-center justify-center rounded-full bg-teal-500"
>
<i class="fas fa-check text-xs text-white"></i>
</div>
</label>
<label
class="group relative flex cursor-pointer items-center rounded-md border p-2 transition-all"
:class="[
form.platform === 'azure_openai'
? 'border-blue-500 bg-blue-50 dark:border-blue-400 dark:bg-blue-900/30'
: 'border-gray-300 bg-white hover:border-blue-400 hover:bg-blue-50/50 dark:border-gray-600 dark:bg-gray-700 dark:hover:border-blue-500 dark:hover:bg-blue-900/20'
]"
>
<input
v-model="form.platform"
class="sr-only"
type="radio"
value="azure_openai"
/>
<div class="flex items-center gap-2">
<i class="fab fa-microsoft text-sm text-blue-600 dark:text-blue-400"></i>
<div>
<span class="block text-xs font-medium text-gray-900 dark:text-gray-100"
>Azure</span
>
<span class="text-xs text-gray-500 dark:text-gray-400"
>Azure Openai</span
>
</div>
</div>
<div
v-if="form.platform === 'azure_openai'"
class="absolute right-1 top-1 flex h-4 w-4 items-center justify-center rounded-full bg-blue-500"
>
<i class="fas fa-check text-xs text-white"></i>
</div>
</label>
</template>
<!-- Gemini 子选项 -->
<template v-if="platformGroup === 'gemini'">
<label
class="group relative flex cursor-pointer items-center rounded-md border p-2 transition-all"
:class="[
form.platform === 'gemini'
? 'border-blue-500 bg-blue-50 dark:border-blue-400 dark:bg-blue-900/30'
: 'border-gray-300 bg-white hover:border-blue-400 hover:bg-blue-50/50 dark:border-gray-600 dark:bg-gray-700 dark:hover:border-blue-500 dark:hover:bg-blue-900/20'
]"
>
<input
v-model="form.platform"
class="sr-only"
type="radio"
value="gemini"
/>
<div class="flex items-center gap-2">
<i class="fab fa-google text-sm text-blue-600 dark:text-blue-400"></i>
<div>
<span class="block text-xs font-medium text-gray-900 dark:text-gray-100"
>Gemini Cli</span
>
<span class="text-xs text-gray-500 dark:text-gray-400">官方</span>
</div>
</div>
<div
v-if="form.platform === 'gemini'"
class="absolute right-1 top-1 flex h-4 w-4 items-center justify-center rounded-full bg-blue-500"
>
<i class="fas fa-check text-xs text-white"></i>
</div>
</label>
</template>
</div>
</div>
</div>
</div>
@@ -131,7 +427,8 @@
!isEdit &&
form.platform !== 'claude-console' &&
form.platform !== 'bedrock' &&
form.platform !== 'azure_openai'
form.platform !== 'azure_openai' &&
form.platform !== 'openai-responses'
"
>
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
@@ -839,6 +1136,81 @@
</div>
</div>
<!-- OpenAI-Responses 特定字段 -->
<div v-if="form.platform === 'openai-responses' && !isEdit" class="space-y-4">
<div>
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
>API 基础地址 *</label
>
<input
v-model="form.baseApi"
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
placeholder="https://api.example.com/v1"
required
type="url"
/>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
第三方 OpenAI 兼容 API 的基础地址,不要包含具体路径
</p>
</div>
<div>
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
>API 密钥 *</label
>
<div class="relative">
<input
v-model="form.apiKey"
class="form-input w-full border-gray-300 pr-10 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
placeholder="sk-xxxxxxxxxxxx"
required
:type="showApiKey ? 'text' : 'password'"
/>
<button
class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-400"
type="button"
@click="showApiKey = !showApiKey"
>
<i :class="showApiKey ? 'fas fa-eye-slash' : 'fas fa-eye'" />
</button>
</div>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
第三方服务提供的 API 密钥
</p>
</div>
<div>
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
>自定义 User-Agent (可选)</label
>
<input
v-model="form.userAgent"
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
placeholder="留空则透传原始请求的 User-Agent"
type="text"
/>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
可选项。如果设置,所有请求将使用此 User-Agent否则透传客户端的 User-Agent
</p>
</div>
<div>
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
>限流时长(分钟)</label
>
<input
v-model.number="form.rateLimitDuration"
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
min="1"
placeholder="60"
type="number"
/>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
遇到 429 错误时的限流等待时间,默认 60 分钟
</p>
</div>
</div>
<!-- Claude 订阅类型选择 -->
<div v-if="form.platform === 'claude'">
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
@@ -1015,7 +1387,9 @@
v-if="
form.addType === 'manual' &&
form.platform !== 'claude-console' &&
form.platform !== 'bedrock'
form.platform !== 'bedrock' &&
form.platform !== 'azure_openai' &&
form.platform !== 'openai-responses'
"
class="space-y-4 rounded-lg border border-blue-200 bg-blue-50 p-4"
>
@@ -1174,7 +1548,8 @@
(form.addType === 'oauth' || form.addType === 'setup-token') &&
form.platform !== 'claude-console' &&
form.platform !== 'bedrock' &&
form.platform !== 'azure_openai'
form.platform !== 'azure_openai' &&
form.platform !== 'openai-responses'
"
class="btn btn-primary flex-1 px-6 py-3 font-semibold"
:disabled="loading"
@@ -1915,6 +2290,93 @@
</div>
</div>
<!-- OpenAI-Responses 特定字段(编辑模式)-->
<div v-if="form.platform === 'openai-responses'" class="space-y-4">
<div>
<label class="mb-3 block text-sm font-semibold text-gray-700">API 基础地址</label>
<input
v-model="form.baseApi"
class="form-input w-full"
placeholder="https://api.example.com/v1"
type="url"
/>
</div>
<div>
<label class="mb-3 block text-sm font-semibold text-gray-700">API 密钥</label>
<div class="relative">
<input
v-model="form.apiKey"
class="form-input w-full pr-10"
placeholder="留空表示不更新"
:type="showApiKey ? 'text' : 'password'"
/>
<button
class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
type="button"
@click="showApiKey = !showApiKey"
>
<i :class="showApiKey ? 'fas fa-eye-slash' : 'fas fa-eye'" />
</button>
</div>
<p class="mt-1 text-xs text-gray-500">留空表示不更新 API Key</p>
</div>
<div>
<label class="mb-3 block text-sm font-semibold text-gray-700"
>自定义 User-Agent</label
>
<input
v-model="form.userAgent"
class="form-input w-full"
placeholder="留空则透传客户端 User-Agent"
type="text"
/>
<p class="mt-1 text-xs text-gray-500">
留空时将自动使用客户端的 User-Agent仅在需要固定特定 UA 时填写
</p>
</div>
<div>
<label class="mb-3 block text-sm font-semibold text-gray-700">限流时长(分钟)</label>
<input
v-model.number="form.rateLimitDuration"
class="form-input w-full"
min="1"
placeholder="60"
type="number"
/>
<p class="mt-1 text-xs text-gray-500">遇到 429 错误时的限流等待时间,默认 60 分钟</p>
</div>
<!-- 额度管理字段 -->
<div class="grid grid-cols-2 gap-4">
<div>
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300">
每日额度限制 ($)
</label>
<input
v-model.number="form.dailyQuota"
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
min="0"
placeholder="0 表示不限制"
step="0.01"
type="number"
/>
</div>
<div>
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300">
额度重置时间
</label>
<input
v-model="form.quotaResetTime"
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
type="time"
/>
</div>
</div>
</div>
<!-- Bedrock 特定字段(编辑模式)-->
<div v-if="form.platform === 'bedrock'" class="space-y-4">
<div>
@@ -2259,6 +2721,7 @@ const show = ref(true)
// OAuth步骤
const oauthStep = ref(1)
const loading = ref(false)
const showApiKey = ref(false)
// Setup Token 相关状态
const setupTokenLoading = ref(false)
@@ -2274,6 +2737,21 @@ const clearingCache = ref(false)
// 客户端标识编辑状态(已废弃,不再需要编辑功能)
// const editingClientId = ref(false)
// 平台分组状态
const platformGroup = ref('')
// 根据现有平台确定分组
const determinePlatformGroup = (platform) => {
if (['claude', 'claude-console', 'bedrock'].includes(platform)) {
return 'claude'
} else if (['openai', 'openai-responses', 'azure_openai'].includes(platform)) {
return 'openai'
} else if (platform === 'gemini') {
return 'gemini'
}
return ''
}
// 初始化代理配置
const initProxyConfig = () => {
if (props.account?.proxy && props.account.proxy.host && props.account.proxy.port) {
@@ -2323,6 +2801,9 @@ const form = ref({
apiUrl: props.account?.apiUrl || '',
apiKey: props.account?.apiKey || '',
priority: props.account?.priority || 50,
// OpenAI-Responses 特定字段
baseApi: props.account?.baseApi || '',
rateLimitDuration: props.account?.rateLimitDuration || 60,
supportedModels: (() => {
const models = props.account?.supportedModels
if (!models) return []
@@ -2338,7 +2819,6 @@ const form = ref({
})(),
userAgent: props.account?.userAgent || '',
enableRateLimit: props.account ? props.account.rateLimitDuration > 0 : true,
rateLimitDuration: props.account?.rateLimitDuration || 60,
// 额度管理字段
dailyQuota: props.account?.dailyQuota || 0,
dailyUsage: props.account?.dailyUsage || 0,
@@ -2440,7 +2920,7 @@ const loadAccountUsage = async () => {
form.value.dailyUsage = response.dailyUsage || 0
}
} catch (error) {
console.warn('Failed to load account usage:', error)
// 静默处理使用量加载失败
}
}
@@ -2452,6 +2932,19 @@ const loadAccountUsage = async () => {
// return form.value.name?.trim()
// })
// 选择平台分组
const selectPlatformGroup = (group) => {
platformGroup.value = group
// 根据分组自动选择默认平台
if (group === 'claude') {
form.value.platform = 'claude'
} else if (group === 'openai') {
form.value.platform = 'openai'
} else if (group === 'gemini') {
form.value.platform = 'gemini'
}
}
// 下一步
const nextStep = async () => {
// 清除之前的错误
@@ -2702,14 +3195,7 @@ const handleOAuthSuccess = async (tokenInfo) => {
showToast(fullMessage, 'error', '', 8000)
// 在控制台打印完整的错误信息以便调试
console.error('账户创建失败:', {
message: errorMessage,
suggestion,
errorDetails,
errorCode: error.response?.data?.errorCode,
networkError: error.response?.data?.networkError
})
// 错误已通过 toast 显示给用户
} finally {
loading.value = false
}
@@ -2740,6 +3226,18 @@ const createAccount = async () => {
errors.value.apiKey = '请填写 API Key'
hasError = true
}
}
// OpenAI-Responses 验证
if (form.value.platform === 'openai-responses') {
if (!form.value.baseApi || form.value.baseApi.trim() === '') {
errors.value.baseApi = '请填写 API 基础地址'
hasError = true
}
if (!form.value.apiKey || form.value.apiKey.trim() === '') {
errors.value.apiKey = '请填写 API 密钥'
hasError = true
}
} else if (form.value.platform === 'bedrock') {
// Bedrock 验证
if (!form.value.accessKeyId || form.value.accessKeyId.trim() === '') {
@@ -2917,6 +3415,15 @@ const createAccount = async () => {
// 额度管理字段
data.dailyQuota = form.value.dailyQuota || 0
data.quotaResetTime = form.value.quotaResetTime || '00:00'
} else if (form.value.platform === 'openai-responses') {
// OpenAI-Responses 账户特定数据
data.baseApi = form.value.baseApi
data.apiKey = form.value.apiKey
data.userAgent = form.value.userAgent || ''
data.priority = form.value.priority || 50
data.rateLimitDuration = form.value.rateLimitDuration || 60
data.dailyQuota = form.value.dailyQuota || 0
data.quotaResetTime = form.value.quotaResetTime || '00:00'
} else if (form.value.platform === 'bedrock') {
// Bedrock 账户特定数据 - 构造 awsCredentials 对象
data.awsCredentials = {
@@ -2949,6 +3456,8 @@ const createAccount = async () => {
result = await accountsStore.createClaudeAccount(data)
} else if (form.value.platform === 'claude-console') {
result = await accountsStore.createClaudeConsoleAccount(data)
} else if (form.value.platform === 'openai-responses') {
result = await accountsStore.createOpenAIResponsesAccount(data)
} else if (form.value.platform === 'bedrock') {
result = await accountsStore.createBedrockAccount(data)
} else if (form.value.platform === 'openai') {
@@ -2984,14 +3493,7 @@ const createAccount = async () => {
showToast(fullMessage, 'error', '', 8000)
// 在控制台打印完整的错误信息以便调试
console.error('账户创建失败:', {
message: errorMessage,
suggestion,
errorDetails,
errorCode: error.response?.data?.errorCode,
networkError: error.response?.data?.networkError
})
// 错误已通过 toast 显示给用户
} finally {
loading.value = false
}
@@ -3160,6 +3662,19 @@ const updateAccount = async () => {
data.quotaResetTime = form.value.quotaResetTime || '00:00'
}
// OpenAI-Responses 特定更新
if (props.account.platform === 'openai-responses') {
data.baseApi = form.value.baseApi
if (form.value.apiKey) {
data.apiKey = form.value.apiKey
}
data.userAgent = form.value.userAgent || ''
data.priority = form.value.priority || 50
data.rateLimitDuration = form.value.rateLimitDuration || 60
data.dailyQuota = form.value.dailyQuota || 0
data.quotaResetTime = form.value.quotaResetTime || '00:00'
}
// Bedrock 特定更新
if (props.account.platform === 'bedrock') {
// 只有当有凭证变更时才构造 awsCredentials 对象
@@ -3205,6 +3720,8 @@ const updateAccount = async () => {
await accountsStore.updateClaudeAccount(props.account.id, data)
} else if (props.account.platform === 'claude-console') {
await accountsStore.updateClaudeConsoleAccount(props.account.id, data)
} else if (props.account.platform === 'openai-responses') {
await accountsStore.updateOpenAIResponsesAccount(props.account.id, data)
} else if (props.account.platform === 'bedrock') {
await accountsStore.updateBedrockAccount(props.account.id, data)
} else if (props.account.platform === 'openai') {
@@ -3240,14 +3757,7 @@ const updateAccount = async () => {
showToast(fullMessage, 'error', '', 8000)
// 在控制台打印完整的错误信息以便调试
console.error('账户更新失败:', {
message: errorMessage,
suggestion,
errorDetails,
errorCode: error.response?.data?.errorCode,
networkError: error.response?.data?.networkError
})
// 错误已通过 toast 显示给用户
} finally {
loading.value = false
}
@@ -3359,8 +3869,12 @@ watch(
() => form.value.platform,
(newPlatform) => {
// 处理添加方式的自动切换
if (newPlatform === 'claude-console' || newPlatform === 'bedrock') {
form.value.addType = 'manual' // Claude Console 和 Bedrock 只支持手动模式
if (
newPlatform === 'claude-console' ||
newPlatform === 'bedrock' ||
newPlatform === 'openai-responses'
) {
form.value.addType = 'manual' // Claude Console、Bedrock 和 OpenAI-Responses 只支持手动模式
} else if (newPlatform === 'claude') {
// 切换到 Claude 时,使用 Setup Token 作为默认方式
form.value.addType = 'setup-token'
@@ -3421,14 +3935,14 @@ watch(setupTokenAuthCode, (newValue) => {
// 成功提取授权码
setupTokenAuthCode.value = code
showToast('成功提取授权码!', 'success')
console.log('Successfully extracted authorization code from URL')
// Successfully extracted authorization code from URL
} else {
// URL 中没有 code 参数
showToast('URL 中未找到授权码参数,请检查链接是否正确', 'error')
}
} catch (error) {
// URL 解析失败
console.error('Failed to parse URL:', error)
// Failed to parse URL
showToast('链接格式错误,请检查是否为完整的 URL', 'error')
}
} else {
@@ -3658,7 +4172,7 @@ const fetchUnifiedUserAgent = async () => {
unifiedUserAgent.value = ''
}
} catch (error) {
console.warn('Failed to fetch unified User-Agent:', error)
// Failed to fetch unified User-Agent
unifiedUserAgent.value = ''
}
}
@@ -3675,7 +4189,7 @@ const clearUnifiedCache = async () => {
showToast('清除缓存失败', 'error')
}
} catch (error) {
console.error('Failed to clear unified User-Agent cache:', error)
// Failed to clear unified User-Agent cache
showToast('清除缓存失败:' + (error.message || '未知错误'), 'error')
} finally {
clearingCache.value = false
@@ -3710,6 +4224,9 @@ const handleUnifiedClientIdChange = () => {
// 组件挂载时获取统一 User-Agent 信息
onMounted(() => {
// 初始化平台分组
platformGroup.value = determinePlatformGroup(form.value.platform)
// 获取Claude Code统一User-Agent信息
fetchUnifiedUserAgent()
// 如果是编辑模式且是Claude Console账户加载使用情况
@@ -3728,3 +4245,20 @@ watch(
}
)
</script>
<style scoped>
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fadeIn {
animation: fadeIn 0.3s ease-out;
}
</style>

View File

@@ -886,31 +886,61 @@ onMounted(async () => {
availableTags.value = await apiKeysStore.fetchTags()
// 初始化账号数据
if (props.accounts) {
// 合并 OpenAI 和 OpenAI-Responses 账号
const openaiAccounts = []
if (props.accounts.openai) {
props.accounts.openai.forEach((account) => {
openaiAccounts.push({
...account,
platform: 'openai'
})
})
}
if (props.accounts.openaiResponses) {
props.accounts.openaiResponses.forEach((account) => {
openaiAccounts.push({
...account,
platform: 'openai-responses'
})
})
}
localAccounts.value = {
claude: props.accounts.claude || [],
gemini: props.accounts.gemini || [],
openai: props.accounts.openai || [],
openai: openaiAccounts,
bedrock: props.accounts.bedrock || [], // 添加 Bedrock 账号
claudeGroups: props.accounts.claudeGroups || [],
geminiGroups: props.accounts.geminiGroups || [],
openaiGroups: props.accounts.openaiGroups || []
}
}
// 自动加载账号数据
await refreshAccounts()
})
// 刷新账号列表
const refreshAccounts = async () => {
accountsLoading.value = true
try {
const [claudeData, claudeConsoleData, geminiData, openaiData, bedrockData, groupsData] =
await Promise.all([
apiClient.get('/admin/claude-accounts'),
apiClient.get('/admin/claude-console-accounts'),
apiClient.get('/admin/gemini-accounts'),
apiClient.get('/admin/openai-accounts'),
apiClient.get('/admin/bedrock-accounts'), // 添加 Bedrock 账号获取
apiClient.get('/admin/account-groups')
])
const [
claudeData,
claudeConsoleData,
geminiData,
openaiData,
openaiResponsesData,
bedrockData,
groupsData
] = await Promise.all([
apiClient.get('/admin/claude-accounts'),
apiClient.get('/admin/claude-console-accounts'),
apiClient.get('/admin/gemini-accounts'),
apiClient.get('/admin/openai-accounts'),
apiClient.get('/admin/openai-responses-accounts'), // 获取 OpenAI-Responses 账号
apiClient.get('/admin/bedrock-accounts'), // 添加 Bedrock 账号获取
apiClient.get('/admin/account-groups')
])
// 合并Claude OAuth账户和Claude Console账户
const claudeAccounts = []
@@ -944,13 +974,31 @@ const refreshAccounts = async () => {
}))
}
// 合并 OpenAI 和 OpenAI-Responses 账号
const openaiAccounts = []
if (openaiData.success) {
localAccounts.value.openai = (openaiData.data || []).map((account) => ({
...account,
isDedicated: account.accountType === 'dedicated' // 保留以便向后兼容
}))
;(openaiData.data || []).forEach((account) => {
openaiAccounts.push({
...account,
platform: 'openai',
isDedicated: account.accountType === 'dedicated' // 保留以便向后兼容
})
})
}
if (openaiResponsesData.success) {
;(openaiResponsesData.data || []).forEach((account) => {
openaiAccounts.push({
...account,
platform: 'openai-responses',
isDedicated: account.accountType === 'dedicated' // 保留以便向后兼容
})
})
}
localAccounts.value.openai = openaiAccounts
if (bedrockData.success) {
localAccounts.value.bedrock = (bedrockData.data || []).map((account) => ({
...account,

View File

@@ -911,15 +911,23 @@ const updateApiKey = async () => {
const refreshAccounts = async () => {
accountsLoading.value = true
try {
const [claudeData, claudeConsoleData, geminiData, openaiData, bedrockData, groupsData] =
await Promise.all([
apiClient.get('/admin/claude-accounts'),
apiClient.get('/admin/claude-console-accounts'),
apiClient.get('/admin/gemini-accounts'),
apiClient.get('/admin/openai-accounts'),
apiClient.get('/admin/bedrock-accounts'), // 添加 Bedrock 账号获取
apiClient.get('/admin/account-groups')
])
const [
claudeData,
claudeConsoleData,
geminiData,
openaiData,
openaiResponsesData,
bedrockData,
groupsData
] = await Promise.all([
apiClient.get('/admin/claude-accounts'),
apiClient.get('/admin/claude-console-accounts'),
apiClient.get('/admin/gemini-accounts'),
apiClient.get('/admin/openai-accounts'),
apiClient.get('/admin/openai-responses-accounts'), // 获取 OpenAI-Responses 账号
apiClient.get('/admin/bedrock-accounts'), // 添加 Bedrock 账号获取
apiClient.get('/admin/account-groups')
])
// 合并Claude OAuth账户和Claude Console账户
const claudeAccounts = []
@@ -953,13 +961,31 @@ const refreshAccounts = async () => {
}))
}
// 合并 OpenAI 和 OpenAI-Responses 账号
const openaiAccounts = []
if (openaiData.success) {
localAccounts.value.openai = (openaiData.data || []).map((account) => ({
...account,
isDedicated: account.accountType === 'dedicated'
}))
;(openaiData.data || []).forEach((account) => {
openaiAccounts.push({
...account,
platform: 'openai',
isDedicated: account.accountType === 'dedicated'
})
})
}
if (openaiResponsesData.success) {
;(openaiResponsesData.data || []).forEach((account) => {
openaiAccounts.push({
...account,
platform: 'openai-responses',
isDedicated: account.accountType === 'dedicated'
})
})
}
localAccounts.value.openai = openaiAccounts
if (bedrockData.success) {
localAccounts.value.bedrock = (bedrockData.data || []).map((account) => ({
...account,
@@ -991,7 +1017,7 @@ const loadUsers = async () => {
availableUsers.value = response.data || []
}
} catch (error) {
console.error('Failed to load users:', error)
// console.error('Failed to load users:', error)
availableUsers.value = [
{
id: 'admin',
@@ -1017,7 +1043,7 @@ onMounted(async () => {
supportedClients.value = clients || []
availableTags.value = tags || []
} catch (error) {
console.error('Error loading initial data:', error)
// console.error('Error loading initial data:', error)
// Fallback to empty arrays if loading fails
supportedClients.value = []
availableTags.value = []
@@ -1025,10 +1051,29 @@ onMounted(async () => {
// 初始化账号数据
if (props.accounts) {
// 合并 OpenAI 和 OpenAI-Responses 账号
const openaiAccounts = []
if (props.accounts.openai) {
props.accounts.openai.forEach((account) => {
openaiAccounts.push({
...account,
platform: 'openai'
})
})
}
if (props.accounts.openaiResponses) {
props.accounts.openaiResponses.forEach((account) => {
openaiAccounts.push({
...account,
platform: 'openai-responses'
})
})
}
localAccounts.value = {
claude: props.accounts.claude || [],
gemini: props.accounts.gemini || [],
openai: props.accounts.openai || [],
openai: openaiAccounts,
bedrock: props.accounts.bedrock || [], // 添加 Bedrock 账号
claudeGroups: props.accounts.claudeGroups || [],
geminiGroups: props.accounts.geminiGroups || [],
@@ -1036,6 +1081,9 @@ onMounted(async () => {
}
}
// 自动加载账号数据
await refreshAccounts()
form.name = props.apiKey.name
// 处理速率限制迁移如果有tokenLimit且没有rateLimitCost提示用户
@@ -1045,7 +1093,7 @@ onMounted(async () => {
// 如果有历史tokenLimit但没有rateLimitCost提示用户需要重新设置
if (props.apiKey.tokenLimit > 0 && !props.apiKey.rateLimitCost) {
// 可以根据需要添加提示,或者自动迁移(这里选择让用户手动设置)
console.log('检测到历史Token限制请考虑设置费用限制')
// console.log('检测到历史Token限制请考虑设置费用限制')
}
form.rateLimitWindow = props.apiKey.rateLimitWindow || ''
@@ -1061,7 +1109,10 @@ onMounted(async () => {
form.claudeAccountId = props.apiKey.claudeAccountId || ''
}
form.geminiAccountId = props.apiKey.geminiAccountId || ''
// 处理 OpenAI 账号 - 直接使用后端传来的值(已包含 responses: 前缀)
form.openaiAccountId = props.apiKey.openaiAccountId || ''
form.bedrockAccountId = props.apiKey.bedrockAccountId || '' // 添加 Bedrock 账号ID初始化
form.restrictedModels = props.apiKey.restrictedModels || []
form.allowedClients = props.apiKey.allowedClients || []

View File

@@ -167,7 +167,7 @@ const copyApiKey = async () => {
await navigator.clipboard.writeText(key)
showToast('API Key 已复制到剪贴板', 'success')
} catch (error) {
console.error('Failed to copy:', error)
// console.error('Failed to copy:', error)
// 降级方案:创建一个临时文本区域
const textArea = document.createElement('textarea')
textArea.value = key

View File

@@ -99,7 +99,13 @@
<div
class="bg-gray-50 px-4 py-2 text-xs font-semibold text-gray-500 dark:bg-gray-700 dark:text-gray-400"
>
{{ platform === 'claude' ? 'Claude OAuth 专属账号' : 'OAuth 专属账号' }}
{{
platform === 'claude'
? 'Claude OAuth 专属账号'
: platform === 'openai'
? 'OpenAI 专属账号'
: 'OAuth 专属账号'
}}
</div>
<div
v-for="account in filteredOAuthAccounts"
@@ -170,6 +176,45 @@
</div>
</div>
<!-- OpenAI-Responses 账号 OpenAI -->
<div v-if="platform === 'openai' && filteredOpenAIResponsesAccounts.length > 0">
<div
class="bg-gray-50 px-4 py-2 text-xs font-semibold text-gray-500 dark:bg-gray-700 dark:text-gray-400"
>
OpenAI-Responses 专属账号
</div>
<div
v-for="account in filteredOpenAIResponsesAccounts"
:key="account.id"
class="cursor-pointer px-4 py-2 transition-colors hover:bg-gray-50 dark:hover:bg-gray-700"
:class="{
'bg-blue-50 dark:bg-blue-900/20': modelValue === `responses:${account.id}`
}"
@click="selectAccount(`responses:${account.id}`)"
>
<div class="flex items-center justify-between">
<div>
<span class="text-gray-700 dark:text-gray-300">{{ account.name }}</span>
<span
class="ml-2 rounded-full px-2 py-0.5 text-xs"
:class="
account.isActive === 'true' || account.isActive === true
? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'
: account.status === 'rate_limited'
? 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'
"
>
{{ getAccountStatusText(account) }}
</span>
</div>
<span class="text-xs text-gray-400 dark:text-gray-500">
{{ formatDate(account.createdAt) }}
</span>
</div>
</div>
</div>
<!-- 无搜索结果 -->
<div
v-if="searchQuery && !hasResults"
@@ -196,7 +241,7 @@ const props = defineProps({
platform: {
type: String,
required: true,
validator: (value) => ['claude', 'gemini'].includes(value)
validator: (value) => ['claude', 'gemini', 'openai', 'bedrock'].includes(value)
},
accounts: {
type: Array,
@@ -251,6 +296,15 @@ const selectedLabel = computed(() => {
return account ? `${account.name} (${getAccountStatusText(account)})` : ''
}
// OpenAI-Responses 账号
if (props.modelValue.startsWith('responses:')) {
const accountId = props.modelValue.substring(10)
const account = props.accounts.find(
(a) => a.id === accountId && a.platform === 'openai-responses'
)
return account ? `${account.name} (${getAccountStatusText(account)})` : ''
}
// OAuth 账号
const account = props.accounts.find((a) => a.id === props.modelValue)
return account ? `${account.name} (${getAccountStatusText(account)})` : ''
@@ -260,8 +314,11 @@ const selectedLabel = computed(() => {
const getAccountStatusText = (account) => {
if (!account) return '未知'
// 处理 OpenAI-Responses 账号isActive 可能是字符串)
const isActive = account.isActive === 'true' || account.isActive === true
// 优先使用 isActive 判断
if (account.isActive === false) {
if (!isActive) {
// 根据 status 提供更详细的状态信息
switch (account.status) {
case 'unauthorized':
@@ -272,11 +329,18 @@ const getAccountStatusText = (account) => {
return '待验证'
case 'rate_limited':
return '限流中'
case 'quota_exceeded':
return '额度超限'
default:
return '异常'
}
}
// 对于激活的账号,如果是限流状态也要显示
if (account.status === 'rate_limited') {
return '限流中'
}
return '正常'
}
@@ -289,18 +353,42 @@ const sortedAccounts = computed(() => {
})
})
// 过滤的分组
// 过滤的分组(根据平台类型过滤)
const filteredGroups = computed(() => {
if (!searchQuery.value) return props.groups
const query = searchQuery.value.toLowerCase()
return props.groups.filter((group) => group.name.toLowerCase().includes(query))
// 只显示与当前平台匹配的分组
let groups = props.groups.filter((group) => {
// 如果分组有platform属性则必须匹配当前平台
// 如果没有platform属性则认为是旧数据根据平台判断
if (group.platform) {
return group.platform === props.platform
}
// 向后兼容如果没有platform字段通过其他方式判断
return true
})
if (searchQuery.value) {
const query = searchQuery.value.toLowerCase()
groups = groups.filter((group) => group.name.toLowerCase().includes(query))
}
return groups
})
// 过滤的 OAuth 账号
const filteredOAuthAccounts = computed(() => {
let accounts = sortedAccounts.value.filter((a) =>
props.platform === 'claude' ? a.platform === 'claude-oauth' : a.platform !== 'claude-console'
)
let accounts = []
if (props.platform === 'claude') {
accounts = sortedAccounts.value.filter((a) => a.platform === 'claude-oauth')
} else if (props.platform === 'openai') {
// 对于 OpenAI只显示 openai 类型的账号
accounts = sortedAccounts.value.filter((a) => a.platform === 'openai')
} else {
// 其他平台显示所有非特殊类型的账号
accounts = sortedAccounts.value.filter(
(a) => !['claude-oauth', 'claude-console', 'openai-responses'].includes(a.platform)
)
}
if (searchQuery.value) {
const query = searchQuery.value.toLowerCase()
@@ -324,12 +412,27 @@ const filteredConsoleAccounts = computed(() => {
return accounts
})
// 过滤的 OpenAI-Responses 账号
const filteredOpenAIResponsesAccounts = computed(() => {
if (props.platform !== 'openai') return []
let accounts = sortedAccounts.value.filter((a) => a.platform === 'openai-responses')
if (searchQuery.value) {
const query = searchQuery.value.toLowerCase()
accounts = accounts.filter((account) => account.name.toLowerCase().includes(query))
}
return accounts
})
// 是否有搜索结果
const hasResults = computed(() => {
return (
filteredGroups.value.length > 0 ||
filteredOAuthAccounts.value.length > 0 ||
filteredConsoleAccounts.value.length > 0
filteredConsoleAccounts.value.length > 0 ||
filteredOpenAIResponsesAccounts.value.length > 0
)
})