""" UI界面模块 """ import gradio as gr from typing import List, Tuple, Optional from .components import UIComponents from ..api import VideoProcessor from ..config import UI_CONFIG class Veo3Interface: """Veo3界面类""" def __init__(self): self.components = UIComponents() self.video_processor = VideoProcessor() self.demo = None def create_interface(self) -> gr.Blocks: """创建完整的界面""" # 读取CSS样式 with open("static/css/styles.css", "r", encoding="utf-8") as f: css = f.read() print(f"CSS样式读取成功\n") with gr.Blocks(css=css, theme=gr.themes.Base()) as demo: self.demo = demo # 页面头部 with gr.Column(elem_classes="header-container"): self.components.create_header() with gr.Column(elem_classes="main-content"): # 信息提示框 self.components.create_info_box() with gr.Row(equal_height=True): # 左侧 - 输入区域 with gr.Column(scale=1): # 输入组件(包含图片上传) api_key, prompt, image_display, file_upload, delete_buttons, aspect_ratio, seeds, random_seed_btn, uploaded_file_state = self.components.create_input_components() # 生成按钮 generate_btn = self.components.create_generate_button() # 右侧 - 输出区域 with gr.Column(scale=1): # 输出区域 output_video, status = self.components.create_output_section() # 示例 examples = self.components.create_examples() examples.inputs = [prompt, api_key] # 设置事件处理器 self._setup_event_handlers( generate_btn, prompt, api_key, aspect_ratio, seeds, random_seed_btn, file_upload, image_display, delete_buttons, output_video, status, uploaded_file_state ) # 添加JavaScript代码 self._add_javascript() return demo def _setup_event_handlers( self, generate_btn, prompt, api_key, aspect_ratio, seeds, random_seed_btn, file_upload, image_display, delete_buttons, output_video, status, uploaded_file_state ): """设置事件处理器""" # 文件上传处理器 self.components.setup_file_upload_handlers(file_upload, image_display, delete_buttons, uploaded_file_state) # 删除按钮处理器 self.components.setup_delete_handlers(delete_buttons, image_display, uploaded_file_state) # 随机种子按钮处理器 def generate_random_seed(): """生成随机种子""" import random return random.randint(10000, 99999) # API要求的范围 random_seed_btn.click( fn=generate_random_seed, outputs=[seeds] ) # 生成按钮事件 def prepare_and_generate(prompt, api_key, aspect_ratio, seeds, saved_file_path, progress=gr.Progress()): """生成视频的主函数""" # 处理文件上传参数,如果为None则转换为空列表 file_paths = [saved_file_path] if saved_file_path is not None else [] return self.video_processor.process_veo3_video( prompt, file_paths, api_key, aspect_ratio, None, seeds, False, progress ) generate_btn.click( fn=prepare_and_generate, inputs=[prompt, api_key, aspect_ratio, seeds, uploaded_file_state], outputs=[output_video, status] ) def _add_javascript(self): """添加JavaScript代码""" self.demo.load(None, None, None, js=""" () => { // 创建全局删除函数 window.deleteImageByIndex = function(index) { // Gradio的elem_id设置在包装器上,需要找到内部的button let deleteBtn = null; // 方法1: 通过ID找到包装器,然后找内部的button const wrapper = document.getElementById(`delete-btn-${index}`); if (wrapper) { deleteBtn = wrapper.querySelector('button'); } // 方法2: 如果方法1失败,查找所有按钮并通过文本内容匹配 if (!deleteBtn) { const allButtons = document.querySelectorAll('button'); for (let btn of allButtons) { if (btn.textContent.includes(`Delete Image ${index + 1}`)) { deleteBtn = btn; break; } } } if (deleteBtn) { deleteBtn.click(); } else { // 调试信息 document.querySelectorAll('[id^="delete-btn-"]').forEach(elem => { console.log(elem.id, elem.tagName, elem.querySelector('button')); }); } }; // 美化文件上传区域 function enhanceFileUpload() { const fileInputs = document.querySelectorAll('.gr-file'); fileInputs.forEach(input => { // 替换中文文本为英文 const textElements = input.querySelectorAll('.wrap > div'); textElements.forEach((element, index) => { const text = element.textContent.trim(); // 替换各种可能的中文文本 if (text.includes('将文件拖放到此处') || text.includes('拖放文件到此处')) { element.textContent = 'Drag and drop files here'; } else if (text.includes('点击上传') || text.includes('点击选择文件')) { element.textContent = 'or click to upload'; } else if (text.includes('- 或 -') || text.includes('或')) { element.textContent = '- or -'; } }); // 如果还有中文文本,直接替换整个内容 const wrap = input.querySelector('.wrap'); if (wrap) { const allText = wrap.textContent; if (allText.includes('将文件') || allText.includes('点击上传')) { wrap.innerHTML = `