feat(Sora): 直连生成并移除sora2api依赖
实现直连 Sora 客户端、媒体落地与清理策略\n更新网关与前端配置以支持 Sora 平台\n补齐单元测试与契约测试,新增 curl 测试脚本\n\n测试: go test ./... -tags=unit
This commit is contained in:
@@ -1501,9 +1501,9 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input type="checkbox" v-model="enableSoraOnOpenAIOAuth" />
|
||||
<span class="slider"></span>
|
||||
<label :class="['switch', { 'switch-active': enableSoraOnOpenAIOAuth }]">
|
||||
<input type="checkbox" v-model="enableSoraOnOpenAIOAuth" class="sr-only" />
|
||||
<span class="switch-thumb"></span>
|
||||
</label>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -45,19 +45,6 @@
|
||||
:placeholder="t('admin.accounts.searchModels')"
|
||||
@click.stop
|
||||
/>
|
||||
<div v-if="props.platform === 'sora'" class="mt-2 flex items-center gap-2 text-xs">
|
||||
<span v-if="loadingSoraModels" class="text-gray-500">
|
||||
{{ t('admin.accounts.soraModelsLoading') }}
|
||||
</span>
|
||||
<button
|
||||
v-else-if="soraLoadError"
|
||||
type="button"
|
||||
class="text-primary-600 hover:underline dark:text-primary-400"
|
||||
@click.stop="loadSoraModels"
|
||||
>
|
||||
{{ t('admin.accounts.soraModelsRetry') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="max-h-52 overflow-auto">
|
||||
<button
|
||||
@@ -133,13 +120,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import ModelIcon from '@/components/common/ModelIcon.vue'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
import { allModels, getModelsByPlatform } from '@/composables/useModelWhitelist'
|
||||
import { adminAPI } from '@/api/admin'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -158,15 +144,8 @@ const showDropdown = ref(false)
|
||||
const searchQuery = ref('')
|
||||
const customModel = ref('')
|
||||
const isComposing = ref(false)
|
||||
const soraModelOptions = ref<{ value: string; label: string }[]>([])
|
||||
const loadingSoraModels = ref(false)
|
||||
const soraLoadError = ref(false)
|
||||
|
||||
const availableOptions = computed(() => {
|
||||
if (props.platform === 'sora') {
|
||||
if (soraModelOptions.value.length > 0) {
|
||||
return soraModelOptions.value
|
||||
}
|
||||
return getModelsByPlatform('sora').map(m => ({ value: m, label: m }))
|
||||
}
|
||||
return allModels
|
||||
@@ -213,9 +192,7 @@ const handleEnter = () => {
|
||||
}
|
||||
|
||||
const fillRelated = () => {
|
||||
const models = props.platform === 'sora' && soraModelOptions.value.length > 0
|
||||
? soraModelOptions.value.map(m => m.value)
|
||||
: getModelsByPlatform(props.platform)
|
||||
const models = getModelsByPlatform(props.platform)
|
||||
const newModels = [...props.modelValue]
|
||||
for (const model of models) {
|
||||
if (!newModels.includes(model)) newModels.push(model)
|
||||
@@ -227,31 +204,4 @@ const clearAll = () => {
|
||||
emit('update:modelValue', [])
|
||||
}
|
||||
|
||||
const loadSoraModels = async () => {
|
||||
if (props.platform !== 'sora') {
|
||||
soraModelOptions.value = []
|
||||
return
|
||||
}
|
||||
if (loadingSoraModels.value) return
|
||||
soraLoadError.value = false
|
||||
loadingSoraModels.value = true
|
||||
try {
|
||||
const models = await adminAPI.models.getPlatformModels('sora')
|
||||
soraModelOptions.value = (models || []).map((m) => ({ value: m, label: m }))
|
||||
} catch (error) {
|
||||
console.warn('加载 Sora 模型列表失败', error)
|
||||
soraLoadError.value = true
|
||||
appStore.showWarning(t('admin.accounts.soraModelsLoadFailed'))
|
||||
} finally {
|
||||
loadingSoraModels.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.platform,
|
||||
() => {
|
||||
loadSoraModels()
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -416,6 +416,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import { useClipboard } from '@/composables/useClipboard'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
import type { AddMethod, AuthInputMethod } from '@/composables/useAccountOAuth'
|
||||
import type { AccountPlatform } from '@/types'
|
||||
|
||||
interface Props {
|
||||
addMethod: AddMethod
|
||||
@@ -428,7 +429,7 @@ interface Props {
|
||||
allowMultiple?: boolean
|
||||
methodLabel?: string
|
||||
showCookieOption?: boolean // Whether to show cookie auto-auth option
|
||||
platform?: 'anthropic' | 'openai' | 'gemini' | 'antigravity' // Platform type for different UI/text
|
||||
platform?: AccountPlatform // Platform type for different UI/text
|
||||
showProjectId?: boolean // New prop to control project ID visibility
|
||||
}
|
||||
|
||||
@@ -455,11 +456,11 @@ const emit = defineEmits<{
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const isOpenAI = computed(() => props.platform === 'openai')
|
||||
const isOpenAI = computed(() => props.platform === 'openai' || props.platform === 'sora')
|
||||
|
||||
// Get translation key based on platform
|
||||
const getOAuthKey = (key: string) => {
|
||||
if (props.platform === 'openai') return `admin.accounts.oauth.openai.${key}`
|
||||
if (props.platform === 'openai' || props.platform === 'sora') return `admin.accounts.oauth.openai.${key}`
|
||||
if (props.platform === 'gemini') return `admin.accounts.oauth.gemini.${key}`
|
||||
if (props.platform === 'antigravity') return `admin.accounts.oauth.antigravity.${key}`
|
||||
return `admin.accounts.oauth.${key}`
|
||||
@@ -478,7 +479,7 @@ const oauthAuthCode = computed(() => t(getOAuthKey('authCode')))
|
||||
const oauthAuthCodePlaceholder = computed(() => t(getOAuthKey('authCodePlaceholder')))
|
||||
const oauthAuthCodeHint = computed(() => t(getOAuthKey('authCodeHint')))
|
||||
const oauthImportantNotice = computed(() => {
|
||||
if (props.platform === 'openai') return t('admin.accounts.oauth.openai.importantNotice')
|
||||
if (props.platform === 'openai' || props.platform === 'sora') return t('admin.accounts.oauth.openai.importantNotice')
|
||||
if (props.platform === 'antigravity') return t('admin.accounts.oauth.antigravity.importantNotice')
|
||||
return ''
|
||||
})
|
||||
@@ -510,7 +511,7 @@ watch(inputMethod, (newVal) => {
|
||||
// Auto-extract code from callback URL (OpenAI/Gemini/Antigravity)
|
||||
// e.g., http://localhost:8085/callback?code=xxx...&state=...
|
||||
watch(authCodeInput, (newVal) => {
|
||||
if (props.platform !== 'openai' && props.platform !== 'gemini' && props.platform !== 'antigravity') return
|
||||
if (props.platform !== 'openai' && props.platform !== 'gemini' && props.platform !== 'antigravity' && props.platform !== 'sora') return
|
||||
|
||||
const trimmed = newVal.trim()
|
||||
// Check if it looks like a URL with code parameter
|
||||
|
||||
Reference in New Issue
Block a user