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 = """ """ # 제목과 사용 가이드 제거 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('
이미지 업로드 및 설정
') 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('
프롬프트 템플릿
') 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('
이미지 생성
') 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('
생성된 이미지
') 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('
결과 정보
') 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('
예제 이미지
') # 모든 예제 한 페이지에 표시 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%")