Merge remote-tracking branch 'f3n9/main' into user-management-new

This commit is contained in:
Feng Yue
2025-08-18 15:32:17 +08:00
34 changed files with 2797 additions and 335 deletions

View File

@@ -555,14 +555,37 @@
</div>
</div>
<!-- ClaudeClaude Console和Bedrock的优先级设置 -->
<div
v-if="
form.platform === 'claude' ||
form.platform === 'claude-console' ||
form.platform === 'bedrock'
"
>
<!-- Claude 订阅类型选择 -->
<div v-if="form.platform === 'claude'">
<label class="mb-3 block text-sm font-semibold text-gray-700">订阅类型</label>
<div class="flex gap-4">
<label class="flex cursor-pointer items-center">
<input
v-model="form.subscriptionType"
class="mr-2"
type="radio"
value="claude_max"
/>
<span class="text-sm text-gray-700">Claude Max</span>
</label>
<label class="flex cursor-pointer items-center">
<input
v-model="form.subscriptionType"
class="mr-2"
type="radio"
value="claude_pro"
/>
<span class="text-sm text-gray-700">Claude Pro</span>
</label>
</div>
<p class="mt-2 text-xs text-gray-500">
<i class="fas fa-info-circle mr-1" />
Pro 账号不支持 Claude Opus 4 模型
</p>
</div>
<!-- 所有平台的优先级设置 -->
<div>
<label class="mb-3 block text-sm font-semibold text-gray-700"
>调度优先级 (1-100)</label
>
@@ -961,14 +984,37 @@
<p class="mt-2 text-xs text-gray-500">Google Cloud/Workspace 账号可能需要提供项目 ID</p>
</div>
<!-- Claude、Claude Console和Bedrock的优先级设置(编辑模式) -->
<div
v-if="
form.platform === 'claude' ||
form.platform === 'claude-console' ||
form.platform === 'bedrock'
"
>
<!-- Claude 订阅类型选择(编辑模式) -->
<div v-if="form.platform === 'claude'">
<label class="mb-3 block text-sm font-semibold text-gray-700">订阅类型</label>
<div class="flex gap-4">
<label class="flex cursor-pointer items-center">
<input
v-model="form.subscriptionType"
class="mr-2"
type="radio"
value="claude_max"
/>
<span class="text-sm text-gray-700">Claude Max</span>
</label>
<label class="flex cursor-pointer items-center">
<input
v-model="form.subscriptionType"
class="mr-2"
type="radio"
value="claude_pro"
/>
<span class="text-sm text-gray-700">Claude Pro</span>
</label>
</div>
<p class="mt-2 text-xs text-gray-500">
<i class="fas fa-info-circle mr-1" />
Pro 账号不支持 Claude Opus 4 模型
</p>
</div>
<!-- 所有平台的优先级设置(编辑模式) -->
<div>
<label class="mb-3 block text-sm font-semibold text-gray-700">调度优先级 (1-100)</label>
<input
v-model.number="form.priority"
@@ -1419,6 +1465,7 @@ const form = ref({
name: props.account?.name || '',
description: props.account?.description || '',
accountType: props.account?.accountType || 'shared',
subscriptionType: 'claude_max', // 默认为 Claude Max兼容旧数据
groupId: '',
projectId: props.account?.projectId || '',
idToken: '',
@@ -1678,12 +1725,21 @@ const handleOAuthSuccess = async (tokenInfo) => {
// Claude使用claudeAiOauth字段
data.claudeAiOauth = tokenInfo.claudeAiOauth || tokenInfo
data.priority = form.value.priority || 50
// 添加订阅类型信息
data.subscriptionInfo = {
accountType: form.value.subscriptionType || 'claude_max',
hasClaudeMax: form.value.subscriptionType === 'claude_max',
hasClaudePro: form.value.subscriptionType === 'claude_pro',
manuallySet: true // 标记为手动设置
}
} else if (form.value.platform === 'gemini') {
// Gemini使用geminiOauth字段
data.geminiOauth = tokenInfo.tokens || tokenInfo
if (form.value.projectId) {
data.projectId = form.value.projectId
}
// 添加 Gemini 优先级
data.priority = form.value.priority || 50
} else if (form.value.platform === 'openai') {
data.openaiOauth = tokenInfo.tokens || tokenInfo
data.accountInfo = tokenInfo.accountInfo
@@ -1803,9 +1859,16 @@ const createAccount = async () => {
accessToken: form.value.accessToken,
refreshToken: form.value.refreshToken || '',
expiresAt: Date.now() + expiresInMs,
scopes: ['user:inference']
scopes: [] // 手动添加没有 scopes
}
data.priority = form.value.priority || 50
// 添加订阅类型信息
data.subscriptionInfo = {
accountType: form.value.subscriptionType || 'claude_max',
hasClaudeMax: form.value.subscriptionType === 'claude_max',
hasClaudePro: form.value.subscriptionType === 'claude_pro',
manuallySet: true // 标记为手动设置
}
} else if (form.value.platform === 'gemini') {
// Gemini手动模式需要构建geminiOauth对象
const expiresInMs = form.value.refreshToken
@@ -1823,6 +1886,9 @@ const createAccount = async () => {
if (form.value.projectId) {
data.projectId = form.value.projectId
}
// 添加 Gemini 优先级
data.priority = form.value.priority || 50
} else if (form.value.platform === 'openai') {
// OpenAI手动模式需要构建openaiOauth对象
const expiresInMs = form.value.refreshToken
@@ -1985,7 +2051,7 @@ const updateAccount = async () => {
accessToken: form.value.accessToken || '',
refreshToken: form.value.refreshToken || '',
expiresAt: Date.now() + expiresInMs,
scopes: ['user:inference']
scopes: props.account.scopes || [] // 保持原有的 scopes如果没有则为空数组
}
} else if (props.account.platform === 'gemini') {
// Gemini需要构建geminiOauth对象
@@ -2019,9 +2085,16 @@ const updateAccount = async () => {
data.projectId = form.value.projectId
}
// Claude 官方账号优先级更新
// Claude 官方账号优先级和订阅类型更新
if (props.account.platform === 'claude') {
data.priority = form.value.priority || 50
// 更新订阅类型信息
data.subscriptionInfo = {
accountType: form.value.subscriptionType || 'claude_max',
hasClaudeMax: form.value.subscriptionType === 'claude_max',
hasClaudePro: form.value.subscriptionType === 'claude_pro',
manuallySet: true // 标记为手动设置
}
}
// OpenAI 账号优先级更新
@@ -2029,6 +2102,11 @@ const updateAccount = async () => {
data.priority = form.value.priority || 50
}
// Gemini 账号优先级更新
if (props.account.platform === 'gemini') {
data.priority = form.value.priority || 50
}
// Claude Console 特定更新
if (props.account.platform === 'claude-console') {
data.apiUrl = form.value.apiUrl
@@ -2319,12 +2397,32 @@ watch(
groupId = newAccount.groupId || (newAccount.groupInfo && newAccount.groupInfo.id) || ''
}
// 初始化订阅类型(从 subscriptionInfo 中提取,兼容旧数据默认为 claude_max
let subscriptionType = 'claude_max'
if (newAccount.subscriptionInfo) {
const info =
typeof newAccount.subscriptionInfo === 'string'
? JSON.parse(newAccount.subscriptionInfo)
: newAccount.subscriptionInfo
if (info.accountType) {
subscriptionType = info.accountType
} else if (info.hasClaudeMax) {
subscriptionType = 'claude_max'
} else if (info.hasClaudePro) {
subscriptionType = 'claude_pro'
} else {
subscriptionType = 'claude_free'
}
}
form.value = {
platform: newAccount.platform,
addType: 'oauth',
name: newAccount.name,
description: newAccount.description || '',
accountType: newAccount.accountType || 'shared',
subscriptionType: subscriptionType,
groupId: groupId,
projectId: newAccount.projectId || '',
accessToken: '',

View File

@@ -436,6 +436,18 @@
platform="openai"
/>
</div>
<div>
<label class="mb-1 block text-sm font-medium text-gray-600">Bedrock 专属账号</label>
<AccountSelector
v-model="form.bedrockAccountId"
:accounts="localAccounts.bedrock"
default-option-text="使用共享账号池"
:disabled="form.permissions === 'gemini' || form.permissions === 'openai'"
:groups="[]"
placeholder="请选择Bedrock账号"
platform="bedrock"
/>
</div>
</div>
<p class="mt-2 text-xs text-gray-500">
选择专属账号后此API Key将只使用该账号不选择则使用共享账号池
@@ -618,6 +630,7 @@ const localAccounts = ref({
claude: [],
gemini: [],
openai: [],
bedrock: [], // 添加 Bedrock 账号列表
claudeGroups: [],
geminiGroups: [],
openaiGroups: []
@@ -658,6 +671,7 @@ const form = reactive({
claudeAccountId: '',
geminiAccountId: '',
openaiAccountId: '',
bedrockAccountId: '', // 添加 Bedrock 账号ID
enableModelRestriction: false,
restrictedModels: [],
modelInput: '',
@@ -676,6 +690,7 @@ onMounted(async () => {
claude: props.accounts.claude || [],
gemini: props.accounts.gemini || [],
openai: props.accounts.openai || [],
bedrock: props.accounts.bedrock || [], // 添加 Bedrock 账号
claudeGroups: props.accounts.claudeGroups || [],
geminiGroups: props.accounts.geminiGroups || [],
openaiGroups: props.accounts.openaiGroups || []
@@ -687,13 +702,15 @@ onMounted(async () => {
const refreshAccounts = async () => {
accountsLoading.value = true
try {
const [claudeData, claudeConsoleData, geminiData, openaiData, 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/account-groups')
])
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')
])
// 合并Claude OAuth账户和Claude Console账户
const claudeAccounts = []
@@ -734,6 +751,13 @@ const refreshAccounts = async () => {
}))
}
if (bedrockData.success) {
localAccounts.value.bedrock = (bedrockData.data || []).map((account) => ({
...account,
isDedicated: account.accountType === 'dedicated' // 保留以便向后兼容
}))
}
// 处理分组数据
if (groupsData.success) {
const allGroups = groupsData.data || []
@@ -939,6 +963,11 @@ const createApiKey = async () => {
baseData.openaiAccountId = form.openaiAccountId
}
// Bedrock账户绑定
if (form.bedrockAccountId) {
baseData.bedrockAccountId = form.bedrockAccountId
}
if (form.createType === 'single') {
// 单个创建
const data = {

View File

@@ -339,6 +339,18 @@
platform="openai"
/>
</div>
<div>
<label class="mb-1 block text-sm font-medium text-gray-600">Bedrock 专属账号</label>
<AccountSelector
v-model="form.bedrockAccountId"
:accounts="localAccounts.bedrock"
default-option-text="使用共享账号池"
:disabled="form.permissions === 'gemini' || form.permissions === 'openai'"
:groups="[]"
placeholder="请选择Bedrock账号"
platform="bedrock"
/>
</div>
</div>
<p class="mt-2 text-xs text-gray-500">修改绑定账号将影响此API Key的请求路由</p>
</div>
@@ -522,6 +534,7 @@ const localAccounts = ref({
claude: [],
gemini: [],
openai: [],
bedrock: [], // 添加 Bedrock 账号列表
claudeGroups: [],
geminiGroups: [],
openaiGroups: []
@@ -551,6 +564,7 @@ const form = reactive({
claudeAccountId: '',
geminiAccountId: '',
openaiAccountId: '',
bedrockAccountId: '', // 添加 Bedrock 账号ID
enableModelRestriction: false,
restrictedModels: [],
modelInput: '',
@@ -673,6 +687,13 @@ const updateApiKey = async () => {
data.openaiAccountId = null
}
// Bedrock账户绑定
if (form.bedrockAccountId) {
data.bedrockAccountId = form.bedrockAccountId
} else {
data.bedrockAccountId = null
}
// 模型限制 - 始终提交这些字段
data.enableModelRestriction = form.enableModelRestriction
data.restrictedModels = form.restrictedModels
@@ -703,13 +724,15 @@ const updateApiKey = async () => {
const refreshAccounts = async () => {
accountsLoading.value = true
try {
const [claudeData, claudeConsoleData, geminiData, openaiData, 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/account-groups')
])
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')
])
// 合并Claude OAuth账户和Claude Console账户
const claudeAccounts = []
@@ -750,6 +773,13 @@ const refreshAccounts = async () => {
}))
}
if (bedrockData.success) {
localAccounts.value.bedrock = (bedrockData.data || []).map((account) => ({
...account,
isDedicated: account.accountType === 'dedicated'
}))
}
// 处理分组数据
if (groupsData.success) {
const allGroups = groupsData.data || []
@@ -778,6 +808,7 @@ onMounted(async () => {
claude: props.accounts.claude || [],
gemini: props.accounts.gemini || [],
openai: props.accounts.openai || [],
bedrock: props.accounts.bedrock || [], // 添加 Bedrock 账号
claudeGroups: props.accounts.claudeGroups || [],
geminiGroups: props.accounts.geminiGroups || [],
openaiGroups: props.accounts.openaiGroups || []
@@ -799,6 +830,7 @@ onMounted(async () => {
}
form.geminiAccountId = props.apiKey.geminiAccountId || ''
form.openaiAccountId = props.apiKey.openaiAccountId || ''
form.bedrockAccountId = props.apiKey.bedrockAccountId || '' // 添加 Bedrock 账号ID初始化
form.restrictedModels = props.apiKey.restrictedModels || []
form.allowedClients = props.apiKey.allowedClients || []
form.tags = props.apiKey.tags || []