LongCat-Image / app.py
tchung1970's picture
Force white background on output section with !important
7492786
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!")
@spaces.GPU
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,
)