improve:优化在线体验的图片生成功能
This commit is contained in:
1 parent
2e7f0beca3
commit
6d277d1a97
5 files changed
+51
-29
No files matched your search
@@ -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"},
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
Reference in new issue
Block a user