diff --git a/pkg/image/gemini_image_client.go b/pkg/image/gemini_image_client.go index 3ffa427..08e9926 100644 --- a/pkg/image/gemini_image_client.go +++ b/pkg/image/gemini_image_client.go @@ -107,7 +107,7 @@ func NewGeminiImageClient(baseURL, apiKey, model, endpoint string) *GeminiImageC func (c *GeminiImageClient) GenerateImage(prompt string, opts ...ImageOption) (*ImageResult, error) { options := &ImageOptions{ - Size: "1024x1024", + Size: "1920x1920", Quality: "standard", } diff --git a/pkg/image/volcengine_image_client.go b/pkg/image/volcengine_image_client.go index 87b920d..9373516 100644 --- a/pkg/image/volcengine_image_client.go +++ b/pkg/image/volcengine_image_client.go @@ -63,7 +63,7 @@ func NewVolcEngineImageClient(baseURL, apiKey, model, endpoint, queryEndpoint st func (c *VolcEngineImageClient) GenerateImage(prompt string, opts ...ImageOption) (*ImageResult, error) { options := &ImageOptions{ - Size: "1024x1024", + Size: "1920x1920", Quality: "standard", } diff --git a/web/src/views/drama/ProfessionalEditor.vue b/web/src/views/drama/ProfessionalEditor.vue index 4ccd6bc..47c08e5 100644 --- a/web/src/views/drama/ProfessionalEditor.vue +++ b/web/src/views/drama/ProfessionalEditor.vue @@ -360,6 +360,33 @@
+ + +
+
+ + 上一镜头 #{{ previousStoryboard?.storyboard_number }} 尾帧 + + 点击添加为首帧参考 +
+
+
+ +
+ ✓ +
+
+
+
+ +
@@ -883,7 +910,7 @@ import { ElMessage, ElMessageBox } from 'element-plus' import { ArrowLeft, Plus, Picture, VideoPlay, VideoPause, View, Setting, Upload, MagicStick, VideoCamera, ZoomIn, ZoomOut, Top, Bottom, Check, Close, Right, - Timer, Calendar, Clock, Loading, WarningFilled, Delete + Timer, Calendar, Clock, Loading, WarningFilled, Delete, Connection } from '@element-plus/icons-vue' import { dramaAPI } from '@/api/drama' import { generateFramePrompt, type FrameType } from '@/api/frame' @@ -1226,6 +1253,67 @@ const currentStoryboard = computed(() => { return storyboards.value.find(s => String(s.id) === String(currentStoryboardId.value)) || null }) +// 获取上一个镜头 +const previousStoryboard = computed(() => { + if (!currentStoryboardId.value || storyboards.value.length < 2) return null + const currentIndex = storyboards.value.findIndex(s => String(s.id) === String(currentStoryboardId.value)) + if (currentIndex <= 0) return null + return storyboards.value[currentIndex - 1] +}) + +// 上一个镜头的尾帧图片列表(支持多个) +const previousStoryboardLastFrames = ref([]) + +// 加载上一个镜头的尾帧 +const loadPreviousStoryboardLastFrame = async () => { + if (!previousStoryboard.value) { + previousStoryboardLastFrames.value = [] + return + } + try { + const result = await imageAPI.listImages({ + storyboard_id: previousStoryboard.value.id, + frame_type: 'last', + page: 1, + page_size: 10 + }) + const images = result.items || [] + previousStoryboardLastFrames.value = images.filter((img: any) => img.status === 'completed' && img.image_url) + } catch (error) { + console.error('加载上一镜头尾帧失败:', error) + previousStoryboardLastFrames.value = [] + } +} + +// 选择上一镜头尾帧作为首帧参考 +const selectPreviousLastFrame = (img: any) => { + // 检查是否已选中,已选中则取消 + const currentIndex = selectedImagesForVideo.value.indexOf(img.id) + if (currentIndex > -1) { + selectedImagesForVideo.value.splice(currentIndex, 1) + ElMessage.success('已取消首帧参考') + return + } + + // 参考handleImageSelect的逻辑,根据模式处理 + if (!selectedReferenceMode.value || selectedReferenceMode.value === 'single') { + // 单图模式或未选模式:直接替换 + selectedImagesForVideo.value = [img.id] + } else if (selectedReferenceMode.value === 'first_last') { + // 首尾帧模式:作为首帧参考 + selectedImagesForVideo.value = [img.id] + } else if (selectedReferenceMode.value === 'multiple') { + // 多图模式:添加到列表 + const capability = currentModelCapability.value + if (capability && selectedImagesForVideo.value.length >= capability.maxImages) { + ElMessage.warning(`最多只能选择${capability.maxImages}张图片`) + return + } + selectedImagesForVideo.value.push(img.id) + } + ElMessage.success('已添加为首帧参考') +} + // 监听帧类型切换,从存储中加载或清空 watch(selectedFrameType, (newType) => { // 切换帧类型时,停止之前的轮询,避免旧结果覆盖新帧类型 @@ -1269,6 +1357,7 @@ watch(currentStoryboard, async (newStoryboard) => { generatedImages.value = [] generatedVideos.value = [] videoReferenceImages.value = [] + previousStoryboardLastFrames.value = [] return } @@ -1297,6 +1386,9 @@ watch(currentStoryboard, async (newStoryboard) => { // 加载该分镜的视频列表 await loadStoryboardVideos(newStoryboard.id) + + // 加载上一镜头的尾帧 + await loadPreviousStoryboardLastFrame() }) // 监听提示词变化,自动保存到sessionStorage @@ -1801,13 +1893,17 @@ const selectedImageObjects = computed(() => { const firstFrameSlotImage = computed(() => { if (selectedImagesForVideo.value.length === 0) return null const firstImageId = selectedImagesForVideo.value[0] - return videoReferenceImages.value.find(img => img.id === firstImageId) + // 同时搜索当前镜头图片和上一镜头尾帧 + return videoReferenceImages.value.find(img => img.id === firstImageId) + || previousStoryboardLastFrames.value.find(img => img.id === firstImageId) }) // 首尾帧模式:获取尾帧图片 const lastFrameSlotImage = computed(() => { if (!selectedLastImageForVideo.value) return null + // 同时搜索当前镜头图片和上一镜头尾帧 return videoReferenceImages.value.find(img => img.id === selectedLastImageForVideo.value) + || previousStoryboardLastFrames.value.find(img => img.id === selectedLastImageForVideo.value) }) // 移除已选择的图片