Spaces:
Running
on
Zero
Running
on
Zero
| import gradio as gr | |
| import numpy as np | |
| import os, random, json, spaces, torch, time | |
| import torch | |
| from transformers import AutoProcessor, AutoTokenizer | |
| from longcat_image.models import LongCatImageTransformer2DModel | |
| from longcat_image.pipelines import LongCatImagePipeline | |
| from utils.image_utils import rescale_image | |
| from utils.prompt_utils import polish_prompt | |
| # Force rebuild v20 | |
| MODEL_REPO = "meituan-longcat/LongCat-Image" | |
| MAX_SEED = np.iinfo(np.int32).max | |
| print("Loading LongCat-Image model... v12") | |
| text_processor = AutoTokenizer.from_pretrained( | |
| MODEL_REPO, | |
| subfolder = 'tokenizer' | |
| ) | |
| transformer = LongCatImageTransformer2DModel.from_pretrained( | |
| MODEL_REPO , | |
| subfolder = 'transformer', | |
| torch_dtype=torch.bfloat16, | |
| use_safetensors=True | |
| ).to("cuda") | |
| pipe = LongCatImagePipeline.from_pretrained( | |
| MODEL_REPO, | |
| transformer=transformer, | |
| text_processor=text_processor | |
| ) | |
| pipe.to("cuda", torch.bfloat16) | |
| print("Model loaded successfully!") | |
| def generate_image( | |
| prompt, | |
| aspect_ratio, | |
| progress=gr.Progress(track_tqdm=True), | |
| ): | |
| """Generate an image using LongCat-Image model.""" | |
| if not prompt.strip(): | |
| raise gr.Error("Please enter a prompt to generate an image.") | |
| # Parse aspect ratio to get dimensions | |
| aspect_ratio_map = { | |
| "1:1 (1024x1024)": (1024, 1024), | |
| "4:3 (1024x768)": (1024, 768), | |
| "3:4 (768x1024)": (768, 1024), | |
| "16:9 (1024x576)": (1024, 576), | |
| "9:16 (576x1024)": (576, 1024), | |
| } | |
| width, height = aspect_ratio_map.get(aspect_ratio, (1024, 1024)) | |
| # Generate random seed | |
| seed = random.randint(0, MAX_SEED) | |
| generator = torch.Generator().manual_seed(seed) | |
| progress(0.1, desc="Generating image...") | |
| try: | |
| image = pipe( | |
| prompt=prompt, | |
| negative_prompt="blurry ugly bad", | |
| width=width, | |
| height=height, | |
| generator=generator, | |
| guidance_scale=1.5, | |
| num_inference_steps=20, | |
| enable_prompt_rewrite=False | |
| ).images[0] | |
| progress(1.0, desc="Complete!") | |
| return image | |
| except Exception as e: | |
| raise gr.Error(f"Generation failed: {str(e)}") | |
| # Apple-style CSS for dark theme | |
| apple_css = """ | |
| /* Global Styles */ | |
| .gradio-container { | |
| max-width: 85vw !important; | |
| margin: 0 auto !important; | |
| padding: 48px 20px !important; | |
| font-family: -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', 'Roboto', sans-serif !important; | |
| } | |
| /* Disable all transitions globally to prevent layout shifts */ | |
| * { | |
| transition: none !important; | |
| animation: none !important; | |
| } | |
| /* Header */ | |
| .header-container { | |
| text-align: left; | |
| margin-bottom: 24px; | |
| } | |
| .main-title { | |
| font-size: 32px !important; | |
| font-weight: 600 !important; | |
| letter-spacing: -0.02em !important; | |
| line-height: 1.07 !important; | |
| color: #f5f5f7 !important; | |
| margin: 0 0 16px 0 !important; | |
| } | |
| /* Input Section */ | |
| .input-section { | |
| background: #1d1d1f; | |
| border-radius: 18px; | |
| padding: 32px; | |
| box-shadow: 0 2px 12px rgba(0, 0, 0, 0.4); | |
| border: 1px solid #424245; | |
| } | |
| /* Textbox */ | |
| textarea { | |
| font-size: 17px !important; | |
| line-height: 1.47 !important; | |
| border-radius: 12px !important; | |
| border: 1px solid #424245 !important; | |
| padding: 12px 16px !important; | |
| background: #1d1d1f !important; | |
| color: #f5f5f7 !important; | |
| font-family: -apple-system, BlinkMacSystemFont, 'Inter', sans-serif !important; | |
| min-height: 200px !important; | |
| max-height: 400px !important; | |
| height: 200px !important; | |
| resize: vertical !important; | |
| overflow-y: auto !important; | |
| margin-bottom: 16px !important; | |
| } | |
| textarea:focus { | |
| border-color: #0071e3 !important; | |
| box-shadow: 0 0 0 4px rgba(0, 113, 227, 0.15) !important; | |
| outline: none !important; | |
| } | |
| textarea::placeholder { | |
| color: #86868b !important; | |
| } | |
| /* Button */ | |
| button.primary { | |
| font-size: 17px !important; | |
| font-weight: 400 !important; | |
| padding: 12px 32px !important; | |
| border-radius: 980px !important; | |
| background: #0071e3 !important; | |
| border: none !important; | |
| color: #ffffff !important; | |
| min-height: 44px !important; | |
| letter-spacing: -0.01em !important; | |
| cursor: pointer !important; | |
| } | |
| button.primary:hover { | |
| background: #0077ed !important; | |
| } | |
| button.primary:active { | |
| opacity: 0.9 !important; | |
| } | |
| /* Output Section */ | |
| div.output-section { | |
| background: #ffffff !important; | |
| border-radius: 18px; | |
| padding: 32px; | |
| box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); | |
| border: none !important; | |
| overflow: hidden; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| min-height: 80vh; | |
| max-height: 90vh; | |
| will-change: auto; | |
| position: relative; | |
| } | |
| .output-section * { | |
| transform: none !important; | |
| transition: none !important; | |
| animation: none !important; | |
| } | |
| .output-section img { | |
| border-radius: 12px !important; | |
| max-width: 100% !important; | |
| max-height: 85vh !important; | |
| width: auto !important; | |
| height: auto !important; | |
| object-fit: contain !important; | |
| transform: none !important; | |
| transition: none !important; | |
| animation: none !important; | |
| backface-visibility: hidden; | |
| -webkit-backface-visibility: hidden; | |
| } | |
| /* Make progress/generation area fill more space */ | |
| .output-section > div { | |
| width: 100% !important; | |
| min-height: 75vh !important; | |
| max-height: 85vh !important; | |
| display: flex !important; | |
| align-items: center !important; | |
| justify-content: center !important; | |
| } | |
| .output-section > div > div { | |
| min-height: 75vh !important; | |
| max-height: 85vh !important; | |
| width: 100% !important; | |
| display: flex !important; | |
| align-items: center !important; | |
| justify-content: center !important; | |
| } | |
| .output-section * { | |
| max-width: 100% !important; | |
| } | |
| /* Progress */ | |
| .progress-bar { | |
| background: #0071e3 !important; | |
| border-radius: 4px !important; | |
| } | |
| /* Labels */ | |
| label { | |
| color: #f5f5f7 !important; | |
| } | |
| /* Dropdown */ | |
| .gr-dropdown { | |
| background: #1d1d1f !important; | |
| border: 1px solid #424245 !important; | |
| border-radius: 12px !important; | |
| color: #f5f5f7 !important; | |
| } | |
| select, .wrap-inner { | |
| background: #1d1d1f !important; | |
| color: #f5f5f7 !important; | |
| border-color: #424245 !important; | |
| } | |
| /* Fix column width to prevent shrinking */ | |
| .input-section { | |
| min-width: 550px !important; | |
| max-width: 550px !important; | |
| width: 550px !important; | |
| flex-shrink: 0 !important; | |
| flex-grow: 0 !important; | |
| } | |
| /* Lock the output section to fill remaining space */ | |
| .output-section { | |
| flex-grow: 1 !important; | |
| flex-shrink: 0 !important; | |
| flex-basis: auto !important; | |
| } | |
| /* Prevent Gradio columns from flexing */ | |
| .gradio-column { | |
| flex-shrink: 0 !important; | |
| } | |
| /* Stabilize row layout */ | |
| .gradio-row, | |
| div.gradio-row, | |
| .gradio-container .gradio-row, | |
| .gradio-container > .gradio-row, | |
| .gradio-container div.gradio-row { | |
| align-items: flex-start !important; | |
| flex-direction: row !important; | |
| display: flex !important; | |
| flex-wrap: nowrap !important; | |
| width: 100% !important; | |
| } | |
| /* Force columns to stay inline */ | |
| .gradio-row > .gradio-column, | |
| .gradio-row > div { | |
| display: inline-flex !important; | |
| vertical-align: top !important; | |
| } | |
| /* First column - input section */ | |
| .gradio-row > .gradio-column:first-child, | |
| .gradio-row > div:first-child { | |
| width: 550px !important; | |
| min-width: 550px !important; | |
| max-width: 550px !important; | |
| flex: 0 0 550px !important; | |
| } | |
| /* Second column - output section */ | |
| .gradio-row > .gradio-column:last-child, | |
| .gradio-row > div:last-child { | |
| flex: 1 1 auto !important; | |
| min-width: 0 !important; | |
| } | |
| /* Hide progress indicator in input section */ | |
| .input-section .progress-container, | |
| .input-section [class*="progress-bar"], | |
| .input-section [class*="progress-text"], | |
| .input-section [class*="progress-level"], | |
| .input-section .progress, | |
| .input-section .eta-bar { | |
| display: none !important; | |
| visibility: hidden !important; | |
| height: 0 !important; | |
| overflow: hidden !important; | |
| } | |
| /* Override responsive behavior */ | |
| @media (max-width: 2000px) { | |
| .gradio-row, | |
| div.gradio-row, | |
| .gradio-container .gradio-row, | |
| .gradio-container > .gradio-row { | |
| flex-direction: row !important; | |
| flex-wrap: nowrap !important; | |
| display: flex !important; | |
| } | |
| .gradio-row > .gradio-column, | |
| .gradio-row > div { | |
| display: inline-flex !important; | |
| } | |
| .gradio-row > .gradio-column:first-child, | |
| .gradio-row > div:first-child { | |
| width: 550px !important; | |
| min-width: 550px !important; | |
| max-width: 550px !important; | |
| flex: 0 0 550px !important; | |
| } | |
| .gradio-row > .gradio-column:last-child, | |
| .gradio-row > div:last-child { | |
| flex: 1 1 auto !important; | |
| min-width: 0 !important; | |
| } | |
| } | |
| /* Responsive text sizing only */ | |
| @media (max-width: 734px) { | |
| .main-title { | |
| font-size: 28px !important; | |
| } | |
| .gradio-container { | |
| padding: 32px 16px !important; | |
| } | |
| .input-section, | |
| .output-section { | |
| padding: 24px !important; | |
| } | |
| /* FORCE horizontal layout even on mobile */ | |
| .gradio-row, | |
| div.gradio-row { | |
| flex-direction: row !important; | |
| flex-wrap: nowrap !important; | |
| } | |
| } | |
| /* Hide Gradio footer */ | |
| footer { | |
| display: none !important; | |
| } | |
| .footer { | |
| display: none !important; | |
| } | |
| """ | |
| # JavaScript to force horizontal layout | |
| js_code = """ | |
| function() { | |
| function forceHorizontalLayout() { | |
| // Set container width | |
| const container = document.querySelector('.gradio-container'); | |
| if (container) { | |
| container.style.maxWidth = '85vw'; | |
| container.style.width = '85vw'; | |
| } | |
| // Target the main row specifically | |
| const mainRow = document.getElementById('main-row'); | |
| if (mainRow) { | |
| mainRow.style.flexDirection = 'row'; | |
| mainRow.style.flexWrap = 'nowrap'; | |
| mainRow.style.display = 'flex'; | |
| mainRow.style.width = '100%'; | |
| } | |
| // Force ALL rows to stay horizontal | |
| const rows = document.querySelectorAll('.gradio-row'); | |
| rows.forEach(row => { | |
| row.style.flexDirection = 'row'; | |
| row.style.flexWrap = 'nowrap'; | |
| row.style.display = 'flex'; | |
| }); | |
| // Target specific columns | |
| const inputCol = document.getElementById('input-column'); | |
| if (inputCol) { | |
| inputCol.style.width = '550px'; | |
| inputCol.style.minWidth = '550px'; | |
| inputCol.style.maxWidth = '550px'; | |
| inputCol.style.flex = '0 0 550px'; | |
| inputCol.style.display = 'inline-flex'; | |
| inputCol.style.flexDirection = 'column'; | |
| } | |
| const outputCol = document.getElementById('output-column'); | |
| if (outputCol) { | |
| outputCol.style.flex = '1 1 auto'; | |
| outputCol.style.minWidth = '0'; | |
| outputCol.style.display = 'inline-flex'; | |
| outputCol.style.flexDirection = 'column'; | |
| } | |
| // Fallback: force all column children of rows | |
| const columns = document.querySelectorAll('.gradio-row > .gradio-column, .gradio-row > div'); | |
| columns.forEach((col, index) => { | |
| if (index === 0) { | |
| col.style.width = '550px'; | |
| col.style.minWidth = '550px'; | |
| col.style.maxWidth = '550px'; | |
| col.style.flex = '0 0 550px'; | |
| } else if (index === 1) { | |
| col.style.flex = '1 1 auto'; | |
| col.style.minWidth = '0'; | |
| } | |
| col.style.display = 'inline-flex'; | |
| }); | |
| } | |
| // Run immediately | |
| forceHorizontalLayout(); | |
| // Run again after delays to override Gradio's dynamic changes | |
| setTimeout(forceHorizontalLayout, 100); | |
| setTimeout(forceHorizontalLayout, 500); | |
| setTimeout(forceHorizontalLayout, 1000); | |
| setTimeout(forceHorizontalLayout, 2000); | |
| // Set up mutation observer to reapply on DOM changes | |
| const observer = new MutationObserver(forceHorizontalLayout); | |
| observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); | |
| } | |
| """ | |
| # Create the interface | |
| with gr.Blocks( | |
| title="LongCat-Image", | |
| fill_height=False, | |
| ) as demo: | |
| # Two-column layout | |
| with gr.Row(equal_height=False, variant="panel", elem_id="main-row"): | |
| # Left column - Input controls (fixed width) | |
| with gr.Column(scale=0, min_width=550, elem_classes="input-section", elem_id="input-column"): | |
| # Title above prompt box | |
| gr.HTML(""" | |
| <div class="header-container"> | |
| <h1 class="main-title">LongCat-Image</h1> | |
| </div> | |
| """) | |
| prompt = gr.Textbox( | |
| placeholder="Describe the image you want to create...", | |
| value="Five shimmering goldfish weave through crevices between stones; four are red-and-white, while one is silver-white. By the pond's edge, a golden shaded British Shorthair cat watches them intently, counting on blind luck. Watercolor style.", | |
| lines=7, | |
| max_lines=7, | |
| label="Prompt", | |
| show_label=True, | |
| container=True, | |
| autoscroll=False, | |
| ) | |
| aspect_ratio = gr.Dropdown( | |
| choices=[ | |
| "1:1 (1024x1024)", | |
| "4:3 (1024x768)", | |
| "3:4 (768x1024)", | |
| "16:9 (1024x576)", | |
| "9:16 (576x1024)", | |
| ], | |
| value="1:1 (1024x1024)", | |
| label="Aspect Ratio", | |
| show_label=True, | |
| container=True, | |
| ) | |
| generate_btn = gr.Button( | |
| "Generate", | |
| variant="primary", | |
| size="lg", | |
| elem_classes="primary" | |
| ) | |
| # Right column - Image output | |
| with gr.Column(scale=2, elem_classes="output-section", elem_id="output-column"): | |
| output_image = gr.Image( | |
| label="Result", | |
| show_label=False, | |
| type="pil", | |
| format="png", | |
| ) | |
| # Event handlers | |
| generate_btn.click( | |
| fn=generate_image, | |
| inputs=[prompt, aspect_ratio], | |
| outputs=output_image, | |
| show_progress="full" | |
| ) | |
| prompt.submit( | |
| fn=generate_image, | |
| inputs=[prompt, aspect_ratio], | |
| outputs=output_image, | |
| show_progress="full" | |
| ) | |
| # Load event to force width with JavaScript | |
| demo.load(None, None, None, js=js_code) | |
| if __name__ == "__main__": | |
| demo.launch( | |
| mcp_server=True, | |
| share=False, | |
| show_error=True, | |
| theme=gr.themes.Soft( | |
| primary_hue=gr.themes.colors.blue, | |
| secondary_hue=gr.themes.colors.slate, | |
| neutral_hue=gr.themes.colors.gray, | |
| spacing_size=gr.themes.sizes.spacing_lg, | |
| radius_size=gr.themes.sizes.radius_lg, | |
| text_size=gr.themes.sizes.text_md, | |
| font=[gr.themes.GoogleFont("Inter"), "SF Pro Display", "-apple-system", "BlinkMacSystemFont", "system-ui", "sans-serif"], | |
| font_mono=[gr.themes.GoogleFont("JetBrains Mono"), "SF Mono", "ui-monospace", "monospace"], | |
| ).set( | |
| body_background_fill='#000000', | |
| body_background_fill_dark='#000000', | |
| button_primary_background_fill='#0071e3', | |
| button_primary_background_fill_hover='#0077ed', | |
| button_primary_text_color='#ffffff', | |
| block_background_fill='#1d1d1f', | |
| block_background_fill_dark='#1d1d1f', | |
| block_border_width='0px', | |
| block_shadow='0 2px 12px rgba(0, 0, 0, 0.4)', | |
| block_shadow_dark='0 2px 12px rgba(0, 0, 0, 0.4)', | |
| input_background_fill='#1d1d1f', | |
| input_background_fill_dark='#1d1d1f', | |
| input_border_width='1px', | |
| input_border_color='#424245', | |
| input_border_color_dark='#424245', | |
| input_shadow='none', | |
| input_shadow_focus='0 0 0 4px rgba(0, 113, 227, 0.15)', | |
| ), | |
| css=apple_css, | |
| ) | |