mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 21:17:30 +00:00
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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user