Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions backend/internal/service/openai_gateway_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,12 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
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
Expand Down
91 changes: 76 additions & 15 deletions frontend/src/components/account/CreateAccountModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -1615,6 +1615,18 @@
</div>
</template>
</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>

<script setup lang="ts">
Expand All @@ -1634,6 +1646,7 @@ import { useGeminiOAuth } from '@/composables/useGeminiOAuth'
import { useAntigravityOAuth } from '@/composables/useAntigravityOAuth'
import type { Proxy, Group, AccountPlatform, AccountType } from '@/types'
import BaseDialog from '@/components/common/BaseDialog.vue'
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
import Icon from '@/components/icons/Icon.vue'
import ProxySelector from '@/components/common/ProxySelector.vue'
import GroupSelector from '@/components/common/GroupSelector.vue'
Expand Down Expand Up @@ -1760,6 +1773,11 @@ const tempUnschedEnabled = ref(false)
const tempUnschedRules = ref<TempUnschedRuleForm[]>([])
const geminiOAuthType = ref<'code_assist' | 'google_one' | 'ai_studio'>('google_one')
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 showGeminiHelpDialog = ref(false)

Expand Down Expand Up @@ -2157,6 +2175,59 @@ const handleClose = () => {
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 () => {
// For OAuth-based type, handle OAuth flow (goes to step 2)
if (isOAuthFlow.value) {
Expand Down Expand Up @@ -2213,21 +2284,11 @@ const handleSubmit = async () => {

form.credentials = credentials

submitting.value = true
try {
await adminAPI.accounts.create({
...form,
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
}
await doCreateAccount({
...form,
group_ids: form.group_ids,
auto_pause_on_expired: autoPauseOnExpired.value
})
}

const goBackToBasicInfo = () => {
Expand Down
60 changes: 58 additions & 2 deletions frontend/src/components/account/EditAccountModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,18 @@
</div>
</template>
</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>

<script setup lang="ts">
Expand All @@ -831,6 +843,7 @@ import { useAuthStore } from '@/stores/auth'
import { adminAPI } from '@/api/admin'
import type { Account, Proxy, Group } from '@/types'
import BaseDialog from '@/components/common/BaseDialog.vue'
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
import Select from '@/components/common/Select.vue'
import Icon from '@/components/icons/Icon.vue'
import ProxySelector from '@/components/common/ProxySelector.vue'
Expand Down Expand Up @@ -897,6 +910,11 @@ const mixedScheduling = ref(false) // For antigravity accounts: enable mixed sch
const tempUnschedEnabled = ref(false)
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)
const windowCostEnabled = ref(false)
const windowCostLimit = ref<number | null>(null)
Expand Down Expand Up @@ -1298,8 +1316,8 @@ const handleSubmit = async () => {
if (!props.account) return

submitting.value = true
const updatePayload: Record<string, unknown> = { ...form }
try {
const updatePayload: Record<string, unknown> = { ...form }
// 后端期望 proxy_id: 0 表示清除代理,而不是 null
if (updatePayload.proxy_id === null) {
updatePayload.proxy_id = 0
Expand Down Expand Up @@ -1415,9 +1433,47 @@ const handleSubmit = async () => {
emit('updated')
handleClose()
} 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 {
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>
2 changes: 2 additions & 0 deletions frontend/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,8 @@ export default {
accountUpdated: 'Account updated successfully',
failedToCreate: 'Failed to create 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',
pleaseEnterApiKey: 'Please enter API Key',
apiKeyIsRequired: 'API Key is required',
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/i18n/locales/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1439,6 +1439,8 @@ export default {
accountUpdated: '账号更新成功',
failedToCreate: '创建账号失败',
failedToUpdate: '更新账号失败',
mixedChannelWarningTitle: '混合渠道警告',
mixedChannelWarning: '警告:分组 "{groupName}" 中同时包含 {currentPlatform} 和 {otherPlatform} 账号。混合使用不同渠道可能导致 thinking block 签名验证问题,会自动回退到非 thinking 模式。确定要继续吗?',
pleaseEnterAccountName: '请输入账号名称',
pleaseEnterApiKey: '请输入 API Key',
apiKeyIsRequired: 'API Key 是必需的',
Expand Down
Loading