mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 00:53:33 +00:00
feat: Add comprehensive Amazon Bedrock integration support
Add complete Amazon Bedrock integration to Claude Relay Service with: ## Core Features - ✅ Bedrock account management with encrypted AWS credential storage - ✅ Full request routing to AWS Bedrock with streaming support - ✅ Integration with unified Claude scheduler system - ✅ Support for Inference Profiles and Application Inference Profiles - ✅ Configurable default and small-fast model settings ## Backend Services - Add bedrockAccountService.js for account management - Add bedrockRelayService.js for request forwarding - Integrate Bedrock accounts into unifiedClaudeScheduler.js - Update admin and API routes to support Bedrock endpoints - Add comprehensive configuration options to config.example.js ## Frontend Integration - Complete Vue.js Web UI for Bedrock account management - Account creation form with AWS credentials and model configuration - Real-time account status monitoring and statistics - Edit/update capabilities for existing accounts ## CLI Support - Interactive CLI commands for Bedrock account operations - Account creation, listing, updating, and testing - Status monitoring and connection validation ## Security & Performance - AES encrypted storage of AWS credentials in Redis - Support for temporary credentials (session tokens) - Region-specific configuration support - Rate limiting and error handling This integration enables the relay service to support three AI platforms: 1. Claude (OAuth) - Original Claude.ai integration 2. Gemini - Google AI integration 3. Amazon Bedrock - New AWS Bedrock integration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -83,10 +83,19 @@
|
||||
>
|
||||
<span class="text-sm text-gray-700">Gemini</span>
|
||||
</label>
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input
|
||||
v-model="form.platform"
|
||||
type="radio"
|
||||
value="bedrock"
|
||||
class="mr-2"
|
||||
>
|
||||
<span class="text-sm text-gray-700">Bedrock</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!isEdit && form.platform !== 'claude-console'">
|
||||
<div v-if="!isEdit && form.platform !== 'claude-console' && form.platform !== 'bedrock'">
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">添加方式</label>
|
||||
<div class="flex gap-4">
|
||||
<label class="flex items-center cursor-pointer">
|
||||
@@ -253,6 +262,157 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bedrock 特定字段 -->
|
||||
<div
|
||||
v-if="form.platform === 'bedrock' && !isEdit"
|
||||
class="space-y-4"
|
||||
>
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">AWS 访问密钥 ID *</label>
|
||||
<input
|
||||
v-model="form.accessKeyId"
|
||||
type="text"
|
||||
required
|
||||
class="form-input w-full"
|
||||
:class="{ 'border-red-500': errors.accessKeyId }"
|
||||
placeholder="请输入 AWS Access Key ID"
|
||||
>
|
||||
<p
|
||||
v-if="errors.accessKeyId"
|
||||
class="text-red-500 text-xs mt-1"
|
||||
>
|
||||
{{ errors.accessKeyId }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">AWS 秘密访问密钥 *</label>
|
||||
<input
|
||||
v-model="form.secretAccessKey"
|
||||
type="password"
|
||||
required
|
||||
class="form-input w-full"
|
||||
:class="{ 'border-red-500': errors.secretAccessKey }"
|
||||
placeholder="请输入 AWS Secret Access Key"
|
||||
>
|
||||
<p
|
||||
v-if="errors.secretAccessKey"
|
||||
class="text-red-500 text-xs mt-1"
|
||||
>
|
||||
{{ errors.secretAccessKey }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">AWS 区域 *</label>
|
||||
<input
|
||||
v-model="form.region"
|
||||
type="text"
|
||||
required
|
||||
class="form-input w-full"
|
||||
:class="{ 'border-red-500': errors.region }"
|
||||
placeholder="例如:us-east-1"
|
||||
>
|
||||
<p
|
||||
v-if="errors.region"
|
||||
class="text-red-500 text-xs mt-1"
|
||||
>
|
||||
{{ errors.region }}
|
||||
</p>
|
||||
<div class="mt-2 p-3 bg-blue-50 border border-blue-200 rounded-lg">
|
||||
<div class="flex items-start gap-2">
|
||||
<i class="fas fa-info-circle text-blue-600 mt-0.5" />
|
||||
<div class="text-xs text-blue-700">
|
||||
<p class="font-medium mb-1">
|
||||
常用 AWS 区域参考:
|
||||
</p>
|
||||
<div class="grid grid-cols-2 gap-1 text-xs">
|
||||
<span>• us-east-1 (美国东部)</span>
|
||||
<span>• us-west-2 (美国西部)</span>
|
||||
<span>• eu-west-1 (欧洲爱尔兰)</span>
|
||||
<span>• ap-southeast-1 (新加坡)</span>
|
||||
<span>• ap-northeast-1 (东京)</span>
|
||||
<span>• eu-central-1 (法兰克福)</span>
|
||||
</div>
|
||||
<p class="mt-2 text-blue-600">
|
||||
💡 请输入完整的区域代码,如 us-east-1
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">会话令牌 (可选)</label>
|
||||
<input
|
||||
v-model="form.sessionToken"
|
||||
type="password"
|
||||
class="form-input w-full"
|
||||
placeholder="如果使用临时凭证,请输入会话令牌"
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
仅在使用临时 AWS 凭证时需要填写
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">默认主模型 (可选)</label>
|
||||
<input
|
||||
v-model="form.defaultModel"
|
||||
type="text"
|
||||
class="form-input w-full"
|
||||
placeholder="例如:us.anthropic.claude-sonnet-4-20250514-v1:0"
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
留空将使用系统默认模型。支持 inference profile ID 或 ARN
|
||||
</p>
|
||||
<div class="mt-2 p-3 bg-amber-50 border border-amber-200 rounded-lg">
|
||||
<div class="flex items-start gap-2">
|
||||
<i class="fas fa-info-circle text-amber-600 mt-0.5" />
|
||||
<div class="text-xs text-amber-700">
|
||||
<p class="font-medium mb-1">
|
||||
Bedrock 模型配置说明:
|
||||
</p>
|
||||
<ul class="list-disc list-inside space-y-1 text-xs">
|
||||
<li>支持 Inference Profile ID(推荐)</li>
|
||||
<li>支持 Application Inference Profile ARN</li>
|
||||
<li>常用模型:us.anthropic.claude-sonnet-4-20250514-v1:0</li>
|
||||
<li>留空将使用系统配置的默认模型</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">小快速模型 (可选)</label>
|
||||
<input
|
||||
v-model="form.smallFastModel"
|
||||
type="text"
|
||||
class="form-input w-full"
|
||||
placeholder="例如:us.anthropic.claude-3-5-haiku-20241022-v1:0"
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
用于快速响应的轻量级模型,留空将使用系统默认
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">限流时间 (分钟)</label>
|
||||
<input
|
||||
v-model.number="form.rateLimitDuration"
|
||||
type="number"
|
||||
min="1"
|
||||
class="form-input w-full"
|
||||
placeholder="默认60分钟"
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
当账号返回429错误时,暂停调度的时间(分钟)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Claude Console 特定字段 -->
|
||||
<div
|
||||
v-if="form.platform === 'claude-console' && !isEdit"
|
||||
@@ -355,8 +515,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Claude和Claude Console的优先级设置 -->
|
||||
<div v-if="(form.platform === 'claude' || form.platform === 'claude-console')">
|
||||
<!-- Claude、Claude Console和Bedrock的优先级设置 -->
|
||||
<div v-if="(form.platform === 'claude' || form.platform === 'claude-console' || form.platform === 'bedrock')">
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">调度优先级 (1-100)</label>
|
||||
<input
|
||||
v-model.number="form.priority"
|
||||
@@ -373,7 +533,7 @@
|
||||
|
||||
<!-- 手动输入 Token 字段 -->
|
||||
<div
|
||||
v-if="form.addType === 'manual' && form.platform !== 'claude-console'"
|
||||
v-if="form.addType === 'manual' && form.platform !== 'claude-console' && form.platform !== 'bedrock'"
|
||||
class="space-y-4 bg-blue-50 p-4 rounded-lg border border-blue-200"
|
||||
>
|
||||
<div class="flex items-start gap-3 mb-4">
|
||||
@@ -462,7 +622,7 @@
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
v-if="form.addType === 'oauth' && form.platform !== 'claude-console'"
|
||||
v-if="form.addType === 'oauth' && form.platform !== 'claude-console' && form.platform !== 'bedrock'"
|
||||
type="button"
|
||||
:disabled="loading"
|
||||
class="btn btn-primary flex-1 py-3 px-6 font-semibold"
|
||||
@@ -609,8 +769,8 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Claude和Claude Console的优先级设置(编辑模式) -->
|
||||
<div v-if="(form.platform === 'claude' || form.platform === 'claude-console')">
|
||||
<!-- Claude、Claude Console和Bedrock的优先级设置(编辑模式) -->
|
||||
<div v-if="(form.platform === 'claude' || form.platform === 'claude-console' || form.platform === 'bedrock')">
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">调度优先级 (1-100)</label>
|
||||
<input
|
||||
v-model.number="form.priority"
|
||||
@@ -708,9 +868,115 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bedrock 特定字段(编辑模式)-->
|
||||
<div
|
||||
v-if="form.platform === 'bedrock'"
|
||||
class="space-y-4"
|
||||
>
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">AWS 访问密钥 ID</label>
|
||||
<input
|
||||
v-model="form.accessKeyId"
|
||||
type="text"
|
||||
class="form-input w-full"
|
||||
placeholder="留空表示不更新"
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
留空表示不更新 AWS Access Key ID
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">AWS 秘密访问密钥</label>
|
||||
<input
|
||||
v-model="form.secretAccessKey"
|
||||
type="password"
|
||||
class="form-input w-full"
|
||||
placeholder="留空表示不更新"
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
留空表示不更新 AWS Secret Access Key
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">AWS 区域</label>
|
||||
<input
|
||||
v-model="form.region"
|
||||
type="text"
|
||||
class="form-input w-full"
|
||||
placeholder="例如:us-east-1"
|
||||
>
|
||||
<div class="mt-2 p-3 bg-blue-50 border border-blue-200 rounded-lg">
|
||||
<div class="flex items-start gap-2">
|
||||
<i class="fas fa-info-circle text-blue-600 mt-0.5" />
|
||||
<div class="text-xs text-blue-700">
|
||||
<p class="font-medium mb-1">
|
||||
常用 AWS 区域参考:
|
||||
</p>
|
||||
<div class="grid grid-cols-2 gap-1 text-xs">
|
||||
<span>• us-east-1 (美国东部)</span>
|
||||
<span>• us-west-2 (美国西部)</span>
|
||||
<span>• eu-west-1 (欧洲爱尔兰)</span>
|
||||
<span>• ap-southeast-1 (新加坡)</span>
|
||||
<span>• ap-northeast-1 (东京)</span>
|
||||
<span>• eu-central-1 (法兰克福)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">会话令牌 (可选)</label>
|
||||
<input
|
||||
v-model="form.sessionToken"
|
||||
type="password"
|
||||
class="form-input w-full"
|
||||
placeholder="留空表示不更新"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">默认主模型 (可选)</label>
|
||||
<input
|
||||
v-model="form.defaultModel"
|
||||
type="text"
|
||||
class="form-input w-full"
|
||||
placeholder="例如:us.anthropic.claude-sonnet-4-20250514-v1:0"
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
留空将使用系统默认模型。支持 inference profile ID 或 ARN
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">小快速模型 (可选)</label>
|
||||
<input
|
||||
v-model="form.smallFastModel"
|
||||
type="text"
|
||||
class="form-input w-full"
|
||||
placeholder="例如:us.anthropic.claude-3-5-haiku-20241022-v1:0"
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
用于快速响应的轻量级模型,留空将使用系统默认
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">限流时间 (分钟)</label>
|
||||
<input
|
||||
v-model.number="form.rateLimitDuration"
|
||||
type="number"
|
||||
min="1"
|
||||
class="form-input w-full"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Token 更新 -->
|
||||
<div
|
||||
v-if="form.platform !== 'claude-console'"
|
||||
v-if="form.platform !== 'claude-console' && form.platform !== 'bedrock'"
|
||||
class="bg-amber-50 p-4 rounded-lg border border-amber-200"
|
||||
>
|
||||
<div class="flex items-start gap-3 mb-4">
|
||||
@@ -884,7 +1150,14 @@ const form = ref({
|
||||
return '';
|
||||
})(),
|
||||
userAgent: props.account?.userAgent || '',
|
||||
rateLimitDuration: props.account?.rateLimitDuration || 60
|
||||
rateLimitDuration: props.account?.rateLimitDuration || 60,
|
||||
// Bedrock 特定字段
|
||||
accessKeyId: props.account?.accessKeyId || '',
|
||||
secretAccessKey: props.account?.secretAccessKey || '',
|
||||
region: props.account?.region || '',
|
||||
sessionToken: props.account?.sessionToken || '',
|
||||
defaultModel: props.account?.defaultModel || '',
|
||||
smallFastModel: props.account?.smallFastModel || ''
|
||||
})
|
||||
|
||||
// 表单验证错误
|
||||
@@ -892,7 +1165,10 @@ const errors = ref({
|
||||
name: '',
|
||||
accessToken: '',
|
||||
apiUrl: '',
|
||||
apiKey: ''
|
||||
apiKey: '',
|
||||
accessKeyId: '',
|
||||
secretAccessKey: '',
|
||||
region: ''
|
||||
})
|
||||
|
||||
// 计算是否可以进入下一步
|
||||
@@ -1015,6 +1291,20 @@ const createAccount = async () => {
|
||||
errors.value.apiKey = '请填写 API Key'
|
||||
hasError = true
|
||||
}
|
||||
} else if (form.value.platform === 'bedrock') {
|
||||
// Bedrock 验证
|
||||
if (!form.value.accessKeyId || form.value.accessKeyId.trim() === '') {
|
||||
errors.value.accessKeyId = '请填写 AWS 访问密钥 ID'
|
||||
hasError = true
|
||||
}
|
||||
if (!form.value.secretAccessKey || form.value.secretAccessKey.trim() === '') {
|
||||
errors.value.secretAccessKey = '请填写 AWS 秘密访问密钥'
|
||||
hasError = true
|
||||
}
|
||||
if (!form.value.region || form.value.region.trim() === '') {
|
||||
errors.value.region = '请选择 AWS 区域'
|
||||
hasError = true
|
||||
}
|
||||
} else if (form.value.addType === 'manual' && (!form.value.accessToken || form.value.accessToken.trim() === '')) {
|
||||
errors.value.accessToken = '请填写 Access Token'
|
||||
hasError = true
|
||||
@@ -1086,6 +1376,16 @@ const createAccount = async () => {
|
||||
: []
|
||||
data.userAgent = form.value.userAgent || null
|
||||
data.rateLimitDuration = form.value.rateLimitDuration || 60
|
||||
} else if (form.value.platform === 'bedrock') {
|
||||
// Bedrock 账户特定数据
|
||||
data.accessKeyId = form.value.accessKeyId
|
||||
data.secretAccessKey = form.value.secretAccessKey
|
||||
data.region = form.value.region
|
||||
data.sessionToken = form.value.sessionToken || null
|
||||
data.defaultModel = form.value.defaultModel || null
|
||||
data.smallFastModel = form.value.smallFastModel || null
|
||||
data.priority = form.value.priority || 50
|
||||
data.rateLimitDuration = form.value.rateLimitDuration || 60
|
||||
}
|
||||
|
||||
let result
|
||||
@@ -1093,6 +1393,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 === 'bedrock') {
|
||||
result = await accountsStore.createBedrockAccount(data)
|
||||
} else {
|
||||
result = await accountsStore.createGeminiAccount(data)
|
||||
}
|
||||
@@ -1207,10 +1509,33 @@ const updateAccount = async () => {
|
||||
data.rateLimitDuration = form.value.rateLimitDuration || 60
|
||||
}
|
||||
|
||||
// Bedrock 特定更新
|
||||
if (props.account.platform === 'bedrock') {
|
||||
if (form.value.accessKeyId) {
|
||||
data.accessKeyId = form.value.accessKeyId
|
||||
}
|
||||
if (form.value.secretAccessKey) {
|
||||
data.secretAccessKey = form.value.secretAccessKey
|
||||
}
|
||||
if (form.value.region) {
|
||||
data.region = form.value.region
|
||||
}
|
||||
if (form.value.sessionToken) {
|
||||
data.sessionToken = form.value.sessionToken
|
||||
}
|
||||
// 模型配置(支持设置为空来使用系统默认)
|
||||
data.defaultModel = form.value.defaultModel || null
|
||||
data.smallFastModel = form.value.smallFastModel || null
|
||||
data.priority = form.value.priority || 50
|
||||
data.rateLimitDuration = form.value.rateLimitDuration || 60
|
||||
}
|
||||
|
||||
if (props.account.platform === 'claude') {
|
||||
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 === 'bedrock') {
|
||||
await accountsStore.updateBedrockAccount(props.account.id, data)
|
||||
} else {
|
||||
await accountsStore.updateGeminiAccount(props.account.id, data)
|
||||
}
|
||||
@@ -1289,8 +1614,8 @@ const handleGroupRefresh = async () => {
|
||||
|
||||
// 监听平台变化,重置表单
|
||||
watch(() => form.value.platform, (newPlatform) => {
|
||||
if (newPlatform === 'claude-console') {
|
||||
form.value.addType = 'manual' // Claude Console 只支持手动模式
|
||||
if (newPlatform === 'claude-console' || newPlatform === 'bedrock') {
|
||||
form.value.addType = 'manual' // Claude Console 和 Bedrock 只支持手动模式
|
||||
}
|
||||
|
||||
// 平台变化时,清空分组选择
|
||||
@@ -1388,7 +1713,14 @@ watch(() => props.account, (newAccount) => {
|
||||
return '';
|
||||
})(),
|
||||
userAgent: newAccount.userAgent || '',
|
||||
rateLimitDuration: newAccount.rateLimitDuration || 60
|
||||
rateLimitDuration: newAccount.rateLimitDuration || 60,
|
||||
// Bedrock 特定字段
|
||||
accessKeyId: '', // 编辑模式不显示现有的访问密钥
|
||||
secretAccessKey: '', // 编辑模式不显示现有的秘密密钥
|
||||
region: newAccount.region || '',
|
||||
sessionToken: '', // 编辑模式不显示现有的会话令牌
|
||||
defaultModel: newAccount.defaultModel || '',
|
||||
smallFastModel: newAccount.smallFastModel || ''
|
||||
}
|
||||
|
||||
// 如果是分组类型,加载分组ID
|
||||
|
||||
@@ -6,6 +6,7 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
// 状态
|
||||
const claudeAccounts = ref([])
|
||||
const claudeConsoleAccounts = ref([])
|
||||
const bedrockAccounts = ref([])
|
||||
const geminiAccounts = ref([])
|
||||
const loading = ref(false)
|
||||
const error = ref(null)
|
||||
@@ -52,6 +53,25 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取Bedrock账户列表
|
||||
const fetchBedrockAccounts = async () => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const response = await apiClient.get('/admin/bedrock-accounts')
|
||||
if (response.success) {
|
||||
bedrockAccounts.value = response.data || []
|
||||
} else {
|
||||
throw new Error(response.message || '获取Bedrock账户失败')
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
throw err
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取Gemini账户列表
|
||||
const fetchGeminiAccounts = async () => {
|
||||
loading.value = true
|
||||
@@ -79,6 +99,7 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
await Promise.all([
|
||||
fetchClaudeAccounts(),
|
||||
fetchClaudeConsoleAccounts(),
|
||||
fetchBedrockAccounts(),
|
||||
fetchGeminiAccounts()
|
||||
])
|
||||
} catch (err) {
|
||||
@@ -129,6 +150,26 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 创建Bedrock账户
|
||||
const createBedrockAccount = async (data) => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const response = await apiClient.post('/admin/bedrock-accounts', data)
|
||||
if (response.success) {
|
||||
await fetchBedrockAccounts()
|
||||
return response.data
|
||||
} else {
|
||||
throw new Error(response.message || '创建Bedrock账户失败')
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
throw err
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 创建Gemini账户
|
||||
const createGeminiAccount = async (data) => {
|
||||
loading.value = true
|
||||
@@ -189,6 +230,26 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 更新Bedrock账户
|
||||
const updateBedrockAccount = async (id, data) => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const response = await apiClient.put(`/admin/bedrock-accounts/${id}`, data)
|
||||
if (response.success) {
|
||||
await fetchBedrockAccounts()
|
||||
return response
|
||||
} else {
|
||||
throw new Error(response.message || '更新Bedrock账户失败')
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
throw err
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 更新Gemini账户
|
||||
const updateGeminiAccount = async (id, data) => {
|
||||
loading.value = true
|
||||
@@ -219,6 +280,8 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
endpoint = `/admin/claude-accounts/${id}/toggle`
|
||||
} else if (platform === 'claude-console') {
|
||||
endpoint = `/admin/claude-console-accounts/${id}/toggle`
|
||||
} else if (platform === 'bedrock') {
|
||||
endpoint = `/admin/bedrock-accounts/${id}/toggle`
|
||||
} else {
|
||||
endpoint = `/admin/gemini-accounts/${id}/toggle`
|
||||
}
|
||||
@@ -229,6 +292,8 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
await fetchClaudeAccounts()
|
||||
} else if (platform === 'claude-console') {
|
||||
await fetchClaudeConsoleAccounts()
|
||||
} else if (platform === 'bedrock') {
|
||||
await fetchBedrockAccounts()
|
||||
} else {
|
||||
await fetchGeminiAccounts()
|
||||
}
|
||||
@@ -254,6 +319,8 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
endpoint = `/admin/claude-accounts/${id}`
|
||||
} else if (platform === 'claude-console') {
|
||||
endpoint = `/admin/claude-console-accounts/${id}`
|
||||
} else if (platform === 'bedrock') {
|
||||
endpoint = `/admin/bedrock-accounts/${id}`
|
||||
} else {
|
||||
endpoint = `/admin/gemini-accounts/${id}`
|
||||
}
|
||||
@@ -264,6 +331,8 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
await fetchClaudeAccounts()
|
||||
} else if (platform === 'claude-console') {
|
||||
await fetchClaudeConsoleAccounts()
|
||||
} else if (platform === 'bedrock') {
|
||||
await fetchBedrockAccounts()
|
||||
} else {
|
||||
await fetchGeminiAccounts()
|
||||
}
|
||||
@@ -374,6 +443,7 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
const reset = () => {
|
||||
claudeAccounts.value = []
|
||||
claudeConsoleAccounts.value = []
|
||||
bedrockAccounts.value = []
|
||||
geminiAccounts.value = []
|
||||
loading.value = false
|
||||
error.value = null
|
||||
@@ -385,6 +455,7 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
// State
|
||||
claudeAccounts,
|
||||
claudeConsoleAccounts,
|
||||
bedrockAccounts,
|
||||
geminiAccounts,
|
||||
loading,
|
||||
error,
|
||||
@@ -394,13 +465,16 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
// Actions
|
||||
fetchClaudeAccounts,
|
||||
fetchClaudeConsoleAccounts,
|
||||
fetchBedrockAccounts,
|
||||
fetchGeminiAccounts,
|
||||
fetchAllAccounts,
|
||||
createClaudeAccount,
|
||||
createClaudeConsoleAccount,
|
||||
createBedrockAccount,
|
||||
createGeminiAccount,
|
||||
updateClaudeAccount,
|
||||
updateClaudeConsoleAccount,
|
||||
updateBedrockAccount,
|
||||
updateGeminiAccount,
|
||||
toggleAccount,
|
||||
deleteAccount,
|
||||
|
||||
@@ -245,6 +245,15 @@
|
||||
<span class="w-px h-4 bg-purple-300 mx-1" />
|
||||
<span class="text-xs font-medium text-purple-700">API Key</span>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="account.platform === 'bedrock'"
|
||||
class="flex items-center gap-1.5 px-2.5 py-1 bg-gradient-to-r from-orange-100 to-red-100 rounded-lg border border-orange-200"
|
||||
>
|
||||
<i class="fab fa-aws text-orange-700 text-xs" />
|
||||
<span class="text-xs font-semibold text-orange-800">Bedrock</span>
|
||||
<span class="w-px h-4 bg-orange-300 mx-1" />
|
||||
<span class="text-xs font-medium text-orange-700">AWS</span>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="flex items-center gap-1.5 px-2.5 py-1 bg-gradient-to-r from-indigo-100 to-blue-100 rounded-lg border border-indigo-200"
|
||||
@@ -303,7 +312,7 @@
|
||||
</td>
|
||||
<td class="px-3 py-4 whitespace-nowrap">
|
||||
<div
|
||||
v-if="account.platform === 'claude' || account.platform === 'claude-console'"
|
||||
v-if="account.platform === 'claude' || account.platform === 'claude-console' || account.platform === 'bedrock'"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<div class="w-16 bg-gray-200 rounded-full h-2">
|
||||
@@ -491,13 +500,16 @@
|
||||
'w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0',
|
||||
account.platform === 'claude'
|
||||
? 'bg-gradient-to-br from-purple-500 to-purple-600'
|
||||
: 'bg-gradient-to-br from-blue-500 to-blue-600'
|
||||
: account.platform === 'bedrock'
|
||||
? 'bg-gradient-to-br from-orange-500 to-red-600'
|
||||
: 'bg-gradient-to-br from-blue-500 to-blue-600'
|
||||
]"
|
||||
>
|
||||
<i
|
||||
:class="[
|
||||
'text-white text-sm',
|
||||
account.platform === 'claude' ? 'fas fa-brain' : 'fas fa-robot'
|
||||
account.platform === 'claude' ? 'fas fa-brain' :
|
||||
account.platform === 'bedrock' ? 'fab fa-aws' : 'fas fa-robot'
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
@@ -765,9 +777,10 @@ const sortedAccounts = computed(() => {
|
||||
const loadAccounts = async () => {
|
||||
accountsLoading.value = true
|
||||
try {
|
||||
const [claudeData, claudeConsoleData, geminiData, apiKeysData, groupsData] = await Promise.all([
|
||||
const [claudeData, claudeConsoleData, bedrockData, geminiData, apiKeysData, groupsData] = await Promise.all([
|
||||
apiClient.get('/admin/claude-accounts'),
|
||||
apiClient.get('/admin/claude-console-accounts'),
|
||||
apiClient.get('/admin/bedrock-accounts'),
|
||||
apiClient.get('/admin/gemini-accounts'),
|
||||
apiClient.get('/admin/api-keys'),
|
||||
apiClient.get('/admin/account-groups')
|
||||
@@ -825,6 +838,15 @@ const loadAccounts = async () => {
|
||||
allAccounts.push(...claudeConsoleAccounts)
|
||||
}
|
||||
|
||||
if (bedrockData.success) {
|
||||
const bedrockAccounts = (bedrockData.data || []).map(acc => {
|
||||
// Bedrock账户暂时不支持直接绑定
|
||||
const groupInfo = accountGroupMap.get(acc.id) || null
|
||||
return { ...acc, platform: 'bedrock', boundApiKeysCount: 0, groupInfo }
|
||||
})
|
||||
allAccounts.push(...bedrockAccounts)
|
||||
}
|
||||
|
||||
if (geminiData.success) {
|
||||
const geminiAccounts = (geminiData.data || []).map(acc => {
|
||||
// 计算每个Gemini账户绑定的API Key数量
|
||||
@@ -1000,6 +1022,8 @@ const deleteAccount = async (account) => {
|
||||
endpoint = `/admin/claude-accounts/${account.id}`
|
||||
} else if (account.platform === 'claude-console') {
|
||||
endpoint = `/admin/claude-console-accounts/${account.id}`
|
||||
} else if (account.platform === 'bedrock') {
|
||||
endpoint = `/admin/bedrock-accounts/${account.id}`
|
||||
} else {
|
||||
endpoint = `/admin/gemini-accounts/${account.id}`
|
||||
}
|
||||
@@ -1050,6 +1074,8 @@ const toggleSchedulable = async (account) => {
|
||||
endpoint = `/admin/claude-accounts/${account.id}/toggle-schedulable`
|
||||
} else if (account.platform === 'claude-console') {
|
||||
endpoint = `/admin/claude-console-accounts/${account.id}/toggle-schedulable`
|
||||
} else if (account.platform === 'bedrock') {
|
||||
endpoint = `/admin/bedrock-accounts/${account.id}/toggle-schedulable`
|
||||
} else if (account.platform === 'gemini') {
|
||||
endpoint = `/admin/gemini-accounts/${account.id}/toggle-schedulable`
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user