jddru8d / app.py
ssboost's picture
Update app.py
3f88133 verified
import os
import tempfile
from PIL import Image, ImageEnhance, ImageFilter
import gradio as gr
import logging
import re
import time
import cv2
import numpy as np
from io import BytesIO
from datetime import datetime, timedelta
import random
from dotenv import load_dotenv
from gradio_client import Client, handle_file
load_dotenv()
# ๋กœ๊น… ์„ค์ •
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# API ์—”๋“œํฌ์ธํŠธ ์„ค์ • (ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ๋งŒ ์„ค์ •)
API_ENDPOINT = os.environ.get("API_ENDPOINT", "")
if not API_ENDPOINT:
raise ValueError("API_ENDPOINT ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
# ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™”
try:
client = Client(API_ENDPOINT)
logger.info("ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ")
except Exception as e:
logger.error(f"ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}")
raise
def safe_client_call(api_name, **kwargs):
"""์•ˆ์ „ํ•œ ํด๋ผ์ด์–ธํŠธ ํ˜ธ์ถœ ํ•จ์ˆ˜"""
try:
return client.predict(api_name=api_name, **kwargs)
except Exception as e:
logger.error(f"API ํ˜ธ์ถœ ์‹คํŒจ ({api_name}): {e}")
return None
def convert_image_to_file(image):
"""PIL Image ๊ฐ์ฒด๋ฅผ ์ž„์‹œ ํŒŒ์ผ๋กœ ๋ณ€ํ™˜"""
if image is None:
return None
try:
# PIL Image ๊ฐ์ฒด์ธ ๊ฒฝ์šฐ
if hasattr(image, 'save'):
# ์ž„์‹œ ํŒŒ์ผ ์ƒ์„ฑ
temp_file = tempfile.NamedTemporaryFile(suffix='.png', delete=False)
image.save(temp_file.name, format='PNG')
temp_file.close()
return temp_file.name
# ์ด๋ฏธ ํŒŒ์ผ ๊ฒฝ๋กœ์ธ ๊ฒฝ์šฐ
elif isinstance(image, str):
return image
# ๊ธฐํƒ€ ๊ฒฝ์šฐ
else:
return image
except Exception as e:
logger.error(f"์ด๋ฏธ์ง€ ํŒŒ์ผ ๋ณ€ํ™˜ ์‹คํŒจ: {e}")
return None
# ========== ์ด๋ฏธ์ง€ ์ƒ์„ฑ๊ธฐ ๊ด€๋ จ ํ•จ์ˆ˜ (ํด๋ผ์ด์–ธํŠธ API ํ™œ์šฉ) ==========
def generate_single_image(image1, image2, image3, prompt):
"""๋‹จ์ผ ์ด๋ฏธ์ง€ ์ƒ์„ฑ - ์›๋ณธ ํ•จ์ˆ˜๋ช… ์œ ์ง€"""
try:
# ์ด๋ฏธ์ง€ ํŒŒ์ผ ์ฒ˜๋ฆฌ - PIL Image๋ฅผ ์ž„์‹œ ํŒŒ์ผ๋กœ ๋ณ€ํ™˜
image1_path = convert_image_to_file(image1)
image2_path = convert_image_to_file(image2)
image3_path = convert_image_to_file(image3)
# handle_file๋กœ ์ฒ˜๋ฆฌ
image1_file = handle_file(image1_path) if image1_path else None
image2_file = handle_file(image2_path) if image2_path else None
image3_file = handle_file(image3_path) if image3_path else None
result = safe_client_call(
"/generate_single_image",
image1=image1_file,
image2=image2_file,
image3=image3_file,
prompt=prompt
)
if result:
return result[0], result[1], result[2]
else:
return None, "API ํ˜ธ์ถœ ์‹คํŒจ", ""
except Exception as e:
logger.error(f"๋‹จ์ผ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹คํŒจ: {e}")
return None, f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}", ""
def generate_multiple_images(image1, image2, image3, prompt, progress=gr.Progress()):
"""๋‹ค์ค‘ ์ด๋ฏธ์ง€ ์ƒ์„ฑ - ์›๋ณธ ํ•จ์ˆ˜๋ช… ์œ ์ง€"""
try:
progress(0, desc="์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค€๋น„ ์ค‘...")
# ์ด๋ฏธ์ง€ ํŒŒ์ผ ์ฒ˜๋ฆฌ - PIL Image๋ฅผ ์ž„์‹œ ํŒŒ์ผ๋กœ ๋ณ€ํ™˜
image1_path = convert_image_to_file(image1)
image2_path = convert_image_to_file(image2)
image3_path = convert_image_to_file(image3)
# handle_file๋กœ ์ฒ˜๋ฆฌ
image1_file = handle_file(image1_path) if image1_path else None
image2_file = handle_file(image2_path) if image2_path else None
image3_file = handle_file(image3_path) if image3_path else None
progress(0.5, desc="์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘...")
result = safe_client_call(
"/generate_multiple_images",
image1=image1_file,
image2=image2_file,
image3=image3_file,
prompt=prompt
)
progress(1.0, desc="์ด๋ฏธ์ง€ ์ƒ์„ฑ ์™„๋ฃŒ!")
if result:
return result[0], result[1], result[2], result[3]
else:
return None, None, "API ํ˜ธ์ถœ ์‹คํŒจ", ""
except Exception as e:
logger.error(f"๋‹ค์ค‘ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹คํŒจ: {e}")
return None, None, f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}", ""
# ========== ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ ํ•จ์ˆ˜๋“ค - ํด๋ผ์ด์–ธํŠธ API ํ™œ์šฉ ==========
def get_prompt_template_1():
"""ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ 1 - ๋ถ€๋ถ„๋ณ€๊ฒฝ-1"""
return "(#1์˜ ์—ฌ์„ฑ)์ด ์‚ด์ง ๋’ค๋กœ ๋Œ์•„๋ณด๋Š” ๋ชจ์Šต์œผ๋กœ ์ตœ๋Œ€ํ•œ ์ด์ „ seed๋ฅผ ์œ ์ง€ํ•œํ…Œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ณ€๊ฒฝํ•˜๋ผ."
def get_prompt_template_2():
"""ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ 2 - ๋ถ€๋ถ„๋ณ€๊ฒฝ-2"""
return "(#1 ๋ ˆ๋ชจ๋ชจํ˜•)์—์„œ ์ฒญ์ƒ‰์ƒ์–ด๋ ˆ๊ณ ๋งŒ ๊ฒ€์€์ƒ‰ ๊ณ ๋ž˜๋ ˆ๊ณ ๋กœ ๋ณ€๊ฒฝํ•˜๊ณ  ๋‚˜๋จธ์ง€ ๋ถ€๋ถ„์€ seed๋ฅผ ๋ณ€๊ฒฝ์„ ํ•˜์ง€๋งˆ๋ผ."
def get_prompt_template_3():
"""ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ 3 - ๋ถ€๋ถ„๋ณ€๊ฒฝ-3"""
return "(#1 ์—ฌํ–‰์šฉ ์–ผ์Œ๋ฐ•์Šค)์•ž์— ์–ผ์Œ์ด ๋‹ด๊ธด 3์ž”์˜ ์ฝœ๋ผ๊ฐ€ ๋†“์—ฌ์žˆ๋Š” ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ."
def get_prompt_template_4():
"""ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ 4 - ๊ธ€์ž์ง€์šฐ๊ธฐ"""
return "(#1 ์ด๋ฏธ์ง€)์— ์žˆ๋Š” ์ค‘๊ตญ์–ด๋ฅผ ๋ชจ๋‘ ์ œ๊ฑฐํ•˜๋ผ."
def get_prompt_template_5():
"""ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ 5 - ๊ธ€์ž๋ณ€๊ฒฝ"""
return '(#1์˜ ํ…์ŠคํŠธ)๋ฅผ ์Šคํƒ€์ผ์„ ์œ ์ง€ํ•œ์ฒด ํ…์ŠคํŠธ๋งŒ "Hello"๋กœ ๋ฐ”๊ฟ”๋ผ'
def get_prompt_template_6():
"""ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ 6 - ์ƒํ’ˆ์ฐฉ์šฉ-1"""
return "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด ์‹ ์ฒด ๋น„์œจ๊ณผ ํฌ์ฆˆ๋Š” ์œ ์ง€ํ•œ ์ฒด (#2์˜ ์„ ๊ธ€๋ผ์Šค)์™€ (#3์˜ ์ฒญ๋ฐ”์ง€)๋ฅผ ์ง์ ‘ ๋ชจ๋ธ์ด ์ฐฉ์šฉํ•œ๊ฒƒ ์ฒ˜๋Ÿผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ."
def get_prompt_template_7():
"""ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ 7 - ์ƒํ’ˆ์ฐฉ์šฉ-2"""
return "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด (#2์˜ ์„ ๊ธ€๋ผ์Šค)์„ ์ฐฉ์šฉํ•˜๊ณ  (#3์˜ ๋’ท๋ฐฐ๊ฒฝ์˜ ์นดํŽ˜์ „์ฒด๊ฐ€ ๋ณด์ด๋ฉฐ) ์˜์ž์— ์•‰์•„ ์žˆ๋Š” ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ."
def get_prompt_template_8():
"""ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ 8 - ์ƒํ’ˆ๋“ค๊ณ """
return "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด(#2์˜ ์™€์ธ์ž”)์„ ๋“ค๊ณ  ์žˆ๋Š” ์ž์—ฐ์Šค๋Ÿฌ์šด ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ."
def get_prompt_template_9():
"""ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ 9 - ๋ฐฐ๊ฒฝ๋ฐ”๊พธ๊ธฐ"""
return "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด (#2 ์นดํŽ˜)์—์„œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์žˆ๋Š” ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ."
def get_prompt_template_10():
"""ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ 10 - ๋ถ€๋ถ„์ง€์šฐ๊ธฐ"""
return "(#1์˜ ๋ ˆ๊ณ ๋ชจํ˜•)์—์„œ ์ฒญ์ƒ‰์ƒ์–ด๋ ˆ๊ณ ๋ฅผ ์ œ๊ฑฐํ•œ ํ›„, ๊ทธ ์ž๋ฆฌ๋ฅผ ์ฃผ๋ณ€ ๋ฐฐ๊ฒฝ๊ณผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์–ด์šฐ๋Ÿฌ์ง€๋„๋ก ์ฑ„์›Œ์ฃผ์„ธ์š”. ๋‹จ, ์ด๋ฏธ์ง€์˜ ๋‹ค๋ฅธ ๋ถ€๋ถ„์˜ ์ฃผ์š” ์š”์†Œ๋Š” ๋™์ผํ•˜๊ฒŒ ์œ ์ง€ ํ•ด์•ผํ•œ๋‹ค."
def get_prompt_template_11():
"""ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ 11 - ์ด๋ฏธ์ง€ํ™•์žฅ"""
return "(#1 ์ด๋ฏธ์ง€)๋ฅผ ์›๋ณธ๊ทธ๋Œ€๋กœ ์ค‘์•™์— ๋‘๊ณ  ๋น„์œจ๋กœ ์œ ์ง€ํ•œ ์ฒด ์œ„์•„๋ž˜ ๋ฐ ์ขŒ์šฐ๋กœ ํฌ๊ฒŒ ํ™•์žฅํ•˜๋ผ."
def get_prompt_template_12():
"""ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ 12 - ํ”Œ๋ ˆ์ดํŒ…-1"""
return "(#1์…€๋Ÿฌ๋“œ)์— ๋‹ด์€ ์šฉ๊ธฐ๋Š” ๋ฒ„๋ฆฌ๊ณ  ๋„“๊ณ  ํฐ ์˜ˆ์œ ์ ‘์‹œ์— (#1์…€๋Ÿฌ๋“œ)์Œ์‹๋งŒ ๊ฐ€๋“ ์ฑ„์›Œ์„œ ์ƒ์—…์ ์ธ ๊ฐ๋„๋กœ ์–ด์šธ๋ฆฌ๋Š” ์†Œํ’ˆ๊ณผ ํ•จ๊ป˜ ํ”Œ๋ ˆ์ดํŒ… ํ•œ ๋ชจ์Šต์„ ์ด๋ฏธ์ง€๋กœ ์ƒ์„ฑํ•˜๋ผ. "
def get_prompt_template_13():
"""ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ 13 - ํ”Œ๋ ˆ์ดํŒ…-2"""
return "(#2 ํ”Œ๋ ˆ์ดํŒ…ํ•œ ์ด๋ฏธ์ง€)์— ๋‹ด๊ธด ์Œ์‹์„ (#1 ์ƒ๋Ÿฌ๋“œ)๋กœ ๋ฐ”๊พธ๊ณ  ๋‚˜๋จธ์ง€๋Š” ์‹œ๋“œ๋ฅผ ์œ ์ง€ํ•œ ์ฒด ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ."
def get_prompt_template_14():
"""ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ 14 - ํ”Œ๋ ˆ์ดํŒ…-3"""
return "(#1์ปต)์— ๋”ธ๊ธฐ, ๋ฐ”๋‹๋ผ, ์ดˆ์ฝ” ์•„์ด์Šคํฌ๋ฆผ์„ ๋‹ด๊ณ  ๊ทธ ์œ„์— ์ดˆ์ฝ” ์‹œ๋Ÿฝ์ด ํ๋ฅด๊ฒŒ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ."
# ๋‹คํฌ๋ชจ๋“œ ์ž๋™ ์ ์šฉ ์ปค์Šคํ…€ CSS ์Šคํƒ€์ผ
custom_css = """
/* ============================================
๋‹คํฌ๋ชจ๋“œ ์ž๋™ ๋ณ€๊ฒฝ ํ…œํ”Œ๋ฆฟ CSS
============================================ */
/* 1. CSS ๋ณ€์ˆ˜ ์ •์˜ (๋ผ์ดํŠธ๋ชจ๋“œ - ๊ธฐ๋ณธ๊ฐ’) */
:root {
/* ๋ฉ”์ธ ์ปฌ๋Ÿฌ */
--primary-color: #FB7F0D;
--secondary-color: #ff9a8b;
--accent-color: #FF6B6B;
/* ๋ฐฐ๊ฒฝ ์ปฌ๋Ÿฌ */
--background-color: #FFFFFF;
--card-bg: #ffffff;
--input-bg: #ffffff;
/* ํ…์ŠคํŠธ ์ปฌ๋Ÿฌ */
--text-color: #334155;
--text-secondary: #64748b;
/* ๋ณด๋” ๋ฐ ๊ตฌ๋ถ„์„  */
--border-color: #dddddd;
--border-light: #e5e5e5;
/* ํ…Œ์ด๋ธ” ์ปฌ๋Ÿฌ */
--table-even-bg: #f3f3f3;
--table-hover-bg: #f0f0f0;
/* ๊ทธ๋ฆผ์ž */
--shadow: 0 8px 30px rgba(251, 127, 13, 0.08);
--shadow-light: 0 2px 4px rgba(0, 0, 0, 0.1);
/* ๊ธฐํƒ€ */
--border-radius: 18px;
}
/* 2. ๋‹คํฌ๋ชจ๋“œ ์ƒ‰์ƒ ๋ณ€์ˆ˜ (์ž๋™ ๊ฐ์ง€) */
@media (prefers-color-scheme: dark) {
:root {
/* ๋ฐฐ๊ฒฝ ์ปฌ๋Ÿฌ */
--background-color: #1a1a1a;
--card-bg: #2d2d2d;
--input-bg: #2d2d2d;
/* ํ…์ŠคํŠธ ์ปฌ๋Ÿฌ */
--text-color: #e5e5e5;
--text-secondary: #a1a1aa;
/* ๋ณด๋” ๋ฐ ๊ตฌ๋ถ„์„  */
--border-color: #404040;
--border-light: #525252;
/* ํ…Œ์ด๋ธ” ์ปฌ๋Ÿฌ */
--table-even-bg: #333333;
--table-hover-bg: #404040;
/* ๊ทธ๋ฆผ์ž */
--shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
--shadow-light: 0 2px 4px rgba(0, 0, 0, 0.2);
}
}
/* 3. ์ˆ˜๋™ ๋‹คํฌ๋ชจ๋“œ ํด๋ž˜์Šค (Gradio ํ† ๊ธ€์šฉ) */
[data-theme="dark"],
.dark,
.gr-theme-dark {
/* ๋ฐฐ๊ฒฝ ์ปฌ๋Ÿฌ */
--background-color: #1a1a1a;
--card-bg: #2d2d2d;
--input-bg: #2d2d2d;
/* ํ…์ŠคํŠธ ์ปฌ๋Ÿฌ */
--text-color: #e5e5e5;
--text-secondary: #a1a1aa;
/* ๋ณด๋” ๋ฐ ๊ตฌ๋ถ„์„  */
--border-color: #404040;
--border-light: #525252;
/* ํ…Œ์ด๋ธ” ์ปฌ๋Ÿฌ */
--table-even-bg: #333333;
--table-hover-bg: #404040;
/* ๊ทธ๋ฆผ์ž */
--shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
--shadow-light: 0 2px 4px rgba(0, 0, 0, 0.2);
}
/* 4. ๊ธฐ๋ณธ ์š”์†Œ ๋‹คํฌ๋ชจ๋“œ ์ ์šฉ */
body {
font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
background-color: var(--background-color) !important;
color: var(--text-color) !important;
line-height: 1.6;
transition: background-color 0.3s ease, color 0.3s ease;
}
/* 5. Gradio ์ปจํ…Œ์ด๋„ˆ ๊ฐ•์ œ ์ ์šฉ */
.gradio-container,
.gradio-container *,
.gr-app,
.gr-app *,
.gr-interface {
background-color: var(--background-color) !important;
color: var(--text-color) !important;
}
/* Gradio ์ปจํ…Œ์ด๋„ˆ ์˜ค๋ฒ„๋ผ์ด๋“œ */
.gradio-container {
max-width: 100% !important;
width: 100% !important;
margin: 0 auto !important;
padding: 0 !important;
background-color: var(--background-color) !important;
box-sizing: border-box !important;
}
/* ์ถ”๊ฐ€: ๋‚ด๋ถ€ ์ปจํ…Œ์ด๋„ˆ๋„ 100% ๋„ˆ๋น„๋กœ ์„ค์ • */
.contain {
max-width: 100% !important;
width: 100% !important;
}
/* ์ถ”๊ฐ€: ๊ฐ ํ–‰(Row)๋„ 100% ๋„ˆ๋น„๋กœ ์„ค์ • */
.gr-padded {
padding: 0 !important;
width: 100% !important;
max-width: 100% !important;
}
/* 6. ์นด๋“œ ๋ฐ ํŒจ๋„ ์Šคํƒ€์ผ */
.gr-group,
.gr-form,
.gr-box,
.gr-panel,
.custom-frame,
[class*="frame"],
[class*="card"],
[class*="panel"] {
background-color: var(--card-bg) !important;
border-radius: var(--border-radius) !important;
box-shadow: var(--shadow) !important;
padding: 1.5rem !important;
margin-bottom: 1.5rem !important;
border: 1px solid var(--border-color) !important;
transition: transform 0.3s ease, background-color 0.3s ease;
color: var(--text-color) !important;
}
.gr-group:hover {
transform: translateY(-5px);
}
/* 7. ์ž…๋ ฅ ํ•„๋“œ ์Šคํƒ€์ผ */
input[type="text"],
input[type="number"],
input[type="email"],
input[type="password"],
textarea,
select,
.gr-input,
.gr-text-input,
.gr-textarea,
.gr-dropdown {
background-color: var(--input-bg) !important;
color: var(--text-color) !important;
border: 1px solid var(--border-color) !important;
border-radius: var(--border-radius) !important;
padding: 12px !important;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05) !important;
transition: all 0.3s ease !important;
}
input[type="text"]:focus,
input[type="number"]:focus,
input[type="email"]:focus,
input[type="password"]:focus,
textarea:focus,
select:focus,
.gr-input:focus,
.gr-text-input:focus,
.gr-textarea:focus,
.gr-dropdown:focus {
border-color: var(--primary-color) !important;
outline: none !important;
box-shadow: 0 0 0 2px rgba(251, 127, 13, 0.2) !important;
}
/* 8. ๋ผ๋ฒจ ๋ฐ ํ…์ŠคํŠธ ์š”์†Œ */
label,
.gr-label,
.gr-checkbox label,
.gr-radio label,
p, span, div {
color: var(--text-color) !important;
}
/* 9. ์„น์…˜ ์ œ๋ชฉ */
.section-title {
font-size: 22px !important;
font-weight: 700 !important;
color: var(--text-color) !important;
margin-bottom: 1rem !important;
padding-bottom: 0.5rem !important;
border-bottom: 2px solid var(--primary-color) !important;
display: flex;
align-items: center;
}
.section-title span {
color: var(--primary-color);
}
/* 10. ๋ฒ„ํŠผ ์Šคํƒ€์ผ๋ง */
.custom-button {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important;
color: white !important;
font-weight: 600 !important;
border: none !important;
border-radius: 30px !important;
padding: 12px 24px !important;
box-shadow: 0 4px 8px rgba(251, 127, 13, 0.25) !important;
transition: all 0.3s ease !important;
text-transform: none !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
.custom-button:hover {
transform: translateY(-2px) !important;
box-shadow: 0 6px 12px rgba(251, 127, 13, 0.3) !important;
}
.custom-button.primary {
background: linear-gradient(135deg, var(--accent-color), #ff9a8b) !important;
}
button:not([class*="custom"]):not([class*="primary"]):not([class*="secondary"]) {
background-color: var(--card-bg) !important;
color: var(--text-color) !important;
border: 1px solid var(--border-color) !important;
border-radius: var(--border-radius) !important;
transition: all 0.3s ease !important;
}
/* 11. ์ด๋ฏธ์ง€ ์ปจํ…Œ์ด๋„ˆ */
.image-container {
border-radius: var(--border-radius);
overflow: hidden;
border: 1px solid var(--border-color);
transition: all 0.3s ease;
background-color: var(--card-bg);
}
.image-container:hover {
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}
/* 12. ํ…Œ์ด๋ธ” ์Šคํƒ€์ผ */
table {
background-color: var(--card-bg) !important;
color: var(--text-color) !important;
border-color: var(--border-color) !important;
}
table th {
background-color: var(--primary-color) !important;
color: white !important;
border-color: var(--border-color) !important;
}
table td {
background-color: var(--card-bg) !important;
color: var(--text-color) !important;
border-color: var(--border-color) !important;
}
table tbody tr:nth-child(even) {
background-color: var(--table-even-bg) !important;
}
table tbody tr:hover {
background-color: var(--table-hover-bg) !important;
}
/* 13. ์ฒดํฌ๋ฐ•์Šค ๋ฐ ๋ผ๋””์˜ค ๋ฒ„ํŠผ */
input[type="checkbox"],
input[type="radio"] {
accent-color: var(--primary-color) !important;
}
/* 14. ๋ฒ„ํŠผ ๊ทธ๋ฃน */
.button-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 0.8rem;
margin-bottom: 1.2rem;
}
/* 15. ์Šคํฌ๋กค๋ฐ” ์Šคํƒ€์ผ */
::-webkit-scrollbar-thumb:hover {
background: var(--secondary-color);
}
/* 16. ์•„์ฝ”๋””์–ธ ๋ฐ ๋“œ๋กญ๋‹ค์šด */
details {
background-color: var(--card-bg) !important;
border-color: var(--border-color) !important;
color: var(--text-color) !important;
}
details summary {
background-color: var(--card-bg) !important;
color: var(--text-color) !important;
}
/* 17. ํˆดํŒ ๋ฐ ํŒ์—… */
[data-tooltip]:hover::after,
.tooltip,
.popup {
background-color: var(--card-bg) !important;
color: var(--text-color) !important;
border-color: var(--border-color) !important;
box-shadow: var(--shadow-light) !important;
}
/* 18. ๋ชจ๋‹ฌ ๋ฐ ์˜ค๋ฒ„๋ ˆ์ด */
.modal,
.overlay,
[class*="modal"],
[class*="overlay"] {
background-color: var(--card-bg) !important;
color: var(--text-color) !important;
border-color: var(--border-color) !important;
}
/* 19. ์ถ”๊ฐ€ Gradio ์ปดํฌ๋„ŒํŠธ๋“ค */
.gr-block,
.gr-group,
.gr-row,
.gr-column {
background-color: var(--background-color) !important;
color: var(--text-color) !important;
}
/* 20. ์ฝ”๋“œ ๋ธ”๋ก ๋ฐ pre ํƒœ๊ทธ */
code,
pre,
.code-block {
background-color: var(--table-even-bg) !important;
color: var(--text-color) !important;
border-color: var(--border-color) !important;
}
/* 21. ์•Œ๋ฆผ ๋ฐ ๋ฉ”์‹œ์ง€ */
.alert,
.message,
.notification,
[class*="alert"],
[class*="message"],
[class*="notification"] {
background-color: var(--card-bg) !important;
color: var(--text-color) !important;
border-color: var(--border-color) !important;
}
/* 22. ์• ๋‹ˆ๋ฉ”์ด์…˜ ์Šคํƒ€์ผ */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.fade-in {
animation: fadeIn 0.5s ease-out;
}
/* 23. Examples ์„น์…˜ ์Šคํƒ€์ผ */
.examples-section {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
margin-top: 1rem;
}
.example-item {
background-color: var(--card-bg);
border-radius: var(--border-radius);
overflow: hidden;
box-shadow: var(--shadow);
transition: transform 0.3s ease, background-color 0.3s ease;
}
.example-item:hover {
transform: translateY(-5px);
}
/* 24. ์ „ํ™˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ */
* {
transition: background-color 0.3s ease,
color 0.3s ease,
border-color 0.3s ease !important;
}
/* 25. ๋ฐ˜์‘ํ˜• */
@media (max-width: 768px) {
.button-grid {
grid-template-columns: repeat(2, 1fr);
}
.examples-section {
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
}
"""
# FontAwesome ์•„์ด์ฝ˜ ํฌํ•จ
fontawesome_link = """
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
"""
# ์ œ๋ชฉ๊ณผ ์‚ฌ์šฉ ๊ฐ€์ด๋“œ ์ œ๊ฑฐ
header_html = ""
image_generator_guide_html = ""
# UI ๊ตฌ์„ฑ
with gr.Blocks(css=custom_css, theme=gr.themes.Default(
primary_hue="orange",
secondary_hue="orange",
font=[gr.themes.GoogleFont("Noto Sans KR"), "ui-sans-serif", "system-ui"]
)) as demo:
gr.HTML(fontawesome_link)
with gr.Row(equal_height=True):
with gr.Column(scale=1):
# ======== ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๋ฐ ์„ค์ • ์„น์…˜ ========
with gr.Group():
gr.HTML('<div class="section-title"><i class="fas fa-upload"></i> <span>์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๋ฐ ์„ค์ •</span></div>')
with gr.Row():
image1_input = gr.Image(type="pil", label="#1", image_mode="RGB", elem_classes="image-container", height=400)
image2_input = gr.Image(type="pil", label="#2", image_mode="RGB", elem_classes="image-container", height=400)
image3_input = gr.Image(type="pil", label="#3", image_mode="RGB", elem_classes="image-container", height=400)
# ํ”„๋กฌํ”„ํŠธ ์ž…๋ ฅ ํ•„๋“œ ์ถ”๊ฐ€
prompt_input = gr.Textbox(
lines=3,
placeholder="ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž…๋ ฅํ•˜๊ฑฐ๋‚˜ ๋น„์›Œ๋‘๋ฉด ์ž๋™ ํ•ฉ์„ฑ๋ฉ๋‹ˆ๋‹ค. '#1', '#2', '#3'์œผ๋กœ ๊ฐ ์ด๋ฏธ์ง€๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
label="ํ”„๋กฌํ”„ํŠธ (์„ ํƒ ์‚ฌํ•ญ)",
elem_classes="gr-text-input"
)
# ======== ๋ณ€ํ™˜ ์˜ต์…˜ ์„น์…˜ ========
with gr.Group():
gr.HTML('<div class="section-title"><i class="fas fa-sliders-h"></i> <span>ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ</span></div>')
with gr.Column(elem_classes="button-grid"):
image_change_btn1 = gr.Button('๐Ÿ”„ ๋ถ€๋ถ„๋ณ€๊ฒฝ-1', elem_classes="custom-button")
image_change_btn2 = gr.Button('๐Ÿ”„ ๋ถ€๋ถ„๋ณ€๊ฒฝ-2', elem_classes="custom-button")
image_change_btn3= gr.Button('๐Ÿ”„ ๋ถ€๋ถ„๋ณ€๊ฒฝ-3', elem_classes="custom-button")
text_remove_btn = gr.Button('๐Ÿงน ๊ธ€์ž์ง€์šฐ๊ธฐ', elem_classes="custom-button")
text_change_btn = gr.Button('๐Ÿ”ค ๊ธ€์ž๋ณ€๊ฒฝ', elem_classes="custom-button")
clothes_change_btn1 = gr.Button('๐Ÿ‘• ์ƒํ’ˆ์ฐฉ์šฉ-1', elem_classes="custom-button")
clothes_change_btn2 = gr.Button('๐Ÿ‘“ ์ƒํ’ˆ์ฐฉ์šฉ-2', elem_classes="custom-button")
holding_product_btn = gr.Button('๐Ÿท ์ƒํ’ˆ๋“ค๊ณ ', elem_classes="custom-button")
background_change_btn = gr.Button('๐Ÿ–ผ๏ธ ๋ฐฐ๊ฒฝ๋ฐ”๊พธ๊ธฐ', elem_classes="custom-button")
composite_product_btn = gr.Button('โœ‚๏ธ ๋ถ€๋ถ„์ง€์šฐ๊ธฐ', elem_classes="custom-button")
outpainting_btn = gr.Button('๐Ÿ” ์ด๋ฏธ์ง€ํ™•์žฅ', elem_classes="custom-button")
food_btn_1 = gr.Button('๐Ÿฝ๏ธ ํ”Œ๋ ˆ์ดํŒ…-1', elem_classes="custom-button")
food_btn_2 = gr.Button('๐Ÿฝ๏ธ ํ”Œ๋ ˆ์ดํŒ…-2', elem_classes="custom-button")
food_btn_3 = gr.Button('๐Ÿฝ๏ธ ํ”Œ๋ ˆ์ดํŒ…-3', elem_classes="custom-button")
# ======== ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์„น์…˜ ========
with gr.Group():
gr.HTML('<div class="section-title"><i class="fas fa-image"></i> <span>์ด๋ฏธ์ง€ ์ƒ์„ฑ</span></div>')
submit_single_btn = gr.Button('โœจ ์ด๋ฏธ์ง€ ์ƒ์„ฑ (1์žฅ)', elem_classes="custom-button primary")
submit_btn = gr.Button('โœจ ์ด๋ฏธ์ง€ ์ƒ์„ฑ (2์žฅ)', elem_classes="custom-button primary")
with gr.Column(scale=1):
# ======== ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€ ์„น์…˜ ========
with gr.Group():
gr.HTML('<div class="section-title"><i class="fas fa-images"></i> <span>์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€</span></div>')
with gr.Row():
with gr.Column():
output_image1 = gr.Image(label="์ด๋ฏธ์ง€ #1", elem_classes="image-container", height=400)
with gr.Column():
output_image2 = gr.Image(label="์ด๋ฏธ์ง€ #2", elem_classes="image-container", height=400)
# ======== ๊ฒฐ๊ณผ ์ •๋ณด ์„น์…˜ ========
with gr.Group():
gr.HTML('<div class="section-title"><i class="fas fa-info-circle"></i> <span>๊ฒฐ๊ณผ ์ •๋ณด</span></div>')
output_text = gr.Textbox(label="์ƒํƒœ ๋ฉ”์‹œ์ง€", lines=2, elem_classes="gr-text-input")
prompt_display = gr.Textbox(label="์‚ฌ์šฉ๋œ ํ”„๋กฌํ”„ํŠธ (์˜์–ด)", visible=True, lines=2, elem_classes="gr-text-input")
# ======== ์˜ˆ์ œ ์ด๋ฏธ์ง€ ์„น์…˜ ========
gr.HTML('<div class="section-title"><i class="fas fa-lightbulb"></i> <span>์˜ˆ์ œ ์ด๋ฏธ์ง€</span></div>')
# ๋ชจ๋“  ์˜ˆ์ œ ํ•œ ํŽ˜์ด์ง€์— ํ‘œ์‹œ
examples = [
["down/๋ชจ๋ธ.jpg", None, None, "(#1์˜ ์—ฌ์„ฑ)์ด ์‚ด์ง ๋’ค๋กœ ๋Œ์•„๋ณด๋Š” ๋ชจ์Šต์œผ๋กœ ์ตœ๋Œ€ํ•œ ์ด์ „ seed๋ฅผ ์œ ์ง€ํ•œ์ฒด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ณ€๊ฒฝํ•˜๋ผ."],
["down/์ƒ์–ด๋ ˆ๊ณ ๋ชจํ˜•.png", None, None, "(#1 ๋ ˆ๋ชจ๋ชจํ˜•)์—์„œ ์ฒญ์ƒ‰์ƒ์–ด๋ ˆ๊ณ ๋งŒ ๊ฒ€์€์ƒ‰ ๊ณ ๋ž˜๋ ˆ๊ณ ๋กœ ๋ณ€๊ฒฝํ•˜๊ณ  ๋‚˜๋จธ์ง€ ๋ถ€๋ถ„์€ seed๋ฅผ ๋ณ€๊ฒฝ์„ ํ•˜์ง€๋งˆ๋ผ."],
["down/์–ผ์Œ๊ฐ€๋ฐฉ.png", None, None, "(#1 ์—ฌํ–‰์šฉ ์–ผ์Œ๋ฐ•์Šค)์•ž์— ์–ผ์Œ์ด ๋‹ด๊ธด 3์ž”์˜ ์ฝœ๋ผ๊ฐ€ ๋†“์—ฌ์žˆ๋Š” ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ."],
["down/์ค‘๊ตญ์–ด.png", None, None, "(#1 ์ด๋ฏธ์ง€)์— ์žˆ๋Š” ์ค‘๊ตญ์–ด๋ฅผ ๋ชจ๋‘ ์ œ๊ฑฐํ•˜๋ผ."],
["down/ํ…์ŠคํŠธ.webp", None, None, '(#1์˜ ํ…์ŠคํŠธ)๋ฅผ ์Šคํƒ€์ผ์„ ์œ ์ง€ํ•œ์ฒด ํ…์ŠคํŠธ๋งŒ "Hello"๋กœ ๋ฐ”๊ฟ”๋ผ'],
["down/๋ชจ๋ธ2.png", "down/์„ ๊ธ€๋ผ์Šค.png", "down/์ฒญ๋ฐ”์ง€.png", "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด ์‹ ์ฒด ๋น„์œจ๊ณผ ํฌ์ฆˆ๋Š” ์œ ์ง€ํ•œ ์ฒด (#2์˜ ์„ ๊ธ€๋ผ์Šค)์™€ (#3์˜ ์ฒญ๋ฐ”์ง€)๋ฅผ ์ง์ ‘ ๋ชจ๋ธ์ด ์ฐฉ์šฉํ•œ๊ฒƒ ์ฒ˜๋Ÿผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ."],
["down/๋ชจ๋ธ2.png", "down/์„ ๊ธ€๋ผ์Šค.png", "down/์นดํŽ˜์ „๊ฒฝ.png", "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด (#2์˜ ์„ ๊ธ€๋ผ์Šค)์„ ์ฐฉ์šฉํ•˜๊ณ  (#3์˜ ๋’ท๋ฐฐ๊ฒฝ์˜ ์นดํŽ˜์ „์ฒด๊ฐ€ ๋ณด์ด๋ฉฐ) ์˜์ž์— ์•‰์•„ ์žˆ๋Š” ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ."],
["down/๋ชจ๋ธ2.png", "down/์™€์ธ์ž”.png", None, "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด(#2์˜ ์™€์ธ์ž”)์„ ๋“ค๊ณ  ์žˆ๋Š” ์ž์—ฐ์Šค๋Ÿฌ์šด ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ."],
["down/๋ชจ๋ธ2.png", "down/์นดํŽ˜์ „๊ฒฝ.png", None, "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด (#2 ์นดํŽ˜)์—์„œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์žˆ๋Š” ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ."],
["down/์ƒ์–ด๋ ˆ๊ณ ๋ชจํ˜•.png", None, None, "(#1์˜ ๋ ˆ๊ณ ๋ชจํ˜•)์—์„œ ์ฒญ์ƒ‰์ƒ์–ด๋ ˆ๊ณ ๋ฅผ ์ œ๊ฑฐํ•œ ํ›„, ๊ทธ ์ž๋ฆฌ๋ฅผ ์ฃผ๋ณ€ ๋ฐฐ๊ฒฝ๊ณผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์–ด์šฐ๋Ÿฌ์ง€๋„๋ก ์ฑ„์›Œ์ฃผ์„ธ์š”. ๋‹จ, ์ด๋ฏธ์ง€์˜ ๋‹ค๋ฅธ ๋ถ€๋ถ„์˜ ์ฃผ์š” ์š”์†Œ๋Š” ๋™์ผํ•˜๊ฒŒ ์œ ์ง€ํ•ด์•ผํ•œ๋‹ค."],
["down/์นดํŽ˜์ „๊ฒฝ.png", None, None, "(#1 ์ด๋ฏธ์ง€)๋ฅผ ์›๋ณธ๊ทธ๋Œ€๋กœ ์ค‘์•™์— ๋‘๊ณ  ๋น„์œจ๋กœ ์œ ์ง€ํ•œ ์ฒด ์œ„์•„๋ž˜ ๋ฐ ์ขŒ์šฐ๋กœ ํฌ๊ฒŒ ํ™•์žฅํ•˜๋ผ."],
["down/์ƒ๋Ÿฌ๋“œ.png", None, None, "(#1์…€๋Ÿฌ๋“œ)์— ๋‹ด์€ ์šฉ๊ธฐ๋Š” ๋ฒ„๋ฆฌ๊ณ  ๋„“๊ณ  ํฐ ์˜ˆ์œ ์ ‘์‹œ์— (#1์…€๋Ÿฌ๋“œ)์Œ์‹๋งŒ ๊ฐ€๋“ ์ฑ„์›Œ์„œ ์ƒ์—…์ ์ธ ๊ฐ๋„๋กœ ์–ด์šธ๋ฆฌ๋Š” ์†Œํ’ˆ๊ณผ ํ•จ๊ป˜ ํ”Œ๋ ˆ์ดํŒ… ํ•œ ๋ชจ์Šต์„ ์ด๋ฏธ์ง€๋กœ ์ƒ์„ฑํ•˜๋ผ. "],
["down/์ƒ๋Ÿฌ๋“œ.png", "down/ํ”Œ๋ ˆ์ดํŒ….png", None, "(#2 ํ”Œ๋ ˆ์ดํŒ…ํ•œ ์ด๋ฏธ์ง€)์— ๋‹ด๊ธด ์Œ์‹์„ (#1 ์ƒ๋Ÿฌ๋“œ)๋กœ ๋ฐ”๊พธ๊ณ  ๋‚˜๋จธ์ง€๋Š” ์‹œ๋“œ๋ฅผ ์œ ์ง€ํ•œ ์ฒด ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ."],
["down/์ปต.png", None, None, "(#1์ปต)์— ๋”ธ๊ธฐ, ๋ฐ”๋‹๋ผ, ์ดˆ์ฝ” ์•„์ด์Šคํฌ๋ฆผ์„ ๋‹ด๊ณ  ๊ทธ ์œ„์— ์ดˆ์ฝ” ์‹œ๋Ÿฝ์ด ํ๋ฅด๊ฒŒ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ."]
]
# ๋ชจ๋“  ์˜ˆ์ œ๋ฅผ ํ•œ ํŽ˜์ด์ง€์— ํ‘œ์‹œํ•˜๋„๋ก ์ˆ˜์ •๋œ ๋ถ€๋ถ„
gr.Examples(
examples=examples,
inputs=[image1_input, image2_input, image3_input, prompt_input],
examples_per_page=len(examples) # ๋ชจ๋“  ์˜ˆ์ œ๋ฅผ ํ•œ ํŽ˜์ด์ง€์— ํ‘œ์‹œ
)
# ========== ์ด๋ฏธ์ง€ ์ƒ์„ฑ๊ธฐ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ ==========
# ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ - ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ
image_change_btn1.click(fn=get_prompt_template_1, outputs=prompt_input)
image_change_btn2.click(fn=get_prompt_template_2, outputs=prompt_input)
image_change_btn3.click(fn=get_prompt_template_3, outputs=prompt_input)
text_remove_btn.click(fn=get_prompt_template_4, outputs=prompt_input)
text_change_btn.click(fn=get_prompt_template_5, outputs=prompt_input)
clothes_change_btn1.click(fn=get_prompt_template_6, outputs=prompt_input)
clothes_change_btn2.click(fn=get_prompt_template_7, outputs=prompt_input)
holding_product_btn.click(fn=get_prompt_template_8, outputs=prompt_input)
background_change_btn.click(fn=get_prompt_template_9, outputs=prompt_input)
composite_product_btn.click(fn=get_prompt_template_10, outputs=prompt_input)
outpainting_btn.click(fn=get_prompt_template_11, outputs=prompt_input)
food_btn_1.click(fn=get_prompt_template_12, outputs=prompt_input)
food_btn_2.click(fn=get_prompt_template_13, outputs=prompt_input)
food_btn_3.click(fn=get_prompt_template_14, outputs=prompt_input)
# ๋‹จ์ผ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
submit_single_btn.click(
fn=generate_single_image,
inputs=[image1_input, image2_input, image3_input, prompt_input],
outputs=[output_image1, output_text, prompt_display],
)
# 2์žฅ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
submit_btn.click(
fn=generate_multiple_images,
inputs=[image1_input, image2_input, image3_input, prompt_input],
outputs=[output_image1, output_image2, output_text, prompt_display],
)
# ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰
demo.queue()
demo.launch(share=False, inbrowser=True, width="100%")