diff --git a/application/services/video_generation_service.go b/application/services/video_generation_service.go index abe813a..f1e063a 100644 --- a/application/services/video_generation_service.go +++ b/application/services/video_generation_service.go @@ -458,7 +458,8 @@ func (s *VideoGenerationService) getVideoClient(provider string, modelName strin var queryEndpoint string switch config.Provider { - case "chatfire": + case "chatfire", "qingyun": + // 青云和 Chatfire 都使用 OpenAI 兼容格式 endpoint = "/video/generations" queryEndpoint = "/video/task/{taskId}" return video.NewChatfireClient(baseURL, apiKey, model, endpoint, queryEndpoint), nil diff --git a/pkg/image/volcengine_image_client.go b/pkg/image/volcengine_image_client.go index 87b920d..243f7fb 100644 --- a/pkg/image/volcengine_image_client.go +++ b/pkg/image/volcengine_image_client.go @@ -24,7 +24,7 @@ type VolcEngineImageRequest struct { Image []string `json:"image,omitempty"` SequentialImageGeneration string `json:"sequential_image_generation,omitempty"` Size string `json:"size,omitempty"` - Watermark bool `json:"watermark,omitempty"` + Watermark bool `json:"watermark"` } type VolcEngineImageResponse struct { @@ -82,7 +82,11 @@ func (c *VolcEngineImageClient) GenerateImage(prompt string, opts ...ImageOption } size := options.Size - if size == "" { + // 火山引擎 img2img 模式要求图片尺寸至少 3686400 像素(约1920x1920) + // 当使用参考图片时,强制使用 2K 尺寸 + if len(options.ReferenceImages) > 0 { + size = "2K" + } else if size == "" || size == "1024x1024" { if model == "doubao-seedream-4-5-251128" { size = "2K" } else { diff --git a/web/src/components/common/AIConfigDialog.vue b/web/src/components/common/AIConfigDialog.vue index a923068..a8c6094 100644 --- a/web/src/components/common/AIConfigDialog.vue +++ b/web/src/components/common/AIConfigDialog.vue @@ -288,6 +288,18 @@ const providerConfigs: Record = { 'doubao-seed-1-8-251228' ] }, + { + id: 'qingyun', + name: '青云', + models: [ + 'gpt-5.2', + 'claude-opus-4-5-20251101', + 'gemini-3-pro-preview', + 'deepseek-v3.2', + 'qwen-max', + 'doubao-seed-1-8-251228' + ] + }, { id: 'gemini', name: 'Google Gemini', @@ -305,6 +317,17 @@ const providerConfigs: Record = { name: 'Chatfire', models: ['doubao-seedream-4-5-251128', 'nano-banana-pro'] }, + { + id: 'qingyun', + name: '青云', + models: [ + 'gpt-image-1.5', + 'gemini-3-pro-image-preview', + 'jimeng-4.5', + 'jimeng-4.1', + 'mj_imagine' + ] + }, { id: 'gemini', name: 'Google Gemini', @@ -337,6 +360,19 @@ const providerConfigs: Record = { 'sora-2-pro' ] }, + { + id: 'qingyun', + name: '青云', + models: [ + 'grok-video-3', + 'veo3.1-pro-4k', + 'sora-2-all', + 'sora-2-pro-all', + 'jimeng-video-3.0', + 'kling-video-1.5', + 'wan2.6-i2v' + ] + }, { id: 'minimax', name: 'MiniMax 海螺', @@ -444,7 +480,8 @@ const generateConfigName = (provider: string, serviceType: AIServiceType): strin 'chatfire': 'ChatFire', 'openai': 'OpenAI', 'gemini': 'Gemini', - 'google': 'Google' + 'google': 'Google', + 'qingyun': '青云' } const serviceNames: Record = { @@ -609,6 +646,8 @@ const handleProviderChange = () => { form.base_url = 'https://ark.cn-beijing.volces.com/api/v3' } else if (form.provider === 'openai') { form.base_url = 'https://api.openai.com/v1' + } else if (form.provider === 'qingyun') { + form.base_url = 'https://api.qingyuntop.top/v1' } else { // chatfire 和其他厂商 form.base_url = 'https://api.chatfire.site/v1' diff --git a/web/src/components/editor/VideoTimelineEditor.vue b/web/src/components/editor/VideoTimelineEditor.vue index 368fb4f..3a18498 100644 --- a/web/src/components/editor/VideoTimelineEditor.vue +++ b/web/src/components/editor/VideoTimelineEditor.vue @@ -2162,7 +2162,7 @@ defineExpose({ } .media-grid { - max-height: 450px; + flex: 1; overflow-y: auto; padding: 12px; display: grid; @@ -2193,7 +2193,6 @@ defineExpose({ position: relative; background: var(--bg-secondary); border-radius: 6px; - overflow: hidden; cursor: move; border: 1px solid var(--border-primary); transition: all 0.3s; @@ -2223,6 +2222,8 @@ defineExpose({ aspect-ratio: 16/9; background: var(--bg-card-hover); cursor: pointer; + overflow: hidden; + border-radius: 6px 6px 0 0; video { width: 100%; diff --git a/web/src/views/drama/ProfessionalEditor.vue b/web/src/views/drama/ProfessionalEditor.vue index 31e2ebe..baabcd3 100644 --- a/web/src/views/drama/ProfessionalEditor.vue +++ b/web/src/views/drama/ProfessionalEditor.vue @@ -292,12 +292,20 @@ -

生成中...

+

{{ img.status === 'failed' ? 'FAILED' : '生成中...' }}

{{ getStatusText(img.status) }} {{ getFrameTypeText(img.frame_type) }}
+ @@ -1652,7 +1660,28 @@ const extractLastFrame = async () => { } } -// 获取状态标签类型 +// 删除图片生成记录 +const deleteImage = async (imageId: number) => { + try { + await ElMessageBox.confirm('确定要删除该图片吗?', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }) + + await imageAPI.deleteImage(imageId) + ElMessage.success('删除成功') + + // 刷新图片列表 + if (currentStoryboard.value) { + await loadStoryboardImages(currentStoryboard.value.id, selectedFrameType.value) + } + } catch (error: any) { + if (error !== 'cancel') { + ElMessage.error('删除失败: ' + (error.message || '未知错误')) + } + } +} const getStatusType = (status: string) => { const statusMap: Record = { pending: 'info', @@ -4317,4 +4346,20 @@ onBeforeUnmount(() => { max-height: 80px; overflow-y: auto; } + +.image-item { + position: relative; +} + +.delete-image-btn { + position: absolute; + top: 4px; + right: 4px; + opacity: 0; + transition: opacity 0.2s; +} + +.image-item:hover .delete-image-btn { + opacity: 1; +} \ No newline at end of file