merge upstream main
This commit is contained in:
@@ -813,7 +813,7 @@ func (r *accountRepository) SetAntigravityQuotaScopeLimit(ctx context.Context, i
|
|||||||
client := clientFromContext(ctx, r.client)
|
client := clientFromContext(ctx, r.client)
|
||||||
result, err := client.ExecContext(
|
result, err := client.ExecContext(
|
||||||
ctx,
|
ctx,
|
||||||
`UPDATE accounts SET
|
`UPDATE accounts SET
|
||||||
extra = jsonb_set(
|
extra = jsonb_set(
|
||||||
jsonb_set(COALESCE(extra, '{}'::jsonb), '{antigravity_quota_scopes}'::text[], COALESCE(extra->'antigravity_quota_scopes', '{}'::jsonb), true),
|
jsonb_set(COALESCE(extra, '{}'::jsonb), '{antigravity_quota_scopes}'::text[], COALESCE(extra->'antigravity_quota_scopes', '{}'::jsonb), true),
|
||||||
ARRAY['antigravity_quota_scopes', $1]::text[],
|
ARRAY['antigravity_quota_scopes', $1]::text[],
|
||||||
|
|||||||
@@ -1952,7 +1952,11 @@ func sleepAntigravityBackoffWithContext(ctx context.Context, attempt int) bool {
|
|||||||
|
|
||||||
func antigravityUseScopeRateLimit() bool {
|
func antigravityUseScopeRateLimit() bool {
|
||||||
v := strings.ToLower(strings.TrimSpace(os.Getenv(antigravityScopeRateLimitEnv)))
|
v := strings.ToLower(strings.TrimSpace(os.Getenv(antigravityScopeRateLimitEnv)))
|
||||||
return v == "1" || v == "true" || v == "yes" || v == "on"
|
// 默认开启按配额域限流,只有明确设置为禁用值时才关闭
|
||||||
|
if v == "0" || v == "false" || v == "no" || v == "off" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func antigravityHasAccountSwitch(ctx context.Context) bool {
|
func antigravityHasAccountSwitch(ctx context.Context) bool {
|
||||||
|
|||||||
@@ -845,6 +845,12 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
|
|||||||
bodyModified = true
|
bodyModified = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove prompt_cache_retention (not supported by upstream OpenAI API)
|
||||||
|
if _, has := reqBody["prompt_cache_retention"]; has {
|
||||||
|
delete(reqBody, "prompt_cache_retention")
|
||||||
|
bodyModified = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-serialize body only if modified
|
// Re-serialize body only if modified
|
||||||
|
|||||||
@@ -1865,6 +1865,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|
||||||
|
<!-- Mixed Channel Warning Dialog -->
|
||||||
|
<ConfirmDialog
|
||||||
|
:show="showMixedChannelWarning"
|
||||||
|
:title="t('admin.accounts.mixedChannelWarningTitle')"
|
||||||
|
:message="mixedChannelWarningDetails ? t('admin.accounts.mixedChannelWarning', mixedChannelWarningDetails) : ''"
|
||||||
|
:confirm-text="t('common.confirm')"
|
||||||
|
:cancel-text="t('common.cancel')"
|
||||||
|
:danger="true"
|
||||||
|
@confirm="handleMixedChannelConfirm"
|
||||||
|
@cancel="handleMixedChannelCancel"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -1884,6 +1896,7 @@ import { useGeminiOAuth } from '@/composables/useGeminiOAuth'
|
|||||||
import { useAntigravityOAuth } from '@/composables/useAntigravityOAuth'
|
import { useAntigravityOAuth } from '@/composables/useAntigravityOAuth'
|
||||||
import type { Proxy, AdminGroup, AccountPlatform, AccountType } from '@/types'
|
import type { Proxy, AdminGroup, AccountPlatform, AccountType } from '@/types'
|
||||||
import BaseDialog from '@/components/common/BaseDialog.vue'
|
import BaseDialog from '@/components/common/BaseDialog.vue'
|
||||||
|
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
|
||||||
import Icon from '@/components/icons/Icon.vue'
|
import Icon from '@/components/icons/Icon.vue'
|
||||||
import ProxySelector from '@/components/common/ProxySelector.vue'
|
import ProxySelector from '@/components/common/ProxySelector.vue'
|
||||||
import GroupSelector from '@/components/common/GroupSelector.vue'
|
import GroupSelector from '@/components/common/GroupSelector.vue'
|
||||||
@@ -2013,6 +2026,11 @@ const tempUnschedEnabled = ref(false)
|
|||||||
const tempUnschedRules = ref<TempUnschedRuleForm[]>([])
|
const tempUnschedRules = ref<TempUnschedRuleForm[]>([])
|
||||||
const geminiOAuthType = ref<'code_assist' | 'google_one' | 'ai_studio'>('google_one')
|
const geminiOAuthType = ref<'code_assist' | 'google_one' | 'ai_studio'>('google_one')
|
||||||
const geminiAIStudioOAuthEnabled = ref(false)
|
const geminiAIStudioOAuthEnabled = ref(false)
|
||||||
|
|
||||||
|
// Mixed channel warning dialog state
|
||||||
|
const showMixedChannelWarning = ref(false)
|
||||||
|
const mixedChannelWarningDetails = ref<{ groupName: string; currentPlatform: string; otherPlatform: string } | null>(null)
|
||||||
|
const pendingCreatePayload = ref<any>(null)
|
||||||
const showAdvancedOAuth = ref(false)
|
const showAdvancedOAuth = ref(false)
|
||||||
const showGeminiHelpDialog = ref(false)
|
const showGeminiHelpDialog = ref(false)
|
||||||
|
|
||||||
@@ -2444,6 +2462,59 @@ const handleClose = () => {
|
|||||||
emit('close')
|
emit('close')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to create account with mixed channel warning handling
|
||||||
|
const doCreateAccount = async (payload: any) => {
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
await adminAPI.accounts.create(payload)
|
||||||
|
appStore.showSuccess(t('admin.accounts.accountCreated'))
|
||||||
|
emit('created')
|
||||||
|
handleClose()
|
||||||
|
} catch (error: any) {
|
||||||
|
// Handle 409 mixed_channel_warning - show confirmation dialog
|
||||||
|
if (error.response?.status === 409 && error.response?.data?.error === 'mixed_channel_warning') {
|
||||||
|
const details = error.response.data.details || {}
|
||||||
|
mixedChannelWarningDetails.value = {
|
||||||
|
groupName: details.group_name || 'Unknown',
|
||||||
|
currentPlatform: details.current_platform || 'Unknown',
|
||||||
|
otherPlatform: details.other_platform || 'Unknown'
|
||||||
|
}
|
||||||
|
pendingCreatePayload.value = payload
|
||||||
|
showMixedChannelWarning.value = true
|
||||||
|
} else {
|
||||||
|
appStore.showError(error.response?.data?.detail || t('admin.accounts.failedToCreate'))
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle mixed channel warning confirmation
|
||||||
|
const handleMixedChannelConfirm = async () => {
|
||||||
|
showMixedChannelWarning.value = false
|
||||||
|
if (pendingCreatePayload.value) {
|
||||||
|
pendingCreatePayload.value.confirm_mixed_channel_risk = true
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
await adminAPI.accounts.create(pendingCreatePayload.value)
|
||||||
|
appStore.showSuccess(t('admin.accounts.accountCreated'))
|
||||||
|
emit('created')
|
||||||
|
handleClose()
|
||||||
|
} catch (error: any) {
|
||||||
|
appStore.showError(error.response?.data?.detail || t('admin.accounts.failedToCreate'))
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
pendingCreatePayload.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMixedChannelCancel = () => {
|
||||||
|
showMixedChannelWarning.value = false
|
||||||
|
pendingCreatePayload.value = null
|
||||||
|
mixedChannelWarningDetails.value = null
|
||||||
|
}
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
// For OAuth-based type, handle OAuth flow (goes to step 2)
|
// For OAuth-based type, handle OAuth flow (goes to step 2)
|
||||||
if (isOAuthFlow.value) {
|
if (isOAuthFlow.value) {
|
||||||
@@ -2530,21 +2601,11 @@ const handleSubmit = async () => {
|
|||||||
|
|
||||||
form.credentials = credentials
|
form.credentials = credentials
|
||||||
|
|
||||||
submitting.value = true
|
await doCreateAccount({
|
||||||
try {
|
...form,
|
||||||
await adminAPI.accounts.create({
|
group_ids: form.group_ids,
|
||||||
...form,
|
auto_pause_on_expired: autoPauseOnExpired.value
|
||||||
group_ids: form.group_ids,
|
})
|
||||||
auto_pause_on_expired: autoPauseOnExpired.value
|
|
||||||
})
|
|
||||||
appStore.showSuccess(t('admin.accounts.accountCreated'))
|
|
||||||
emit('created')
|
|
||||||
handleClose()
|
|
||||||
} catch (error: any) {
|
|
||||||
appStore.showError(error.response?.data?.detail || t('admin.accounts.failedToCreate'))
|
|
||||||
} finally {
|
|
||||||
submitting.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const goBackToBasicInfo = () => {
|
const goBackToBasicInfo = () => {
|
||||||
|
|||||||
@@ -875,6 +875,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|
||||||
|
<!-- Mixed Channel Warning Dialog -->
|
||||||
|
<ConfirmDialog
|
||||||
|
:show="showMixedChannelWarning"
|
||||||
|
:title="t('admin.accounts.mixedChannelWarningTitle')"
|
||||||
|
:message="mixedChannelWarningDetails ? t('admin.accounts.mixedChannelWarning', mixedChannelWarningDetails) : ''"
|
||||||
|
:confirm-text="t('common.confirm')"
|
||||||
|
:cancel-text="t('common.cancel')"
|
||||||
|
:danger="true"
|
||||||
|
@confirm="handleMixedChannelConfirm"
|
||||||
|
@cancel="handleMixedChannelCancel"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -885,6 +897,7 @@ import { useAuthStore } from '@/stores/auth'
|
|||||||
import { adminAPI } from '@/api/admin'
|
import { adminAPI } from '@/api/admin'
|
||||||
import type { Account, Proxy, AdminGroup } from '@/types'
|
import type { Account, Proxy, AdminGroup } from '@/types'
|
||||||
import BaseDialog from '@/components/common/BaseDialog.vue'
|
import BaseDialog from '@/components/common/BaseDialog.vue'
|
||||||
|
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
|
||||||
import Select from '@/components/common/Select.vue'
|
import Select from '@/components/common/Select.vue'
|
||||||
import Icon from '@/components/icons/Icon.vue'
|
import Icon from '@/components/icons/Icon.vue'
|
||||||
import ProxySelector from '@/components/common/ProxySelector.vue'
|
import ProxySelector from '@/components/common/ProxySelector.vue'
|
||||||
@@ -951,6 +964,11 @@ const mixedScheduling = ref(false) // For antigravity accounts: enable mixed sch
|
|||||||
const tempUnschedEnabled = ref(false)
|
const tempUnschedEnabled = ref(false)
|
||||||
const tempUnschedRules = ref<TempUnschedRuleForm[]>([])
|
const tempUnschedRules = ref<TempUnschedRuleForm[]>([])
|
||||||
|
|
||||||
|
// Mixed channel warning dialog state
|
||||||
|
const showMixedChannelWarning = ref(false)
|
||||||
|
const mixedChannelWarningDetails = ref<{ groupName: string; currentPlatform: string; otherPlatform: string } | null>(null)
|
||||||
|
const pendingUpdatePayload = ref<Record<string, unknown> | null>(null)
|
||||||
|
|
||||||
// Quota control state (Anthropic OAuth/SetupToken only)
|
// Quota control state (Anthropic OAuth/SetupToken only)
|
||||||
const windowCostEnabled = ref(false)
|
const windowCostEnabled = ref(false)
|
||||||
const windowCostLimit = ref<number | null>(null)
|
const windowCostLimit = ref<number | null>(null)
|
||||||
@@ -1366,8 +1384,8 @@ const handleSubmit = async () => {
|
|||||||
if (!props.account) return
|
if (!props.account) return
|
||||||
|
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
|
const updatePayload: Record<string, unknown> = { ...form }
|
||||||
try {
|
try {
|
||||||
const updatePayload: Record<string, unknown> = { ...form }
|
|
||||||
// 后端期望 proxy_id: 0 表示清除代理,而不是 null
|
// 后端期望 proxy_id: 0 表示清除代理,而不是 null
|
||||||
if (updatePayload.proxy_id === null) {
|
if (updatePayload.proxy_id === null) {
|
||||||
updatePayload.proxy_id = 0
|
updatePayload.proxy_id = 0
|
||||||
@@ -1497,9 +1515,47 @@ const handleSubmit = async () => {
|
|||||||
emit('updated')
|
emit('updated')
|
||||||
handleClose()
|
handleClose()
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
appStore.showError(error.response?.data?.message || error.response?.data?.detail || t('admin.accounts.failedToUpdate'))
|
// Handle 409 mixed_channel_warning - show confirmation dialog
|
||||||
|
if (error.response?.status === 409 && error.response?.data?.error === 'mixed_channel_warning') {
|
||||||
|
const details = error.response.data.details || {}
|
||||||
|
mixedChannelWarningDetails.value = {
|
||||||
|
groupName: details.group_name || 'Unknown',
|
||||||
|
currentPlatform: details.current_platform || 'Unknown',
|
||||||
|
otherPlatform: details.other_platform || 'Unknown'
|
||||||
|
}
|
||||||
|
pendingUpdatePayload.value = updatePayload
|
||||||
|
showMixedChannelWarning.value = true
|
||||||
|
} else {
|
||||||
|
appStore.showError(error.response?.data?.message || error.response?.data?.detail || t('admin.accounts.failedToUpdate'))
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
submitting.value = false
|
submitting.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle mixed channel warning confirmation
|
||||||
|
const handleMixedChannelConfirm = async () => {
|
||||||
|
showMixedChannelWarning.value = false
|
||||||
|
if (pendingUpdatePayload.value && props.account) {
|
||||||
|
pendingUpdatePayload.value.confirm_mixed_channel_risk = true
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
await adminAPI.accounts.update(props.account.id, pendingUpdatePayload.value)
|
||||||
|
appStore.showSuccess(t('admin.accounts.accountUpdated'))
|
||||||
|
emit('updated')
|
||||||
|
handleClose()
|
||||||
|
} catch (error: any) {
|
||||||
|
appStore.showError(error.response?.data?.message || error.response?.data?.detail || t('admin.accounts.failedToUpdate'))
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
pendingUpdatePayload.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMixedChannelCancel = () => {
|
||||||
|
showMixedChannelWarning.value = false
|
||||||
|
pendingUpdatePayload.value = null
|
||||||
|
mixedChannelWarningDetails.value = null
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1453,6 +1453,8 @@ export default {
|
|||||||
accountUpdated: 'Account updated successfully',
|
accountUpdated: 'Account updated successfully',
|
||||||
failedToCreate: 'Failed to create account',
|
failedToCreate: 'Failed to create account',
|
||||||
failedToUpdate: 'Failed to update account',
|
failedToUpdate: 'Failed to update account',
|
||||||
|
mixedChannelWarningTitle: 'Mixed Channel Warning',
|
||||||
|
mixedChannelWarning: 'Warning: Group "{groupName}" contains both {currentPlatform} and {otherPlatform} accounts. Mixing different channels may cause thinking block signature validation issues, which will fallback to non-thinking mode. Are you sure you want to continue?',
|
||||||
pleaseEnterAccountName: 'Please enter account name',
|
pleaseEnterAccountName: 'Please enter account name',
|
||||||
pleaseEnterApiKey: 'Please enter API Key',
|
pleaseEnterApiKey: 'Please enter API Key',
|
||||||
apiKeyIsRequired: 'API Key is required',
|
apiKeyIsRequired: 'API Key is required',
|
||||||
|
|||||||
@@ -1585,6 +1585,8 @@ export default {
|
|||||||
accountUpdated: '账号更新成功',
|
accountUpdated: '账号更新成功',
|
||||||
failedToCreate: '创建账号失败',
|
failedToCreate: '创建账号失败',
|
||||||
failedToUpdate: '更新账号失败',
|
failedToUpdate: '更新账号失败',
|
||||||
|
mixedChannelWarningTitle: '混合渠道警告',
|
||||||
|
mixedChannelWarning: '警告:分组 "{groupName}" 中同时包含 {currentPlatform} 和 {otherPlatform} 账号。混合使用不同渠道可能导致 thinking block 签名验证问题,会自动回退到非 thinking 模式。确定要继续吗?',
|
||||||
pleaseEnterAccountName: '请输入账号名称',
|
pleaseEnterAccountName: '请输入账号名称',
|
||||||
pleaseEnterApiKey: '请输入 API Key',
|
pleaseEnterApiKey: '请输入 API Key',
|
||||||
apiKeyIsRequired: 'API Key 是必需的',
|
apiKeyIsRequired: 'API Key 是必需的',
|
||||||
|
|||||||
Reference in New Issue
Block a user