mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-05 16:00:21 +08:00
feat: add mixed-channel precheck flow for antigravity accounts
This commit is contained in:
@@ -15,7 +15,9 @@ import type {
|
||||
AccountUsageStatsResponse,
|
||||
TempUnschedulableStatus,
|
||||
AdminDataPayload,
|
||||
AdminDataImportResult
|
||||
AdminDataImportResult,
|
||||
CheckMixedChannelRequest,
|
||||
CheckMixedChannelResponse
|
||||
} from '@/types'
|
||||
|
||||
/**
|
||||
@@ -80,6 +82,16 @@ export async function update(id: number, updates: UpdateAccountRequest): Promise
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Check mixed-channel risk for account-group binding.
|
||||
*/
|
||||
export async function checkMixedChannelRisk(
|
||||
payload: CheckMixedChannelRequest
|
||||
): Promise<CheckMixedChannelResponse> {
|
||||
const { data } = await apiClient.post<CheckMixedChannelResponse>('/admin/accounts/check-mixed-channel', payload)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete account
|
||||
* @param id - Account ID
|
||||
@@ -458,6 +470,7 @@ export const accountsAPI = {
|
||||
getById,
|
||||
create,
|
||||
update,
|
||||
checkMixedChannelRisk,
|
||||
delete: deleteAccount,
|
||||
toggleStatus,
|
||||
testAccount,
|
||||
|
||||
@@ -1932,7 +1932,7 @@
|
||||
<ConfirmDialog
|
||||
:show="showMixedChannelWarning"
|
||||
:title="t('admin.accounts.mixedChannelWarningTitle')"
|
||||
:message="mixedChannelWarningDetails ? t('admin.accounts.mixedChannelWarning', mixedChannelWarningDetails) : ''"
|
||||
:message="mixedChannelWarningMessageText"
|
||||
:confirm-text="t('common.confirm')"
|
||||
:cancel-text="t('common.cancel')"
|
||||
:danger="true"
|
||||
@@ -1964,8 +1964,14 @@ import {
|
||||
import { useOpenAIOAuth } from '@/composables/useOpenAIOAuth'
|
||||
import { useGeminiOAuth } from '@/composables/useGeminiOAuth'
|
||||
import { useAntigravityOAuth } from '@/composables/useAntigravityOAuth'
|
||||
import { useMixedChannelWarning } from '@/composables/useMixedChannelWarning'
|
||||
import type { Proxy, AdminGroup, AccountPlatform, AccountType } from '@/types'
|
||||
import type {
|
||||
Proxy,
|
||||
AdminGroup,
|
||||
AccountPlatform,
|
||||
AccountType,
|
||||
CheckMixedChannelResponse,
|
||||
CreateAccountRequest
|
||||
} from '@/types'
|
||||
import BaseDialog from '@/components/common/BaseDialog.vue'
|
||||
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
@@ -2103,9 +2109,13 @@ const tempUnschedRules = ref<TempUnschedRuleForm[]>([])
|
||||
const geminiOAuthType = ref<'code_assist' | 'google_one' | 'ai_studio'>('google_one')
|
||||
const geminiAIStudioOAuthEnabled = ref(false)
|
||||
|
||||
const mixedChannelWarning = useMixedChannelWarning()
|
||||
const showMixedChannelWarning = mixedChannelWarning.show
|
||||
const mixedChannelWarningDetails = mixedChannelWarning.details
|
||||
const showMixedChannelWarning = ref(false)
|
||||
const mixedChannelWarningDetails = ref<{ groupName: string; currentPlatform: string; otherPlatform: string } | null>(
|
||||
null
|
||||
)
|
||||
const mixedChannelWarningRawMessage = ref('')
|
||||
const mixedChannelWarningAction = ref<(() => Promise<void>) | null>(null)
|
||||
const antigravityMixedChannelConfirmed = ref(false)
|
||||
const showAdvancedOAuth = ref(false)
|
||||
const showGeminiHelpDialog = ref(false)
|
||||
|
||||
@@ -2137,6 +2147,13 @@ const geminiSelectedTier = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const mixedChannelWarningMessageText = computed(() => {
|
||||
if (mixedChannelWarningDetails.value) {
|
||||
return t('admin.accounts.mixedChannelWarning', mixedChannelWarningDetails.value)
|
||||
}
|
||||
return mixedChannelWarningRawMessage.value
|
||||
})
|
||||
|
||||
const geminiQuotaDocs = {
|
||||
codeAssist: 'https://developers.google.com/gemini-code-assist/resources/quotas',
|
||||
aiStudio: 'https://ai.google.dev/pricing',
|
||||
@@ -2528,6 +2545,105 @@ const splitTempUnschedKeywords = (value: string) => {
|
||||
.filter((item) => item.length > 0)
|
||||
}
|
||||
|
||||
const isAntigravityAccount = (platform: AccountPlatform) => platform === 'antigravity'
|
||||
|
||||
const buildMixedChannelDetails = (resp?: CheckMixedChannelResponse) => {
|
||||
const details = resp?.details
|
||||
if (!details) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
groupName: details.group_name || 'Unknown',
|
||||
currentPlatform: details.current_platform || 'Unknown',
|
||||
otherPlatform: details.other_platform || 'Unknown'
|
||||
}
|
||||
}
|
||||
|
||||
const clearMixedChannelDialog = () => {
|
||||
showMixedChannelWarning.value = false
|
||||
mixedChannelWarningDetails.value = null
|
||||
mixedChannelWarningRawMessage.value = ''
|
||||
mixedChannelWarningAction.value = null
|
||||
}
|
||||
|
||||
const openMixedChannelDialog = (opts: {
|
||||
response?: CheckMixedChannelResponse
|
||||
message?: string
|
||||
onConfirm: () => Promise<void>
|
||||
}) => {
|
||||
mixedChannelWarningDetails.value = buildMixedChannelDetails(opts.response)
|
||||
mixedChannelWarningRawMessage.value =
|
||||
opts.message || opts.response?.message || t('admin.accounts.failedToCreate')
|
||||
mixedChannelWarningAction.value = opts.onConfirm
|
||||
showMixedChannelWarning.value = true
|
||||
}
|
||||
|
||||
const withAntigravityConfirmFlag = (payload: CreateAccountRequest): CreateAccountRequest => {
|
||||
if (isAntigravityAccount(payload.platform) && antigravityMixedChannelConfirmed.value) {
|
||||
return {
|
||||
...payload,
|
||||
confirm_mixed_channel_risk: true
|
||||
}
|
||||
}
|
||||
const cloned = { ...payload }
|
||||
delete cloned.confirm_mixed_channel_risk
|
||||
return cloned
|
||||
}
|
||||
|
||||
const ensureAntigravityMixedChannelConfirmed = async (onConfirm: () => Promise<void>): Promise<boolean> => {
|
||||
if (!isAntigravityAccount(form.platform)) {
|
||||
return true
|
||||
}
|
||||
if (antigravityMixedChannelConfirmed.value) {
|
||||
return true
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await adminAPI.accounts.checkMixedChannelRisk({
|
||||
platform: form.platform,
|
||||
group_ids: form.group_ids
|
||||
})
|
||||
if (!result.has_risk) {
|
||||
return true
|
||||
}
|
||||
openMixedChannelDialog({
|
||||
response: result,
|
||||
onConfirm: async () => {
|
||||
antigravityMixedChannelConfirmed.value = true
|
||||
await onConfirm()
|
||||
}
|
||||
})
|
||||
return false
|
||||
} catch (error: any) {
|
||||
appStore.showError(error.response?.data?.message || error.response?.data?.detail || t('admin.accounts.failedToCreate'))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const submitCreateAccount = async (payload: CreateAccountRequest) => {
|
||||
submitting.value = true
|
||||
try {
|
||||
await adminAPI.accounts.create(withAntigravityConfirmFlag(payload))
|
||||
appStore.showSuccess(t('admin.accounts.accountCreated'))
|
||||
emit('created')
|
||||
handleClose()
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 409 && error.response?.data?.error === 'mixed_channel_warning' && isAntigravityAccount(form.platform)) {
|
||||
openMixedChannelDialog({
|
||||
message: error.response?.data?.message,
|
||||
onConfirm: async () => {
|
||||
antigravityMixedChannelConfirmed.value = true
|
||||
await submitCreateAccount(payload)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
appStore.showError(error.response?.data?.message || error.response?.data?.detail || t('admin.accounts.failedToCreate'))
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Methods
|
||||
const resetForm = () => {
|
||||
step.value = 1
|
||||
@@ -2583,45 +2699,45 @@ const resetForm = () => {
|
||||
geminiOAuth.resetState()
|
||||
antigravityOAuth.resetState()
|
||||
oauthFlowRef.value?.reset()
|
||||
mixedChannelWarning.cancel()
|
||||
antigravityMixedChannelConfirmed.value = false
|
||||
clearMixedChannelDialog()
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
mixedChannelWarning.cancel()
|
||||
antigravityMixedChannelConfirmed.value = false
|
||||
clearMixedChannelDialog()
|
||||
emit('close')
|
||||
}
|
||||
|
||||
// Helper function to create account with mixed channel warning handling
|
||||
const doCreateAccount = async (payload: any) => {
|
||||
submitting.value = true
|
||||
try {
|
||||
await mixedChannelWarning.tryRequest(payload, (p) => adminAPI.accounts.create(p), {
|
||||
onSuccess: () => {
|
||||
appStore.showSuccess(t('admin.accounts.accountCreated'))
|
||||
emit('created')
|
||||
handleClose()
|
||||
},
|
||||
onError: (error: any) => {
|
||||
appStore.showError(error.response?.data?.detail || t('admin.accounts.failedToCreate'))
|
||||
}
|
||||
})
|
||||
} finally {
|
||||
submitting.value = false
|
||||
const doCreateAccount = async (payload: CreateAccountRequest) => {
|
||||
const canContinue = await ensureAntigravityMixedChannelConfirmed(async () => {
|
||||
await submitCreateAccount(payload)
|
||||
})
|
||||
if (!canContinue) {
|
||||
return
|
||||
}
|
||||
await submitCreateAccount(payload)
|
||||
}
|
||||
|
||||
// Handle mixed channel warning confirmation
|
||||
const handleMixedChannelConfirm = async () => {
|
||||
const action = mixedChannelWarningAction.value
|
||||
if (!action) {
|
||||
clearMixedChannelDialog()
|
||||
return
|
||||
}
|
||||
clearMixedChannelDialog()
|
||||
submitting.value = true
|
||||
try {
|
||||
await mixedChannelWarning.confirm()
|
||||
await action()
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleMixedChannelCancel = () => {
|
||||
mixedChannelWarning.cancel()
|
||||
clearMixedChannelDialog()
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
@@ -2631,6 +2747,12 @@ const handleSubmit = async () => {
|
||||
appStore.showError(t('admin.accounts.pleaseEnterAccountName'))
|
||||
return
|
||||
}
|
||||
const canContinue = await ensureAntigravityMixedChannelConfirmed(async () => {
|
||||
step.value = 2
|
||||
})
|
||||
if (!canContinue) {
|
||||
return
|
||||
}
|
||||
step.value = 2
|
||||
return
|
||||
}
|
||||
@@ -2666,15 +2788,8 @@ const handleSubmit = async () => {
|
||||
credentials.model_mapping = antigravityModelMapping
|
||||
}
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
const extra = mixedScheduling.value ? { mixed_scheduling: true } : undefined
|
||||
await createAccountAndFinish(form.platform, 'apikey', credentials, extra)
|
||||
} catch (error: any) {
|
||||
appStore.showError(error.response?.data?.detail || t('admin.accounts.failedToCreate'))
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
const extra = mixedScheduling.value ? { mixed_scheduling: true } : undefined
|
||||
await createAccountAndFinish(form.platform, 'apikey', credentials, extra)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2951,7 +3066,7 @@ const handleAntigravityValidateRT = async (refreshTokenInput: string) => {
|
||||
const accountName = refreshTokens.length > 1 ? `${form.name} #${i + 1}` : form.name
|
||||
|
||||
// Note: Antigravity doesn't have buildExtraInfo, so we pass empty extra or rely on credentials
|
||||
await adminAPI.accounts.create({
|
||||
const createPayload = withAntigravityConfirmFlag({
|
||||
name: accountName,
|
||||
notes: form.notes,
|
||||
platform: 'antigravity',
|
||||
@@ -2966,6 +3081,7 @@ const handleAntigravityValidateRT = async (refreshTokenInput: string) => {
|
||||
expires_at: form.expires_at,
|
||||
auto_pause_on_expired: autoPauseOnExpired.value
|
||||
})
|
||||
await adminAPI.accounts.create(createPayload)
|
||||
successCount++
|
||||
} catch (error: any) {
|
||||
failedCount++
|
||||
|
||||
@@ -970,7 +970,7 @@
|
||||
<ConfirmDialog
|
||||
:show="showMixedChannelWarning"
|
||||
:title="t('admin.accounts.mixedChannelWarningTitle')"
|
||||
:message="mixedChannelWarningDetails ? t('admin.accounts.mixedChannelWarning', mixedChannelWarningDetails) : ''"
|
||||
:message="mixedChannelWarningMessageText"
|
||||
:confirm-text="t('common.confirm')"
|
||||
:cancel-text="t('common.cancel')"
|
||||
:danger="true"
|
||||
@@ -985,7 +985,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { adminAPI } from '@/api/admin'
|
||||
import type { Account, Proxy, AdminGroup } from '@/types'
|
||||
import type { Account, Proxy, AdminGroup, CheckMixedChannelResponse } from '@/types'
|
||||
import BaseDialog from '@/components/common/BaseDialog.vue'
|
||||
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
|
||||
import Select from '@/components/common/Select.vue'
|
||||
@@ -994,7 +994,6 @@ import ProxySelector from '@/components/common/ProxySelector.vue'
|
||||
import GroupSelector from '@/components/common/GroupSelector.vue'
|
||||
import ModelWhitelistSelector from '@/components/account/ModelWhitelistSelector.vue'
|
||||
import { formatDateTimeLocalInput, parseDateTimeLocalInput } from '@/utils/format'
|
||||
import { useMixedChannelWarning } from '@/composables/useMixedChannelWarning'
|
||||
import {
|
||||
getPresetMappingsByPlatform,
|
||||
commonErrorCodes,
|
||||
@@ -1061,9 +1060,13 @@ const antigravityModelMappings = ref<ModelMapping[]>([])
|
||||
const tempUnschedEnabled = ref(false)
|
||||
const tempUnschedRules = ref<TempUnschedRuleForm[]>([])
|
||||
|
||||
const mixedChannelWarning = useMixedChannelWarning()
|
||||
const showMixedChannelWarning = mixedChannelWarning.show
|
||||
const mixedChannelWarningDetails = mixedChannelWarning.details
|
||||
const showMixedChannelWarning = ref(false)
|
||||
const mixedChannelWarningDetails = ref<{ groupName: string; currentPlatform: string; otherPlatform: string } | null>(
|
||||
null
|
||||
)
|
||||
const mixedChannelWarningRawMessage = ref('')
|
||||
const mixedChannelWarningAction = ref<(() => Promise<void>) | null>(null)
|
||||
const antigravityMixedChannelConfirmed = ref(false)
|
||||
|
||||
// Quota control state (Anthropic OAuth/SetupToken only)
|
||||
const windowCostEnabled = ref(false)
|
||||
@@ -1114,6 +1117,13 @@ const defaultBaseUrl = computed(() => {
|
||||
return 'https://api.anthropic.com'
|
||||
})
|
||||
|
||||
const mixedChannelWarningMessageText = computed(() => {
|
||||
if (mixedChannelWarningDetails.value) {
|
||||
return t('admin.accounts.mixedChannelWarning', mixedChannelWarningDetails.value)
|
||||
}
|
||||
return mixedChannelWarningRawMessage.value
|
||||
})
|
||||
|
||||
const form = reactive({
|
||||
name: '',
|
||||
notes: '',
|
||||
@@ -1143,6 +1153,11 @@ watch(
|
||||
() => props.account,
|
||||
(newAccount) => {
|
||||
if (newAccount) {
|
||||
antigravityMixedChannelConfirmed.value = false
|
||||
showMixedChannelWarning.value = false
|
||||
mixedChannelWarningDetails.value = null
|
||||
mixedChannelWarningRawMessage.value = ''
|
||||
mixedChannelWarningAction.value = null
|
||||
form.name = newAccount.name
|
||||
form.notes = newAccount.notes || ''
|
||||
form.proxy_id = newAccount.proxy_id
|
||||
@@ -1520,20 +1535,123 @@ function toPositiveNumber(value: unknown) {
|
||||
return Math.trunc(num)
|
||||
}
|
||||
|
||||
const isAntigravityAccount = () => props.account?.platform === 'antigravity'
|
||||
|
||||
const buildMixedChannelDetails = (resp?: CheckMixedChannelResponse) => {
|
||||
const details = resp?.details
|
||||
if (!details) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
groupName: details.group_name || 'Unknown',
|
||||
currentPlatform: details.current_platform || 'Unknown',
|
||||
otherPlatform: details.other_platform || 'Unknown'
|
||||
}
|
||||
}
|
||||
|
||||
const clearMixedChannelDialog = () => {
|
||||
showMixedChannelWarning.value = false
|
||||
mixedChannelWarningDetails.value = null
|
||||
mixedChannelWarningRawMessage.value = ''
|
||||
mixedChannelWarningAction.value = null
|
||||
}
|
||||
|
||||
const openMixedChannelDialog = (opts: {
|
||||
response?: CheckMixedChannelResponse
|
||||
message?: string
|
||||
onConfirm: () => Promise<void>
|
||||
}) => {
|
||||
mixedChannelWarningDetails.value = buildMixedChannelDetails(opts.response)
|
||||
mixedChannelWarningRawMessage.value =
|
||||
opts.message || opts.response?.message || t('admin.accounts.failedToUpdate')
|
||||
mixedChannelWarningAction.value = opts.onConfirm
|
||||
showMixedChannelWarning.value = true
|
||||
}
|
||||
|
||||
const withAntigravityConfirmFlag = (payload: Record<string, unknown>) => {
|
||||
if (isAntigravityAccount() && antigravityMixedChannelConfirmed.value) {
|
||||
return {
|
||||
...payload,
|
||||
confirm_mixed_channel_risk: true
|
||||
}
|
||||
}
|
||||
const cloned = { ...payload }
|
||||
delete cloned.confirm_mixed_channel_risk
|
||||
return cloned
|
||||
}
|
||||
|
||||
const ensureAntigravityMixedChannelConfirmed = async (onConfirm: () => Promise<void>): Promise<boolean> => {
|
||||
if (!isAntigravityAccount()) {
|
||||
return true
|
||||
}
|
||||
if (antigravityMixedChannelConfirmed.value) {
|
||||
return true
|
||||
}
|
||||
if (!props.account) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await adminAPI.accounts.checkMixedChannelRisk({
|
||||
platform: props.account.platform,
|
||||
group_ids: form.group_ids,
|
||||
account_id: props.account.id
|
||||
})
|
||||
if (!result.has_risk) {
|
||||
return true
|
||||
}
|
||||
openMixedChannelDialog({
|
||||
response: result,
|
||||
onConfirm: async () => {
|
||||
antigravityMixedChannelConfirmed.value = true
|
||||
await onConfirm()
|
||||
}
|
||||
})
|
||||
return false
|
||||
} catch (error: any) {
|
||||
appStore.showError(error.response?.data?.message || error.response?.data?.detail || t('admin.accounts.failedToUpdate'))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const formatDateTimeLocal = formatDateTimeLocalInput
|
||||
const parseDateTimeLocal = parseDateTimeLocalInput
|
||||
|
||||
// Methods
|
||||
const handleClose = () => {
|
||||
mixedChannelWarning.cancel()
|
||||
antigravityMixedChannelConfirmed.value = false
|
||||
clearMixedChannelDialog()
|
||||
emit('close')
|
||||
}
|
||||
|
||||
const submitUpdateAccount = async (accountID: number, updatePayload: Record<string, unknown>) => {
|
||||
submitting.value = true
|
||||
try {
|
||||
await adminAPI.accounts.update(accountID, withAntigravityConfirmFlag(updatePayload))
|
||||
appStore.showSuccess(t('admin.accounts.accountUpdated'))
|
||||
emit('updated')
|
||||
handleClose()
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 409 && error.response?.data?.error === 'mixed_channel_warning' && isAntigravityAccount()) {
|
||||
openMixedChannelDialog({
|
||||
message: error.response?.data?.message,
|
||||
onConfirm: async () => {
|
||||
antigravityMixedChannelConfirmed.value = true
|
||||
await submitUpdateAccount(accountID, updatePayload)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
appStore.showError(error.response?.data?.message || error.response?.data?.detail || t('admin.accounts.failedToUpdate'))
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!props.account) return
|
||||
const accountID = props.account.id
|
||||
|
||||
submitting.value = true
|
||||
const updatePayload: Record<string, unknown> = { ...form }
|
||||
try {
|
||||
// 后端期望 proxy_id: 0 表示清除代理,而不是 null
|
||||
@@ -1565,7 +1683,6 @@ const handleSubmit = async () => {
|
||||
newCredentials.api_key = currentCredentials.api_key
|
||||
} else {
|
||||
appStore.showError(t('admin.accounts.apiKeyIsRequired'))
|
||||
submitting.value = false
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1585,7 +1702,6 @@ const handleSubmit = async () => {
|
||||
newCredentials.intercept_warmup_requests = true
|
||||
}
|
||||
if (!applyTempUnschedConfig(newCredentials)) {
|
||||
submitting.value = false
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1601,7 +1717,6 @@ const handleSubmit = async () => {
|
||||
}
|
||||
|
||||
if (!applyTempUnschedConfig(newCredentials)) {
|
||||
submitting.value = false
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1617,7 +1732,6 @@ const handleSubmit = async () => {
|
||||
delete newCredentials.intercept_warmup_requests
|
||||
}
|
||||
if (!applyTempUnschedConfig(newCredentials)) {
|
||||
submitting.value = false
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1700,34 +1814,36 @@ const handleSubmit = async () => {
|
||||
updatePayload.extra = newExtra
|
||||
}
|
||||
|
||||
await mixedChannelWarning.tryRequest(updatePayload, (p) => adminAPI.accounts.update(accountID, p), {
|
||||
onSuccess: () => {
|
||||
appStore.showSuccess(t('admin.accounts.accountUpdated'))
|
||||
emit('updated')
|
||||
handleClose()
|
||||
},
|
||||
onError: (error: any) => {
|
||||
appStore.showError(error.response?.data?.message || error.response?.data?.detail || t('admin.accounts.failedToUpdate'))
|
||||
}
|
||||
const canContinue = await ensureAntigravityMixedChannelConfirmed(async () => {
|
||||
await submitUpdateAccount(accountID, updatePayload)
|
||||
})
|
||||
if (!canContinue) {
|
||||
return
|
||||
}
|
||||
|
||||
await submitUpdateAccount(accountID, updatePayload)
|
||||
} catch (error: any) {
|
||||
appStore.showError(error.response?.data?.message || error.response?.data?.detail || t('admin.accounts.failedToUpdate'))
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Handle mixed channel warning confirmation
|
||||
const handleMixedChannelConfirm = async () => {
|
||||
const action = mixedChannelWarningAction.value
|
||||
if (!action) {
|
||||
clearMixedChannelDialog()
|
||||
return
|
||||
}
|
||||
clearMixedChannelDialog()
|
||||
submitting.value = true
|
||||
try {
|
||||
await mixedChannelWarning.confirm()
|
||||
await action()
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleMixedChannelCancel = () => {
|
||||
mixedChannelWarning.cancel()
|
||||
clearMixedChannelDialog()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
export interface MixedChannelWarningDetails {
|
||||
groupName: string
|
||||
currentPlatform: string
|
||||
otherPlatform: string
|
||||
}
|
||||
|
||||
function isMixedChannelWarningError(error: any): boolean {
|
||||
return error?.response?.status === 409 && error?.response?.data?.error === 'mixed_channel_warning'
|
||||
}
|
||||
|
||||
function extractMixedChannelWarningDetails(error: any): MixedChannelWarningDetails {
|
||||
const details = error?.response?.data?.details || {}
|
||||
return {
|
||||
groupName: details.group_name || 'Unknown',
|
||||
currentPlatform: details.current_platform || 'Unknown',
|
||||
otherPlatform: details.other_platform || 'Unknown'
|
||||
}
|
||||
}
|
||||
|
||||
export function useMixedChannelWarning() {
|
||||
const show = ref(false)
|
||||
const details = ref<MixedChannelWarningDetails | null>(null)
|
||||
|
||||
const pendingPayload = ref<any | null>(null)
|
||||
const pendingRequest = ref<((payload: any) => Promise<any>) | null>(null)
|
||||
const pendingOnSuccess = ref<(() => void) | null>(null)
|
||||
const pendingOnError = ref<((error: any) => void) | null>(null)
|
||||
|
||||
const clearPending = () => {
|
||||
pendingPayload.value = null
|
||||
pendingRequest.value = null
|
||||
pendingOnSuccess.value = null
|
||||
pendingOnError.value = null
|
||||
details.value = null
|
||||
}
|
||||
|
||||
const tryRequest = async (
|
||||
payload: any,
|
||||
request: (payload: any) => Promise<any>,
|
||||
opts?: {
|
||||
onSuccess?: () => void
|
||||
onError?: (error: any) => void
|
||||
}
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
await request(payload)
|
||||
opts?.onSuccess?.()
|
||||
return true
|
||||
} catch (error: any) {
|
||||
if (isMixedChannelWarningError(error)) {
|
||||
details.value = extractMixedChannelWarningDetails(error)
|
||||
pendingPayload.value = payload
|
||||
pendingRequest.value = request
|
||||
pendingOnSuccess.value = opts?.onSuccess || null
|
||||
pendingOnError.value = opts?.onError || null
|
||||
show.value = true
|
||||
return false
|
||||
}
|
||||
|
||||
if (opts?.onError) {
|
||||
opts.onError(error)
|
||||
return false
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const confirm = async (): Promise<boolean> => {
|
||||
show.value = false
|
||||
if (!pendingPayload.value || !pendingRequest.value) {
|
||||
clearPending()
|
||||
return false
|
||||
}
|
||||
|
||||
pendingPayload.value.confirm_mixed_channel_risk = true
|
||||
|
||||
try {
|
||||
await pendingRequest.value(pendingPayload.value)
|
||||
pendingOnSuccess.value?.()
|
||||
return true
|
||||
} catch (error: any) {
|
||||
if (pendingOnError.value) {
|
||||
pendingOnError.value(error)
|
||||
return false
|
||||
}
|
||||
throw error
|
||||
} finally {
|
||||
clearPending()
|
||||
}
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
show.value = false
|
||||
clearPending()
|
||||
}
|
||||
|
||||
return {
|
||||
show,
|
||||
details,
|
||||
tryRequest,
|
||||
confirm,
|
||||
cancel
|
||||
}
|
||||
}
|
||||
|
||||
@@ -716,6 +716,26 @@ export interface UpdateAccountRequest {
|
||||
confirm_mixed_channel_risk?: boolean
|
||||
}
|
||||
|
||||
export interface CheckMixedChannelRequest {
|
||||
platform: AccountPlatform
|
||||
group_ids: number[]
|
||||
account_id?: number
|
||||
}
|
||||
|
||||
export interface MixedChannelWarningDetails {
|
||||
group_id: number
|
||||
group_name: string
|
||||
current_platform: string
|
||||
other_platform: string
|
||||
}
|
||||
|
||||
export interface CheckMixedChannelResponse {
|
||||
has_risk: boolean
|
||||
error?: string
|
||||
message?: string
|
||||
details?: MixedChannelWarningDetails
|
||||
}
|
||||
|
||||
export interface CreateProxyRequest {
|
||||
name: string
|
||||
protocol: ProxyProtocol
|
||||
|
||||
Reference in New Issue
Block a user