Merge remote-tracking branch 'upstream/main' into feat/channel-insights
# Conflicts: # backend/cmd/server/wire_gen.go
This commit is contained in:
@@ -6,6 +6,19 @@ vi.mock('@/stores/app', () => ({
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: (key: string) => {
|
||||
const messages: Record<string, string> = {
|
||||
'admin.accounts.oauth.openai.failedToExchangeCode': 'OpenAI 授权码兑换失败',
|
||||
'admin.accounts.oauth.openai.errors.OPENAI_OAUTH_PROXY_REQUIRED':
|
||||
'未设置代理,当前服务器无法直连 OpenAI,导致 OpenAI OAuth 请求失败。请先选择可访问 OpenAI 的代理后重试;如果授权码已失效,请重新生成授权链接。'
|
||||
}
|
||||
return messages[key] ?? key
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/api/admin', () => ({
|
||||
adminAPI: {
|
||||
accounts: {
|
||||
@@ -17,6 +30,7 @@ vi.mock('@/api/admin', () => ({
|
||||
}))
|
||||
|
||||
import { useOpenAIOAuth } from '@/composables/useOpenAIOAuth'
|
||||
import { adminAPI } from '@/api/admin'
|
||||
|
||||
describe('useOpenAIOAuth.buildCredentials', () => {
|
||||
it('should keep client_id when token response contains it', () => {
|
||||
@@ -46,3 +60,21 @@ describe('useOpenAIOAuth.buildCredentials', () => {
|
||||
expect(creds.refresh_token).toBe('rt')
|
||||
})
|
||||
})
|
||||
|
||||
describe('useOpenAIOAuth.exchangeAuthCode', () => {
|
||||
it('shows a clear proxy hint when code exchange fails without a proxy', async () => {
|
||||
vi.mocked(adminAPI.accounts.exchangeCode).mockRejectedValueOnce({
|
||||
status: 502,
|
||||
reason: 'OPENAI_OAUTH_PROXY_REQUIRED',
|
||||
message: 'OpenAI OAuth token exchange failed: no proxy is configured.'
|
||||
})
|
||||
const oauth = useOpenAIOAuth()
|
||||
|
||||
const tokenInfo = await oauth.exchangeAuthCode('code', 'session-id', 'state')
|
||||
|
||||
expect(tokenInfo).toBeNull()
|
||||
expect(oauth.error.value).toBe(
|
||||
'未设置代理,当前服务器无法直连 OpenAI,导致 OpenAI OAuth 请求失败。请先选择可访问 OpenAI 的代理后重试;如果授权码已失效,请重新生成授权链接。'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -16,6 +16,8 @@ const openaiModels = [
|
||||
// GPT-5.2 系列
|
||||
'gpt-5.2', 'gpt-5.2-2025-12-11', 'gpt-5.2-chat-latest',
|
||||
'gpt-5.2-pro', 'gpt-5.2-pro-2025-12-11',
|
||||
// GPT-5.5 系列
|
||||
'gpt-5.5',
|
||||
// GPT-5.4 系列
|
||||
'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.4-2026-03-05',
|
||||
// GPT-5.3 系列
|
||||
@@ -260,6 +262,7 @@ const openaiPresetMappings = [
|
||||
{ label: 'o3', from: 'o3', to: 'o3', color: 'bg-emerald-100 text-emerald-700 hover:bg-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400' },
|
||||
{ label: 'GPT-5.3 Codex Spark', from: 'gpt-5.3-codex-spark', to: 'gpt-5.3-codex-spark', color: 'bg-teal-100 text-teal-700 hover:bg-teal-200 dark:bg-teal-900/30 dark:text-teal-400' },
|
||||
{ label: 'GPT-5.2', from: 'gpt-5.2', to: 'gpt-5.2', color: 'bg-red-100 text-red-700 hover:bg-red-200 dark:bg-red-900/30 dark:text-red-400' },
|
||||
{ label: 'GPT-5.5', from: 'gpt-5.5', to: 'gpt-5.5', color: 'bg-amber-100 text-amber-700 hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-400' },
|
||||
{ label: 'GPT-5.4', from: 'gpt-5.4', to: 'gpt-5.4', color: 'bg-rose-100 text-rose-700 hover:bg-rose-200 dark:bg-rose-900/30 dark:text-rose-400' },
|
||||
{ label: 'Haiku→5.4', from: 'claude-haiku-4-5-20251001', to: 'gpt-5.4', color: 'bg-emerald-100 text-emerald-700 hover:bg-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400' },
|
||||
{ label: 'Opus→5.4', from: 'claude-opus-4-6', to: 'gpt-5.4', color: 'bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400' },
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { adminAPI } from '@/api/admin'
|
||||
import { extractApiErrorMessage, extractI18nErrorMessage } from '@/utils/apiError'
|
||||
|
||||
export interface OpenAITokenInfo {
|
||||
access_token?: string
|
||||
@@ -26,6 +28,7 @@ export type OpenAIOAuthPlatform = 'openai'
|
||||
|
||||
export function useOpenAIOAuth() {
|
||||
const appStore = useAppStore()
|
||||
const { t } = useI18n()
|
||||
const endpointPrefix = '/admin/openai'
|
||||
|
||||
// State
|
||||
@@ -78,7 +81,7 @@ export function useOpenAIOAuth() {
|
||||
}
|
||||
return true
|
||||
} catch (err: any) {
|
||||
error.value = err.response?.data?.detail || 'Failed to generate OpenAI auth URL'
|
||||
error.value = extractApiErrorMessage(err, t('admin.accounts.oauth.openai.failedToGenerateUrl'))
|
||||
appStore.showError(error.value)
|
||||
return false
|
||||
} finally {
|
||||
@@ -114,7 +117,12 @@ export function useOpenAIOAuth() {
|
||||
const tokenInfo = await adminAPI.accounts.exchangeCode(`${endpointPrefix}/exchange-code`, payload)
|
||||
return tokenInfo as OpenAITokenInfo
|
||||
} catch (err: any) {
|
||||
error.value = err.response?.data?.detail || 'Failed to exchange OpenAI auth code'
|
||||
error.value = extractI18nErrorMessage(
|
||||
err,
|
||||
t,
|
||||
'admin.accounts.oauth.openai.errors',
|
||||
t('admin.accounts.oauth.openai.failedToExchangeCode')
|
||||
)
|
||||
appStore.showError(error.value)
|
||||
return null
|
||||
} finally {
|
||||
@@ -147,7 +155,12 @@ export function useOpenAIOAuth() {
|
||||
)
|
||||
return tokenInfo as OpenAITokenInfo
|
||||
} catch (err: any) {
|
||||
error.value = err.response?.data?.detail || err.message || 'Failed to validate refresh token'
|
||||
error.value = extractI18nErrorMessage(
|
||||
err,
|
||||
t,
|
||||
'admin.accounts.oauth.openai.errors',
|
||||
t('admin.accounts.oauth.openai.failedToValidateRT')
|
||||
)
|
||||
appStore.showError(error.value)
|
||||
return null
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user