| """ |
| UI组件模块 |
| """ |
|
|
| import gradio as gr |
| from typing import List, Tuple |
|
|
| from ..config import ( |
| UI_CONFIG, |
| EXAMPLE_PROMPTS, |
| ASPECT_RATIOS |
| ) |
| from ..utils import handle_file_upload, create_image_html |
|
|
|
|
| class UIComponents: |
| """UI组件类""" |
| |
| def __init__(self): |
| self.delete_buttons = [] |
| |
| def create_header(self) -> gr.HTML: |
| """创建页面头部""" |
| return gr.HTML(f""" |
| <h1 class="logo-text">{UI_CONFIG['APP_TITLE']}</h1> |
| <p class="subtitle">{UI_CONFIG['APP_SUBTITLE']}</p> |
| <div class="mode-indicator"> |
| {UI_CONFIG['APP_DESCRIPTION']} |
| </div> |
| """) |
| |
| def create_info_box(self) -> gr.HTML: |
| """创建信息提示框""" |
| return gr.HTML(""" |
| <div class="info-box"> |
| <strong>Usage Instructions:</strong><br> |
| • Get your Veo 3 API Key <a href="https://kie.ai/playground/veo3" target="_blank">👉 here 👈</a><br> |
| • Select your model: Choose Veo 3 Fast for more cost-effective video generation (Free test) or Veo 3 Quality for higher-quality videos.<br> |
| • Enter your prompt (text-to-video) or upload your image (image-to-video).<br> |
| • Click Generate and wait ~1–2 minutes for processing |
| </div> |
| """) |
| |
| def create_input_components(self) -> Tuple[gr.Textbox, gr.Textbox, gr.HTML, gr.File, List[gr.Button], gr.Dropdown, gr.Number, gr.Button]: |
| """创建输入组件""" |
| |
| api_key = gr.Textbox( |
| label="API Key", |
| placeholder="Please enter your KIE AI API Key", |
| type="password", |
| elem_classes="api-key-input" |
| ) |
|
|
| |
| prompt = gr.Textbox( |
| label="Video Prompt", |
| placeholder="Describe the video you want to generate, e.g.: A dog playing in a park...", |
| lines=3, |
| value=UI_CONFIG["DEFAULT_PROMPT"], |
| elem_classes="prompt-input" |
| ) |
| |
| |
| with gr.Group(elem_classes="image-upload-container"): |
| gr.Markdown("### 📸 Image Upload (Optional)") |
| gr.Markdown("*Upload 1 image for image-to-video generation, or leave empty for text-to-video*") |
|
|
| |
| image_display = gr.HTML( |
| value="<div id='image-display-area'><div class='no-images'>No image uploaded - will generate from text only</div></div>", |
| elem_id="image-display" |
| ) |
|
|
| |
| file_upload = gr.File( |
| show_label=False, |
| file_count="single", |
| file_types=["image"], |
| type="filepath", |
| height=120, |
| ) |
|
|
| |
| with gr.Row(elem_id="delete-buttons-row"): |
| delete_label = gr.Markdown("**Delete Images:**", visible=True, elem_id="delete-label") |
| delete_buttons = [] |
| for i in range(1): |
| btn = gr.Button(f"Delete {i + 1}", visible=True, size="sm", elem_id=f"delete-btn-{i}") |
| delete_buttons.append(btn) |
| |
| |
| aspect_ratio = gr.Dropdown( |
| choices=ASPECT_RATIOS, |
| value=UI_CONFIG["DEFAULT_ASPECT_RATIO"], |
| label="Aspect Ratio", |
| info="16:9 for landscape, 9:16 for portrait" |
| ) |
| |
| |
| with gr.Row(): |
| seeds = gr.Number( |
| label="Seed (Optional)", |
| value=10001, |
| info="Random seed for reproducible results (10000-99999)", |
| scale=4 |
| ) |
| random_seed_btn = gr.Button( |
| "🎲", |
| size="sm", |
| scale=1, |
| elem_id="random-seed-btn" |
| ) |
| |
| |
| uploaded_file_state = gr.State(None) |
| |
| return api_key, prompt, image_display, file_upload, delete_buttons, aspect_ratio, seeds, random_seed_btn, uploaded_file_state |
|
|
| def create_output_section(self) -> Tuple[gr.Video, gr.Textbox]: |
| """创建输出区域""" |
| |
| output_video = gr.Video( |
| show_label=False, |
| elem_id="output-video", |
| height=400, |
| container=True |
| ) |
|
|
| |
| status = gr.Textbox( |
| label="Processing Status", |
| interactive=False, |
| lines=2, |
| value="Ready, please enter a prompt to generate video..." |
| ) |
| |
| return output_video, status |
| |
| def create_examples(self) -> gr.Examples: |
| """创建示例""" |
| return gr.Examples( |
| examples=[[prompt, None] for prompt in EXAMPLE_PROMPTS], |
| inputs=[], |
| label="Video Prompt Examples" |
| ) |
| |
| def create_generate_button(self) -> gr.Button: |
| """创建生成按钮""" |
| return gr.Button( |
| "🚀 Start Generation", |
| variant="primary", |
| size="lg" |
| ) |
| |
| def setup_file_upload_handlers(self, file_upload, image_display, delete_buttons, uploaded_file_state): |
| """设置文件上传处理器""" |
| def on_file_upload(new_files): |
| if not new_files: |
| return gr.update(value=None), "<div id='image-display-area'><div class='no-images'>No image uploaded - will generate from text only</div></div>", gr.update(visible=False), None |
| |
| |
| file_path = new_files[0] if isinstance(new_files, list) else new_files |
| |
| |
| html_content = create_image_html([file_path]) |
| return gr.update(value=None), html_content, gr.update(visible=True, value="Delete Image 1"), file_path |
|
|
| file_upload.upload( |
| fn=on_file_upload, |
| inputs=[file_upload], |
| outputs=[file_upload, image_display, delete_buttons[0], uploaded_file_state] |
| ) |
| |
| def setup_delete_handlers(self, delete_buttons, image_display, uploaded_file_state): |
| """设置删除按钮处理器""" |
| def delete_image(): |
| return "<div id='image-display-area'><div class='no-images'>No image uploaded - will generate from text only</div></div>", gr.update(visible=False), None |
|
|
| |
| delete_buttons[0].click( |
| fn=delete_image, |
| outputs=[image_display, delete_buttons[0], uploaded_file_state] |
| ) |
|
|