feat(account-test): 增强 Sora 账号测试能力探测与弹窗交互

- 后端新增 Sora2 邀请码与剩余额度探测,并补充对应结果解析
- Sora 测试流程补齐请求头与 Cloudflare 场景提示,完善单测覆盖
- 前端测试弹窗对 Sora 账号改为免选模型流程,并新增中英文提示文案

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
yangjianbo
2026-02-19 08:29:51 +08:00
parent 5d2219d299
commit be09188bda
6 changed files with 251 additions and 26 deletions

View File

@@ -41,7 +41,7 @@
</span>
</div>
<div class="space-y-1.5">
<div v-if="!isSoraAccount" class="space-y-1.5">
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.accounts.selectTestModel') }}
</label>
@@ -54,6 +54,12 @@
:placeholder="loadingModels ? t('common.loading') + '...' : t('admin.accounts.selectTestModel')"
/>
</div>
<div
v-else
class="rounded-lg border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-700 dark:border-blue-700 dark:bg-blue-900/20 dark:text-blue-300"
>
{{ t('admin.accounts.soraTestHint') }}
</div>
<!-- Terminal Output -->
<div class="group relative">
@@ -135,12 +141,12 @@
<div class="flex items-center gap-3">
<span class="flex items-center gap-1">
<Icon name="cpu" size="sm" :stroke-width="2" />
{{ t('admin.accounts.testModel') }}
{{ isSoraAccount ? t('admin.accounts.soraTestTarget') : t('admin.accounts.testModel') }}
</span>
</div>
<span class="flex items-center gap-1">
<Icon name="chatBubble" size="sm" :stroke-width="2" />
{{ t('admin.accounts.testPrompt') }}
{{ isSoraAccount ? t('admin.accounts.soraTestMode') : t('admin.accounts.testPrompt') }}
</span>
</div>
</div>
@@ -156,10 +162,10 @@
</button>
<button
@click="startTest"
:disabled="status === 'connecting' || !selectedModelId"
:disabled="status === 'connecting' || (!isSoraAccount && !selectedModelId)"
:class="[
'flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-all',
status === 'connecting' || !selectedModelId
status === 'connecting' || (!isSoraAccount && !selectedModelId)
? 'cursor-not-allowed bg-primary-400 text-white'
: status === 'success'
? 'bg-green-500 text-white hover:bg-green-600'
@@ -232,7 +238,7 @@
</template>
<script setup lang="ts">
import { ref, watch, nextTick } from 'vue'
import { computed, ref, watch, nextTick } from 'vue'
import { useI18n } from 'vue-i18n'
import BaseDialog from '@/components/common/BaseDialog.vue'
import Select from '@/components/common/Select.vue'
@@ -267,6 +273,7 @@ const availableModels = ref<ClaudeModel[]>([])
const selectedModelId = ref('')
const loadingModels = ref(false)
let eventSource: EventSource | null = null
const isSoraAccount = computed(() => props.account?.platform === 'sora')
// Load available models when modal opens
watch(
@@ -283,6 +290,12 @@ watch(
const loadAvailableModels = async () => {
if (!props.account) return
if (props.account.platform === 'sora') {
availableModels.value = []
selectedModelId.value = ''
loadingModels.value = false
return
}
loadingModels.value = true
selectedModelId.value = '' // Reset selection before loading
@@ -350,7 +363,7 @@ const scrollToBottom = async () => {
}
const startTest = async () => {
if (!props.account || !selectedModelId.value) return
if (!props.account || (!isSoraAccount.value && !selectedModelId.value)) return
resetState()
status.value = 'connecting'
@@ -371,7 +384,9 @@ const startTest = async () => {
Authorization: `Bearer ${localStorage.getItem('auth_token')}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ model_id: selectedModelId.value })
body: JSON.stringify(
isSoraAccount.value ? {} : { model_id: selectedModelId.value }
)
})
if (!response.ok) {
@@ -428,7 +443,10 @@ const handleEvent = (event: {
if (event.model) {
addLine(t('admin.accounts.usingModel', { model: event.model }), 'text-cyan-400')
}
addLine(t('admin.accounts.sendingTestMessage'), 'text-gray-400')
addLine(
isSoraAccount.value ? t('admin.accounts.soraTestingFlow') : t('admin.accounts.sendingTestMessage'),
'text-gray-400'
)
addLine('', 'text-gray-300')
addLine(t('admin.accounts.response'), 'text-yellow-400')
break

View File

@@ -41,7 +41,7 @@
</span>
</div>
<div class="space-y-1.5">
<div v-if="!isSoraAccount" class="space-y-1.5">
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.accounts.selectTestModel') }}
</label>
@@ -54,6 +54,12 @@
:placeholder="loadingModels ? t('common.loading') + '...' : t('admin.accounts.selectTestModel')"
/>
</div>
<div
v-else
class="rounded-lg border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-700 dark:border-blue-700 dark:bg-blue-900/20 dark:text-blue-300"
>
{{ t('admin.accounts.soraTestHint') }}
</div>
<!-- Terminal Output -->
<div class="group relative">
@@ -114,12 +120,12 @@
<div class="flex items-center gap-3">
<span class="flex items-center gap-1">
<Icon name="grid" size="sm" :stroke-width="2" />
{{ t('admin.accounts.testModel') }}
{{ isSoraAccount ? t('admin.accounts.soraTestTarget') : t('admin.accounts.testModel') }}
</span>
</div>
<span class="flex items-center gap-1">
<Icon name="chat" size="sm" :stroke-width="2" />
{{ t('admin.accounts.testPrompt') }}
{{ isSoraAccount ? t('admin.accounts.soraTestMode') : t('admin.accounts.testPrompt') }}
</span>
</div>
</div>
@@ -135,10 +141,10 @@
</button>
<button
@click="startTest"
:disabled="status === 'connecting' || !selectedModelId"
:disabled="status === 'connecting' || (!isSoraAccount && !selectedModelId)"
:class="[
'flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-all',
status === 'connecting' || !selectedModelId
status === 'connecting' || (!isSoraAccount && !selectedModelId)
? 'cursor-not-allowed bg-primary-400 text-white'
: status === 'success'
? 'bg-green-500 text-white hover:bg-green-600'
@@ -172,7 +178,7 @@
</template>
<script setup lang="ts">
import { ref, watch, nextTick } from 'vue'
import { computed, ref, watch, nextTick } from 'vue'
import { useI18n } from 'vue-i18n'
import BaseDialog from '@/components/common/BaseDialog.vue'
import Select from '@/components/common/Select.vue'
@@ -207,6 +213,7 @@ const availableModels = ref<ClaudeModel[]>([])
const selectedModelId = ref('')
const loadingModels = ref(false)
let eventSource: EventSource | null = null
const isSoraAccount = computed(() => props.account?.platform === 'sora')
// Load available models when modal opens
watch(
@@ -223,6 +230,12 @@ watch(
const loadAvailableModels = async () => {
if (!props.account) return
if (props.account.platform === 'sora') {
availableModels.value = []
selectedModelId.value = ''
loadingModels.value = false
return
}
loadingModels.value = true
selectedModelId.value = '' // Reset selection before loading
@@ -238,11 +251,6 @@ const loadAvailableModels = async () => {
availableModels.value.find((m) => m.id === 'gemini-3-flash-preview') ||
availableModels.value.find((m) => m.id === 'gemini-3-pro-preview')
selectedModelId.value = preferred?.id || availableModels.value[0].id
} else if (props.account.platform === 'sora') {
const preferred =
availableModels.value.find((m) => m.id === 'gpt-image') ||
availableModels.value.find((m) => !m.id.startsWith('prompt-enhance'))
selectedModelId.value = preferred?.id || availableModels.value[0].id
} else {
// Try to select Sonnet as default, otherwise use first model
const sonnetModel = availableModels.value.find((m) => m.id.includes('sonnet'))
@@ -295,7 +303,7 @@ const scrollToBottom = async () => {
}
const startTest = async () => {
if (!props.account || !selectedModelId.value) return
if (!props.account || (!isSoraAccount.value && !selectedModelId.value)) return
resetState()
status.value = 'connecting'
@@ -316,7 +324,9 @@ const startTest = async () => {
Authorization: `Bearer ${localStorage.getItem('auth_token')}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ model_id: selectedModelId.value })
body: JSON.stringify(
isSoraAccount.value ? {} : { model_id: selectedModelId.value }
)
})
if (!response.ok) {
@@ -373,7 +383,10 @@ const handleEvent = (event: {
if (event.model) {
addLine(t('admin.accounts.usingModel', { model: event.model }), 'text-cyan-400')
}
addLine(t('admin.accounts.sendingTestMessage'), 'text-gray-400')
addLine(
isSoraAccount.value ? t('admin.accounts.soraTestingFlow') : t('admin.accounts.sendingTestMessage'),
'text-gray-400'
)
addLine('', 'text-gray-300')
addLine(t('admin.accounts.response'), 'text-yellow-400')
break

View File

@@ -1993,6 +1993,10 @@ export default {
selectTestModel: 'Select Test Model',
testModel: 'Test model',
testPrompt: 'Prompt: "hi"',
soraTestHint: 'Sora test runs connectivity and capability checks (/backend/me, subscription, Sora2 invite and remaining quota).',
soraTestTarget: 'Target: Sora account capability',
soraTestMode: 'Mode: Connectivity + Capability checks',
soraTestingFlow: 'Running Sora connectivity and capability checks...',
// Stats Modal
viewStats: 'View Stats',
usageStatistics: 'Usage Statistics',

View File

@@ -2125,6 +2125,10 @@ export default {
selectTestModel: '选择测试模型',
testModel: '测试模型',
testPrompt: '提示词:"hi"',
soraTestHint: 'Sora 测试将执行连通性与能力检测(/backend/me、订阅信息、Sora2 邀请码与剩余额度)。',
soraTestTarget: '检测目标Sora 账号能力',
soraTestMode: '模式:连通性 + 能力探测',
soraTestingFlow: '执行 Sora 连通性与能力检测...',
// Stats Modal
viewStats: '查看统计',
usageStatistics: '使用统计',