improve:优化在线体验的图片生成功能

This commit is contained in:
qian committed 2026-06-13 10:08:46 +08:00
1 parent 2e7f0beca3
commit 6d277d1a97
5 files changed
+51 -29

No files matched your search

+5
View File
@@ -20,6 +20,11 @@ func convertImageRequestToChat(requestBody []byte, info *common.RelayInfo) (io.R
return nil, fmt.Errorf("parse image request: %w", err)
}
// Banana 内生图模式不支持 N>1,每次请求只生成一张图
if imgReq.N != nil && *imgReq.N > 1 {
return nil, constant.NewRequestError("Gemini native image generation (Banana mode) does not support n > 1; use Imagen models (e.g. imagen-3.0-generate-002) or call the endpoint multiple times", nil)
}
generationConfig := &dto.GeminiGenerationConfig{
ResponseModalities: []string{"TEXT", "IMAGE"},
}
+7 -4
View File
@@ -26,14 +26,17 @@ const (
)
// GetTimeoutSeconds 返回请求超时秒数。
// 渠道自定义优先,未配置时根据请求模式返回默认值(图片生成 600s,其余 60s)。
// 图片生成模式强制最低 600s(即使渠道配置了更短的自定义超时),其余模式渠道自定义优先。
func (s ChannelSettings) GetTimeoutSeconds(relayMode int) int {
if constant.RelayMode(relayMode) == constant.RelayModeImagesGenerations {
if s.TimeoutSeconds > ImagesGenerationTimeoutSecs {
return s.TimeoutSeconds
}
return ImagesGenerationTimeoutSecs
}
if s.TimeoutSeconds > 0 {
return s.TimeoutSeconds
}
if constant.RelayMode(relayMode) == constant.RelayModeImagesGenerations {
return ImagesGenerationTimeoutSecs
}
return DefaultTimeoutSeconds
}
@@ -113,11 +113,26 @@ function parseValue(raw: string): string | number | boolean {
return raw
}
// 切换模型时自动应用预设
// 默认参数项(无预设时显示,用户自行填写值)
const DEFAULT_PARAMS = [
{ key: 'size', value: '' },
{ key: 'quality', value: '' },
]
function applyDefaults() {
customParams.value = []
for (const { key, value } of DEFAULT_PARAMS) {
addParam(key, value)
}
}
// 切换模型时自动应用预设,无预设则显示默认参数项
watch(selectedModel, () => {
const preset = currentPreset.value
if (preset) {
applyPreset()
} else {
applyDefaults()
}
}, { immediate: true })
@@ -225,13 +240,6 @@ function downloadImage(img: ImageResult, idx: number) {
>
重置预设
</button>
<button
class="btn btn-sm btn-secondary"
@click="addParam()"
>
<Icon name="plus" size="xs" />
添加
</button>
</div>
</div>
@@ -239,7 +247,7 @@ function downloadImage(img: ImageResult, idx: number) {
<div
v-if="customParams.length === 0"
class="rounded-xl border-2 border-dashed border-gray-200 py-6 text-center cursor-pointer hover:border-primary-300 hover:bg-primary-50/30 transition-colors duration-200"
@click="currentPreset ? applyPreset() : addParam()"
@click="currentPreset ? applyPreset() : applyDefaults()"
>
<Icon name="plus" size="sm" class="text-gray-300 mx-auto mb-2" />
<p class="text-xs text-gray-400">
@@ -15,6 +15,7 @@ export interface ParamDef {
export interface ModelParamGroup {
key: string
label: string
docUrl?: string
params: ParamDef[]
}
@@ -35,11 +36,13 @@ const imageGroups: ModelParamGroup[] = [
{
key: 'gemini-image',
label: 'Gemini',
docUrl: 'https://ai.google.dev/gemini-api/docs/image-generation?hl=zh-cn',
params: [
{ name: 'prompt', type: 'string', required: true, description: '图像描述文本,支持中英文,可描述画面内容、风格、构图等' },
{ name: 'model', type: 'string', required: true, description: '模型 ID,如 gemini-2.5-flash-image、gemini-3-pro-image-preview' },
{ name: 'n', type: 'integer', required: false, default: '1', values: ['1'], description: '生成图像数量,目前仅支持 1' },
{ name: 'size', type: 'string', required: false, default: '1024x1024', values: ['1024x1024', '1536x1024', '1024x1536'], description: '图像尺寸(宽x高)。支持正方形和横/纵向' },
{ name: 'model', type: 'string', required: true, description: '模型 ID。Banana 内生图:gemini-2.5-flash-image、gemini-3-pro-image-preview 等;Imagen:imagen-3.0-generate-002 等' },
{ name: 'n', type: 'integer', required: false, default: '1', values: ['1', '2', '3', '4'], description: '生成图像数量。Banana 模式(gemini-*-image)仅支持 1,传 N>1 会报错;Imagen 模式(imagen-*)支持 1-4' },
{ name: 'size', type: 'string', required: false, default: '1024x1024', values: ['1024x1024', '1536x1024', '1024x1536', '1280x720', '720x1280', '1152x864', '864x1152', '1344x576', '1:1', '16:9', '9:16', '3:2', '2:3', '4:3', '3:4', '21:9'], description: '图像尺寸。支持 OpenAI 格式(宽x高)和 Gemini 原生比例格式(如 1:1、16:9)' },
{ name: 'quality', type: 'string', required: false, default: '1K', values: ['hd', 'high', 'standard', 'medium', 'low', '256', '512', '1K', '2K', '4K'], description: '图像分辨率/质量。OpenAI 格式 hd/high → 2K;Gemini 原生格式 256/512/1K/2K/4K 直接控制输出分辨率' },
{ name: 'response_format', type: 'string', required: false, default: 'b64_json', values: ['b64_json', 'url'], description: '返回格式。b64_json 返回 Base64 编码;url 返回临时下载链接' },
],
},
@@ -165,6 +168,22 @@ const currentGroup = computed(() => groups.value.find(g => g.key === activeGroup
</table>
</div>
<!-- 官方文档链接 -->
<div v-if="currentGroup?.docUrl" class="mt-3 rounded-xl border border-primary-200 bg-primary-50/60 px-4 py-3">
<div class="flex items-start gap-2">
<Icon name="document" size="sm" class="text-primary-500 mt-0.5 flex-shrink-0" />
<div class="text-xs text-primary-700">
官方文档:
<a
:href="currentGroup.docUrl"
target="_blank"
rel="noopener noreferrer"
class="font-medium text-primary-600 hover:text-primary-700 underline underline-offset-2 break-all"
>{{ currentGroup.docUrl }}</a>
</div>
</div>
</div>
<!-- 提示信息 -->
<div class="mt-4 rounded-xl border border-amber-200 bg-amber-50/60 px-4 py-3">
<div class="flex items-start gap-2">
@@ -1,7 +1,6 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { createPlaygroundApi } from '@/utils/playgroundApi'
import ParamRefModal from './ParamRefModal.vue'
import Icon from '@/components/common/Icon.vue'
import BaseSelect from '../../../components/common/BaseSelect.vue'
@@ -15,7 +14,6 @@ const props = defineProps<{ models: ModelItem[]; apiKey: string }>()
const submitting = ref(false)
const selectedModel = ref(props.models[0]?.model_id || '')
const prompt = ref('')
const showParamRef = ref(false)
const resolution = ref('1280x720')
const duration = ref(5)
@@ -214,15 +212,6 @@ function downloadVideo() {
<label class="input-label">时长({{ duration }} 秒)</label>
<input v-model.number="duration" type="number" class="input" min="5" max="15" step="1" />
</div>
<div class="flex justify-end">
<button
class="text-xs px-2.5 py-1.5 rounded-lg text-gray-600 hover:border-primary-300 hover:text-primary-600 transition-colors duration-150 cursor-pointer flex items-center gap-1.5 border border-gray-200"
@click="showParamRef = true"
>
<Icon name="bookOpen" size="xs" />
参数参考
</button>
</div>
<button class="btn btn-primary w-full" :disabled="submitting || polling || !prompt.trim()" @click="submitTask">
{{ submitting ? '提交中...' : polling ? '生成中...' : '生成视频' }}
</button>
@@ -304,6 +293,4 @@ function downloadVideo() {
</div>
</div>
<!-- 参数参考弹窗 -->
<ParamRefModal :show="showParamRef" mode="video" @close="showParamRef = false" />
</template>