Merge remote-tracking branch 'upstream/main' into rebuild/auth-identity-foundation
# Conflicts: # backend/internal/service/openai_images.go
This commit is contained in:
@@ -55,12 +55,12 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="supportsGeminiImageTest" class="space-y-1.5">
|
||||
<div v-if="supportsImageTest" class="space-y-1.5">
|
||||
<TextArea
|
||||
v-model="testPrompt"
|
||||
:label="t('admin.accounts.geminiImagePromptLabel')"
|
||||
:placeholder="t('admin.accounts.geminiImagePromptPlaceholder')"
|
||||
:hint="t('admin.accounts.geminiImageTestHint')"
|
||||
:label="t('admin.accounts.imagePromptLabel')"
|
||||
:placeholder="t('admin.accounts.imagePromptPlaceholder')"
|
||||
:hint="t('admin.accounts.imageTestHint')"
|
||||
:disabled="status === 'connecting'"
|
||||
rows="3"
|
||||
/>
|
||||
@@ -122,25 +122,49 @@
|
||||
|
||||
<div v-if="generatedImages.length > 0" class="space-y-2">
|
||||
<div class="text-xs font-medium text-gray-600 dark:text-gray-300">
|
||||
{{ t('admin.accounts.geminiImagePreview') }}
|
||||
{{ t('admin.accounts.imagePreview') }}
|
||||
</div>
|
||||
<div class="grid gap-3 sm:grid-cols-2">
|
||||
<a
|
||||
<div class="flex flex-wrap justify-center gap-3">
|
||||
<div
|
||||
v-for="(image, index) in generatedImages"
|
||||
:key="`${image.url}-${index}`"
|
||||
:href="image.url"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm transition hover:border-primary-300 hover:shadow-md dark:border-dark-500 dark:bg-dark-700"
|
||||
class="group/img relative cursor-pointer overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm transition hover:border-primary-300 hover:shadow-md dark:border-dark-500 dark:bg-dark-700"
|
||||
@click="previewImageUrl = image.url"
|
||||
>
|
||||
<img :src="image.url" :alt="`gemini-test-image-${index + 1}`" class="h-48 w-full object-cover" />
|
||||
<div class="border-t border-gray-100 px-3 py-2 text-xs text-gray-500 dark:border-dark-500 dark:text-gray-300">
|
||||
<img :src="image.url" :alt="`test-image-${index + 1}`" class="max-h-[360px] w-full object-contain" />
|
||||
<div class="absolute inset-0 flex items-center justify-center bg-black/0 transition-colors group-hover/img:bg-black/20">
|
||||
<Icon name="eye" size="lg" class="text-white opacity-0 drop-shadow-lg transition-opacity group-hover/img:opacity-100" :stroke-width="2" />
|
||||
</div>
|
||||
<div class="border-t border-gray-100 px-3 py-1.5 text-xs text-gray-500 dark:border-dark-500 dark:text-gray-300">
|
||||
{{ image.mimeType || 'image/*' }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image Lightbox -->
|
||||
<Teleport to="body">
|
||||
<Transition name="fade">
|
||||
<div
|
||||
v-if="previewImageUrl"
|
||||
class="fixed inset-0 z-[100] flex items-center justify-center bg-black/80 p-4"
|
||||
@click.self="previewImageUrl = ''"
|
||||
>
|
||||
<button
|
||||
class="absolute right-4 top-4 rounded-full bg-black/50 p-2 text-white transition-colors hover:bg-black/70"
|
||||
@click="previewImageUrl = ''"
|
||||
>
|
||||
<Icon name="x" size="lg" :stroke-width="2" />
|
||||
</button>
|
||||
<img
|
||||
:src="previewImageUrl"
|
||||
alt="preview"
|
||||
class="max-h-[90vh] max-w-[90vw] rounded-lg object-contain shadow-2xl"
|
||||
/>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
|
||||
<!-- Test Info -->
|
||||
<div class="flex items-center justify-between px-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
<div class="flex items-center gap-3">
|
||||
@@ -152,8 +176,8 @@
|
||||
<span class="flex items-center gap-1">
|
||||
<Icon name="chat" size="sm" :stroke-width="2" />
|
||||
{{
|
||||
supportsGeminiImageTest
|
||||
? t('admin.accounts.geminiImageTestMode')
|
||||
supportsImageTest
|
||||
? t('admin.accounts.imageTestMode')
|
||||
: t('admin.accounts.testPrompt')
|
||||
}}
|
||||
</span>
|
||||
@@ -250,6 +274,7 @@ const testPrompt = ref('')
|
||||
const loadingModels = ref(false)
|
||||
let abortController: AbortController | null = null
|
||||
const generatedImages = ref<PreviewImage[]>([])
|
||||
const previewImageUrl = ref('')
|
||||
const prioritizedGeminiModels = ['gemini-3.1-flash-image', 'gemini-2.5-flash-image', 'gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-3-flash-preview', 'gemini-3-pro-preview', 'gemini-2.0-flash']
|
||||
const supportsGeminiImageTest = computed(() => {
|
||||
const modelID = selectedModelId.value.toLowerCase()
|
||||
@@ -258,6 +283,14 @@ const supportsGeminiImageTest = computed(() => {
|
||||
return props.account?.platform === 'gemini' || (props.account?.platform === 'antigravity' && props.account?.type === 'apikey')
|
||||
})
|
||||
|
||||
const supportsOpenAIImageTest = computed(() => {
|
||||
const modelID = selectedModelId.value.toLowerCase()
|
||||
if (!modelID.startsWith('gpt-image-')) return false
|
||||
return props.account?.platform === 'openai'
|
||||
})
|
||||
|
||||
const supportsImageTest = computed(() => supportsGeminiImageTest.value || supportsOpenAIImageTest.value)
|
||||
|
||||
const sortTestModels = (models: ClaudeModel[]) => {
|
||||
const priorityMap = new Map(prioritizedGeminiModels.map((id, index) => [id, index]))
|
||||
|
||||
@@ -284,8 +317,8 @@ watch(
|
||||
)
|
||||
|
||||
watch(selectedModelId, () => {
|
||||
if (supportsGeminiImageTest.value && !testPrompt.value.trim()) {
|
||||
testPrompt.value = t('admin.accounts.geminiImagePromptDefault')
|
||||
if (supportsImageTest.value && !testPrompt.value.trim()) {
|
||||
testPrompt.value = t('admin.accounts.imagePromptDefault')
|
||||
}
|
||||
})
|
||||
|
||||
@@ -325,6 +358,7 @@ const resetState = () => {
|
||||
streamingContent.value = ''
|
||||
errorMessage.value = ''
|
||||
generatedImages.value = []
|
||||
previewImageUrl.value = ''
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
@@ -377,7 +411,7 @@ const startTest = async () => {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model_id: selectedModelId.value,
|
||||
prompt: supportsGeminiImageTest.value ? testPrompt.value.trim() : ''
|
||||
prompt: supportsImageTest.value ? testPrompt.value.trim() : ''
|
||||
}),
|
||||
signal: abortController.signal
|
||||
})
|
||||
@@ -444,8 +478,8 @@ const handleEvent = (event: {
|
||||
addLine(t('admin.accounts.usingModel', { model: event.model }), 'text-cyan-400')
|
||||
}
|
||||
addLine(
|
||||
supportsGeminiImageTest.value
|
||||
? t('admin.accounts.sendingGeminiImageRequest')
|
||||
supportsImageTest.value
|
||||
? t('admin.accounts.sendingImageRequest')
|
||||
: t('admin.accounts.sendingTestMessage'),
|
||||
'text-gray-400'
|
||||
)
|
||||
@@ -466,7 +500,7 @@ const handleEvent = (event: {
|
||||
url: event.image_url,
|
||||
mimeType: event.mime_type
|
||||
})
|
||||
addLine(t('admin.accounts.geminiImageReceived', { count: generatedImages.value.length }), 'text-purple-300')
|
||||
addLine(t('admin.accounts.imageReceived', { count: generatedImages.value.length }), 'text-purple-300')
|
||||
}
|
||||
break
|
||||
|
||||
@@ -500,3 +534,14 @@ const copyOutput = () => {
|
||||
copyToClipboard(text, t('admin.accounts.outputCopied'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -55,12 +55,12 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="supportsGeminiImageTest" class="space-y-1.5">
|
||||
<div v-if="supportsImageTest" class="space-y-1.5">
|
||||
<TextArea
|
||||
v-model="testPrompt"
|
||||
:label="t('admin.accounts.geminiImagePromptLabel')"
|
||||
:placeholder="t('admin.accounts.geminiImagePromptPlaceholder')"
|
||||
:hint="t('admin.accounts.geminiImageTestHint')"
|
||||
:label="t('admin.accounts.imagePromptLabel')"
|
||||
:placeholder="t('admin.accounts.imagePromptPlaceholder')"
|
||||
:hint="t('admin.accounts.imageTestHint')"
|
||||
:disabled="status === 'connecting'"
|
||||
rows="3"
|
||||
/>
|
||||
@@ -122,25 +122,49 @@
|
||||
|
||||
<div v-if="generatedImages.length > 0" class="space-y-2">
|
||||
<div class="text-xs font-medium text-gray-600 dark:text-gray-300">
|
||||
{{ t('admin.accounts.geminiImagePreview') }}
|
||||
{{ t('admin.accounts.imagePreview') }}
|
||||
</div>
|
||||
<div class="grid gap-3 sm:grid-cols-2">
|
||||
<a
|
||||
<div class="flex flex-wrap justify-center gap-3">
|
||||
<div
|
||||
v-for="(image, index) in generatedImages"
|
||||
:key="`${image.url}-${index}`"
|
||||
:href="image.url"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm transition hover:border-primary-300 hover:shadow-md dark:border-dark-500 dark:bg-dark-700"
|
||||
class="group/img relative cursor-pointer overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm transition hover:border-primary-300 hover:shadow-md dark:border-dark-500 dark:bg-dark-700"
|
||||
@click="previewImageUrl = image.url"
|
||||
>
|
||||
<img :src="image.url" :alt="`gemini-test-image-${index + 1}`" class="h-48 w-full object-cover" />
|
||||
<div class="border-t border-gray-100 px-3 py-2 text-xs text-gray-500 dark:border-dark-500 dark:text-gray-300">
|
||||
<img :src="image.url" :alt="`test-image-${index + 1}`" class="max-h-[360px] w-full object-contain" />
|
||||
<div class="absolute inset-0 flex items-center justify-center bg-black/0 transition-colors group-hover/img:bg-black/20">
|
||||
<Icon name="eye" size="lg" class="text-white opacity-0 drop-shadow-lg transition-opacity group-hover/img:opacity-100" :stroke-width="2" />
|
||||
</div>
|
||||
<div class="border-t border-gray-100 px-3 py-1.5 text-xs text-gray-500 dark:border-dark-500 dark:text-gray-300">
|
||||
{{ image.mimeType || 'image/*' }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image Lightbox -->
|
||||
<Teleport to="body">
|
||||
<Transition name="fade">
|
||||
<div
|
||||
v-if="previewImageUrl"
|
||||
class="fixed inset-0 z-[100] flex items-center justify-center bg-black/80 p-4"
|
||||
@click.self="previewImageUrl = ''"
|
||||
>
|
||||
<button
|
||||
class="absolute right-4 top-4 rounded-full bg-black/50 p-2 text-white transition-colors hover:bg-black/70"
|
||||
@click="previewImageUrl = ''"
|
||||
>
|
||||
<Icon name="x" size="lg" :stroke-width="2" />
|
||||
</button>
|
||||
<img
|
||||
:src="previewImageUrl"
|
||||
alt="preview"
|
||||
class="max-h-[90vh] max-w-[90vw] rounded-lg object-contain shadow-2xl"
|
||||
/>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
|
||||
<!-- Test Info -->
|
||||
<div class="flex items-center justify-between px-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
<div class="flex items-center gap-3">
|
||||
@@ -152,8 +176,8 @@
|
||||
<span class="flex items-center gap-1">
|
||||
<Icon name="chat" size="sm" :stroke-width="2" />
|
||||
{{
|
||||
supportsGeminiImageTest
|
||||
? t('admin.accounts.geminiImageTestMode')
|
||||
supportsImageTest
|
||||
? t('admin.accounts.imageTestMode')
|
||||
: t('admin.accounts.testPrompt')
|
||||
}}
|
||||
</span>
|
||||
@@ -250,6 +274,7 @@ const testPrompt = ref('')
|
||||
const loadingModels = ref(false)
|
||||
let abortController: AbortController | null = null
|
||||
const generatedImages = ref<PreviewImage[]>([])
|
||||
const previewImageUrl = ref('')
|
||||
const prioritizedGeminiModels = ['gemini-3.1-flash-image', 'gemini-2.5-flash-image', 'gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-3-flash-preview', 'gemini-3-pro-preview', 'gemini-2.0-flash']
|
||||
const supportsGeminiImageTest = computed(() => {
|
||||
const modelID = selectedModelId.value.toLowerCase()
|
||||
@@ -258,6 +283,14 @@ const supportsGeminiImageTest = computed(() => {
|
||||
return props.account?.platform === 'gemini' || (props.account?.platform === 'antigravity' && props.account?.type === 'apikey')
|
||||
})
|
||||
|
||||
const supportsOpenAIImageTest = computed(() => {
|
||||
const modelID = selectedModelId.value.toLowerCase()
|
||||
if (!modelID.startsWith('gpt-image-')) return false
|
||||
return props.account?.platform === 'openai'
|
||||
})
|
||||
|
||||
const supportsImageTest = computed(() => supportsGeminiImageTest.value || supportsOpenAIImageTest.value)
|
||||
|
||||
const sortTestModels = (models: ClaudeModel[]) => {
|
||||
const priorityMap = new Map(prioritizedGeminiModels.map((id, index) => [id, index]))
|
||||
|
||||
@@ -284,8 +317,8 @@ watch(
|
||||
)
|
||||
|
||||
watch(selectedModelId, () => {
|
||||
if (supportsGeminiImageTest.value && !testPrompt.value.trim()) {
|
||||
testPrompt.value = t('admin.accounts.geminiImagePromptDefault')
|
||||
if (supportsImageTest.value && !testPrompt.value.trim()) {
|
||||
testPrompt.value = t('admin.accounts.imagePromptDefault')
|
||||
}
|
||||
})
|
||||
|
||||
@@ -325,6 +358,7 @@ const resetState = () => {
|
||||
streamingContent.value = ''
|
||||
errorMessage.value = ''
|
||||
generatedImages.value = []
|
||||
previewImageUrl.value = ''
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
@@ -377,7 +411,7 @@ const startTest = async () => {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model_id: selectedModelId.value,
|
||||
prompt: supportsGeminiImageTest.value ? testPrompt.value.trim() : ''
|
||||
prompt: supportsImageTest.value ? testPrompt.value.trim() : ''
|
||||
}),
|
||||
signal: abortController.signal
|
||||
})
|
||||
@@ -444,8 +478,8 @@ const handleEvent = (event: {
|
||||
addLine(t('admin.accounts.usingModel', { model: event.model }), 'text-cyan-400')
|
||||
}
|
||||
addLine(
|
||||
supportsGeminiImageTest.value
|
||||
? t('admin.accounts.sendingGeminiImageRequest')
|
||||
supportsImageTest.value
|
||||
? t('admin.accounts.sendingImageRequest')
|
||||
: t('admin.accounts.sendingTestMessage'),
|
||||
'text-gray-400'
|
||||
)
|
||||
@@ -466,7 +500,7 @@ const handleEvent = (event: {
|
||||
url: event.image_url,
|
||||
mimeType: event.mime_type
|
||||
})
|
||||
addLine(t('admin.accounts.geminiImageReceived', { count: generatedImages.value.length }), 'text-purple-300')
|
||||
addLine(t('admin.accounts.imageReceived', { count: generatedImages.value.length }), 'text-purple-300')
|
||||
}
|
||||
break
|
||||
|
||||
@@ -500,3 +534,14 @@ const copyOutput = () => {
|
||||
copyToClipboard(text, t('admin.accounts.outputCopied'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -24,13 +24,13 @@ vi.mock('@/composables/useClipboard', () => ({
|
||||
vi.mock('vue-i18n', async () => {
|
||||
const actual = await vi.importActual<typeof import('vue-i18n')>('vue-i18n')
|
||||
const messages: Record<string, string> = {
|
||||
'admin.accounts.geminiImagePromptDefault': 'Generate a cute orange cat astronaut sticker on a clean pastel background.'
|
||||
'admin.accounts.imagePromptDefault': 'Generate a cute orange cat astronaut sticker on a clean pastel background.'
|
||||
}
|
||||
return {
|
||||
...actual,
|
||||
useI18n: () => ({
|
||||
t: (key: string, params?: Record<string, string | number>) => {
|
||||
if (key === 'admin.accounts.geminiImageReceived' && params?.count) {
|
||||
if (key === 'admin.accounts.imageReceived' && params?.count) {
|
||||
return `received-${params.count}`
|
||||
}
|
||||
return messages[key] || key
|
||||
@@ -140,7 +140,7 @@ describe('AccountTestModal', () => {
|
||||
prompt: 'draw a tiny orange cat astronaut'
|
||||
})
|
||||
|
||||
const preview = wrapper.find('img[alt="gemini-test-image-1"]')
|
||||
const preview = wrapper.find('img[alt="test-image-1"]')
|
||||
expect(preview.exists()).toBe(true)
|
||||
expect(preview.attributes('src')).toBe('data:image/png;base64,QUJD')
|
||||
})
|
||||
|
||||
@@ -21,7 +21,9 @@ const openaiModels = [
|
||||
// GPT-5.3 系列
|
||||
'gpt-5.3-codex', 'gpt-5.3-codex-spark',
|
||||
'chatgpt-4o-latest',
|
||||
'gpt-4o-audio-preview', 'gpt-4o-realtime-preview'
|
||||
'gpt-4o-audio-preview', 'gpt-4o-realtime-preview',
|
||||
// GPT Image 系列
|
||||
'gpt-image-1', 'gpt-image-1.5', 'gpt-image-2'
|
||||
]
|
||||
|
||||
// Anthropic Claude
|
||||
|
||||
@@ -3023,7 +3023,7 @@ export default {
|
||||
connectedToApi: 'Connected to API',
|
||||
usingModel: 'Using model: {model}',
|
||||
sendingTestMessage: 'Sending test message: "hi"',
|
||||
sendingGeminiImageRequest: 'Sending Gemini image generation test request...',
|
||||
sendingImageRequest: 'Sending image generation test request...',
|
||||
response: 'Response:',
|
||||
startTest: 'Start Test',
|
||||
testing: 'Testing...',
|
||||
@@ -3035,13 +3035,13 @@ export default {
|
||||
selectTestModel: 'Select Test Model',
|
||||
testModel: 'Test model',
|
||||
testPrompt: 'Prompt: "hi"',
|
||||
geminiImagePromptLabel: 'Image prompt',
|
||||
geminiImagePromptPlaceholder: 'Example: Generate an orange cat astronaut sticker in pixel-art style on a solid background.',
|
||||
geminiImagePromptDefault: 'Generate a cute orange cat astronaut sticker on a clean pastel background.',
|
||||
geminiImageTestHint: 'When a Gemini image model is selected, this test sends a real image-generation request and previews the returned image below.',
|
||||
geminiImageTestMode: 'Mode: Gemini image generation test',
|
||||
geminiImagePreview: 'Generated images:',
|
||||
geminiImageReceived: 'Received test image #{count}',
|
||||
imagePromptLabel: 'Image prompt',
|
||||
imagePromptPlaceholder: 'Example: Generate an orange cat astronaut sticker in pixel-art style on a solid background.',
|
||||
imagePromptDefault: 'Generate a cute orange cat astronaut sticker on a clean pastel background.',
|
||||
imageTestHint: 'When an image model is selected, this test sends a real image-generation request and previews the returned image below.',
|
||||
imageTestMode: 'Mode: Image generation test',
|
||||
imagePreview: 'Generated images:',
|
||||
imageReceived: 'Received test image #{count}',
|
||||
// Stats Modal
|
||||
viewStats: 'View Stats',
|
||||
usageStatistics: 'Usage Statistics',
|
||||
|
||||
@@ -3154,7 +3154,7 @@ export default {
|
||||
connectedToApi: '已连接到 API',
|
||||
usingModel: '使用模型:{model}',
|
||||
sendingTestMessage: '发送测试消息:"hi"',
|
||||
sendingGeminiImageRequest: '发送 Gemini 生图测试请求...',
|
||||
sendingImageRequest: '发送生图测试请求...',
|
||||
response: '响应:',
|
||||
startTest: '开始测试',
|
||||
retry: '重试',
|
||||
@@ -3165,13 +3165,13 @@ export default {
|
||||
selectTestModel: '选择测试模型',
|
||||
testModel: '测试模型',
|
||||
testPrompt: '提示词:"hi"',
|
||||
geminiImagePromptLabel: '生图提示词',
|
||||
geminiImagePromptPlaceholder: '例如:生成一只戴宇航员头盔的橘猫,像素插画风格,纯色背景。',
|
||||
geminiImagePromptDefault: 'Generate a cute orange cat astronaut sticker on a clean pastel background.',
|
||||
geminiImageTestHint: '选择 Gemini 图片模型后,这里会直接发起生图测试,并在下方展示返回图片。',
|
||||
geminiImageTestMode: '模式:Gemini 生图测试',
|
||||
geminiImagePreview: '生成结果:',
|
||||
geminiImageReceived: '已收到第 {count} 张测试图片',
|
||||
imagePromptLabel: '生图提示词',
|
||||
imagePromptPlaceholder: '例如:生成一只戴宇航员头盔的橘猫,像素插画风格,纯色背景。',
|
||||
imagePromptDefault: 'Generate a cute orange cat astronaut sticker on a clean pastel background.',
|
||||
imageTestHint: '选择图片模型后,这里会直接发起生图测试,并在下方展示返回图片。',
|
||||
imageTestMode: '模式:生图测试',
|
||||
imagePreview: '生成结果:',
|
||||
imageReceived: '已收到第 {count} 张测试图片',
|
||||
// Stats Modal
|
||||
viewStats: '查看统计',
|
||||
usageStatistics: '使用统计',
|
||||
|
||||
@@ -628,11 +628,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图片生成计费配置(antigravity 和 gemini 平台) -->
|
||||
<!-- 图片生成计费配置 -->
|
||||
<div
|
||||
v-if="
|
||||
createForm.platform === 'antigravity' ||
|
||||
createForm.platform === 'gemini'
|
||||
createForm.platform === 'gemini' ||
|
||||
createForm.platform === 'openai'
|
||||
"
|
||||
class="border-t pt-4"
|
||||
>
|
||||
@@ -1750,11 +1751,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图片生成计费配置(antigravity 和 gemini 平台) -->
|
||||
<!-- 图片生成计费配置 -->
|
||||
<div
|
||||
v-if="
|
||||
editForm.platform === 'antigravity' ||
|
||||
editForm.platform === 'gemini'
|
||||
editForm.platform === 'gemini' ||
|
||||
editForm.platform === 'openai'
|
||||
"
|
||||
class="border-t pt-4"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user