ssboost commited on
Commit
633e68e
ยท
verified ยท
1 Parent(s): 73d4b03

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +883 -1
app.py CHANGED
@@ -1,2 +1,884 @@
1
  import os
2
- exec(os.environ.get('APP'))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
+ import tempfile
3
+ from PIL import Image, ImageEnhance, ImageFilter
4
+ import gradio as gr
5
+ import logging
6
+ import re
7
+ import time
8
+ import cv2
9
+ import numpy as np
10
+ from io import BytesIO
11
+ from datetime import datetime, timedelta
12
+ import random
13
+ import requests
14
+ import base64
15
+ from dotenv import load_dotenv
16
+
17
+ load_dotenv()
18
+
19
+ # ๋กœ๊น… ์„ค์ •
20
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
21
+ logger = logging.getLogger(__name__)
22
+
23
+ # ------------------- API ์—”๋“œํฌ์ธํŠธ ์„ค์ • -------------------
24
+ def get_api_endpoint():
25
+ """ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ API ์—”๋“œํฌ์ธํŠธ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ํ•จ์ˆ˜"""
26
+ endpoint = os.getenv('API_ENDPOINT')
27
+ if not endpoint:
28
+ logger.error("API_ENDPOINT ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
29
+ return None
30
+ return endpoint
31
+
32
+ def encode_image_to_base64(image):
33
+ """PIL ์ด๋ฏธ์ง€๋ฅผ base64๋กœ ์ธ์ฝ”๋”ฉ"""
34
+ if image is None:
35
+ return None
36
+
37
+ buffered = BytesIO()
38
+ image.save(buffered, format="PNG")
39
+ img_str = base64.b64encode(buffered.getvalue()).decode()
40
+ return img_str
41
+
42
+ def decode_base64_to_image(base64_str):
43
+ """base64 ๋ฌธ์ž์—ด์„ PIL ์ด๋ฏธ์ง€๋กœ ๋””์ฝ”๋”ฉ"""
44
+ if not base64_str:
45
+ return None
46
+
47
+ image_data = base64.b64decode(base64_str)
48
+ image = Image.open(BytesIO(image_data))
49
+ return image
50
+
51
+ def call_api_endpoint(endpoint_url, data):
52
+ """API ์—”๋“œํฌ์ธํŠธ ํ˜ธ์ถœ ํ•จ์ˆ˜"""
53
+ try:
54
+ response = requests.post(endpoint_url, json=data, timeout=300)
55
+ response.raise_for_status()
56
+ return response.json()
57
+ except requests.exceptions.RequestException as e:
58
+ logger.error(f"API ํ˜ธ์ถœ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}")
59
+ return {"error": f"API ํ˜ธ์ถœ ์‹คํŒจ: {str(e)}"}
60
+
61
+ # ========== ์ด๋ฏธ์ง€ ์ƒ์„ฑ๊ธฐ ๊ด€๋ จ ํ•จ์ˆ˜ ==========
62
+ def translate_prompt_to_english(prompt):
63
+ """ํ”„๋กฌํ”„ํŠธ ๋ฒˆ์—ญ - API ์—”๋“œํฌ์ธํŠธ๋กœ ์ „๋‹ฌ"""
64
+ endpoint_url = get_api_endpoint()
65
+ if not endpoint_url:
66
+ return prompt
67
+
68
+ data = {
69
+ "function": "translate_prompt",
70
+ "prompt": prompt
71
+ }
72
+
73
+ try:
74
+ result = call_api_endpoint(f"{endpoint_url}/translate", data)
75
+ if "error" in result:
76
+ logger.warning(f"๋ฒˆ์—ญ API ์˜ค๋ฅ˜: {result['error']}")
77
+ return prompt
78
+ return result.get("translated_prompt", prompt)
79
+ except Exception as e:
80
+ logger.warning(f"๋ฒˆ์—ญ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}")
81
+ return prompt
82
+
83
+ def preprocess_prompt(prompt, image1, image2, image3):
84
+ """ํ”„๋กฌํ”„ํŠธ ์ „์ฒ˜๋ฆฌ - ๊ธฐ์กด ๋กœ์ง ์œ ์ง€"""
85
+ has_img1 = image1 is not None
86
+ has_img2 = image2 is not None
87
+ has_img3 = image3 is not None
88
+
89
+ if "#1" in prompt and not has_img1:
90
+ prompt = prompt.replace("#1", "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€(์—†์Œ)")
91
+ else:
92
+ prompt = prompt.replace("#1", "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€")
93
+
94
+ if "#2" in prompt and not has_img2:
95
+ prompt = prompt.replace("#2", "๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€(์—†์Œ)")
96
+ else:
97
+ prompt = prompt.replace("#2", "๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€")
98
+
99
+ if "#3" in prompt and not has_img3:
100
+ prompt = prompt.replace("#3", "์„ธ ๋ฒˆ์งธ ์ด๋ฏธ์ง€(์—†์Œ)")
101
+ else:
102
+ prompt = prompt.replace("#3", "์„ธ ๋ฒˆ์งธ ์ด๋ฏธ์ง€")
103
+
104
+ if "1. ์ด๋ฏธ์ง€ ๋ณ€๊ฒฝ" in prompt:
105
+ desc_match = re.search(r'#1์„ "(.*?)"์œผ๋กœ ๋ฐ”๊ฟ”๋ผ', prompt)
106
+ if desc_match:
107
+ description = desc_match.group(1)
108
+ prompt = f"์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€๋ฅผ {description}์œผ๋กœ ๋ณ€๊ฒฝํ•ด์ฃผ์„ธ์š”. ์›๋ณธ ์ด๋ฏธ์ง€์˜ ์ฃผ์š” ๋‚ด์šฉ์€ ์œ ์ง€ํ•˜๋˜ ์ƒˆ๋กœ์šด ์Šคํƒ€์ผ๊ณผ ๋ถ„์œ„๊ธฐ๋กœ ์žฌํ•ด์„ํ•ด์ฃผ์„ธ์š”."
109
+ else:
110
+ prompt = "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€๋ฅผ ์ฐฝ์˜์ ์œผ๋กœ ๋ณ€ํ˜•ํ•ด์ฃผ์„ธ์š”. ๋” ์ƒ์ƒํ•˜๊ณ  ์˜ˆ์ˆ ์ ์ธ ๋ฒ„์ „์œผ๋กœ ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”."
111
+
112
+ elif "2. ๊ธ€์ž์ง€์šฐ๊ธฐ" in prompt:
113
+ text_match = re.search(r'#1์—์„œ "(.*?)"๋ฅผ ์ง€์›Œ๋ผ', prompt)
114
+ if text_match:
115
+ text_to_remove = text_match.group(1)
116
+ prompt = f"์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์—์„œ '{text_to_remove}' ํ…์ŠคํŠธ๋ฅผ ์ฐพ์•„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ œ๊ฑฐํ•ด์ฃผ์„ธ์š”. ํ…์ŠคํŠธ๊ฐ€ ์žˆ๋˜ ๋ถ€๋ถ„์„ ๋ฐฐ๊ฒฝ๊ณผ ์กฐํ™”๋กญ๊ฒŒ ์ฑ„์›Œ์ฃผ์„ธ์š”."
117
+ else:
118
+ prompt = "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์—์„œ ๋ชจ๋“  ํ…์ŠคํŠธ๋ฅผ ์ฐพ์•„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ œ๊ฑฐํ•ด์ฃผ์„ธ์š”. ๊น”๋”ํ•œ ์ด๋ฏธ์ง€๋กœ ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”."
119
+
120
+ elif "4. ์˜ท๋ฐ”๊พธ๊ธฐ" in prompt:
121
+ prompt = "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์˜ ์ธ๋ฌผ ์˜์ƒ์„ ๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์˜ ์˜์ƒ์œผ๋กœ ๋ณ€๊ฒฝํ•ด์ฃผ์„ธ์š”. ์˜์ƒ์˜ ์Šคํƒ€์ผ๊ณผ ์ƒ‰์ƒ์€ ๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€๋ฅผ ๋”ฐ๋ฅด๋˜, ์‹ ์ฒด ๋น„์œจ๊ณผ ํฌ์ฆˆ๋Š” ์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€๋ฅผ ์œ ์ง€ํ•ด์ฃผ์„ธ์š”."
122
+
123
+ elif "5. ๋ฐฐ๊ฒฝ๋ฐ”๊พธ๊ธฐ" in prompt:
124
+ prompt = "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์˜ ๋ฐฐ๊ฒฝ์„ ๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์˜ ๋ฐฐ๊ฒฝ์œผ๋กœ ๋ณ€๊ฒฝํ•ด์ฃผ์„ธ์š”. ์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์˜ ์ฃผ์š” ํ”ผ์‚ฌ์ฒด๋Š” ์œ ์ง€ํ•˜๊ณ , ๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์˜ ๋ฐฐ๊ฒฝ๊ณผ ์กฐํ™”๋กญ๊ฒŒ ํ•ฉ์„ฑํ•ด์ฃผ์„ธ์š”."
125
+
126
+ elif "6. ์ด๋ฏธ์ง€ ํ•ฉ์„ฑ(์ƒํ’ˆํฌํ•จ)" in prompt:
127
+ prompt = "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์™€ ๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€(๋˜๋Š” ์„ธ ๋ฒˆ์งธ ์ด๋ฏธ์ง€)๋ฅผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํ•ฉ์„ฑํ•ด์ฃผ์„ธ์š”. ๋ชจ๋“  ์ด๋ฏธ์ง€์˜ ์ฃผ์š” ์š”์†Œ๋ฅผ ํฌํ•จํ•˜๊ณ , ํŠนํžˆ ์ƒํ’ˆ์ด ๋‹๋ณด์ด๋„๋ก ์กฐํ™”๋กญ๊ฒŒ ํ†ตํ•ฉํ•ด์ฃผ์„ธ์š”."
128
+
129
+ prompt += " ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”. ์ด๋ฏธ์ง€์— ํ…์ŠคํŠธ๋‚˜ ๊ธ€์ž๋ฅผ ํฌํ•จํ•˜์ง€ ๋งˆ์„ธ์š”."
130
+ return prompt
131
+
132
+ def generate_with_images(prompt, images, variation_index=0):
133
+ """API ์—”๋“œํฌ์ธํŠธ๋กœ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์š”์ฒญ"""
134
+ endpoint_url = get_api_endpoint()
135
+ if not endpoint_url:
136
+ return None, "API ์—”๋“œํฌ์ธํŠธ๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."
137
+
138
+ # ์ด๋ฏธ์ง€๋“ค์„ base64๋กœ ์ธ์ฝ”๋”ฉ
139
+ encoded_images = []
140
+ for img in images:
141
+ if img is not None:
142
+ encoded_img = encode_image_to_base64(img)
143
+ encoded_images.append(encoded_img)
144
+ else:
145
+ encoded_images.append(None)
146
+
147
+ data = {
148
+ "function": "generate_with_images",
149
+ "prompt": prompt,
150
+ "images": encoded_images,
151
+ "variation_index": variation_index
152
+ }
153
+
154
+ try:
155
+ result = call_api_endpoint(f"{endpoint_url}/generate", data)
156
+ if "error" in result:
157
+ return None, result["error"]
158
+
159
+ # base64 ์ด๋ฏธ์ง€๋ฅผ PIL ์ด๋ฏธ์ง€๋กœ ๋ณ€ํ™˜
160
+ if "image" in result:
161
+ generated_image = decode_base64_to_image(result["image"])
162
+ return generated_image, result.get("status", "์ด๋ฏธ์ง€๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
163
+ else:
164
+ return None, "API์—์„œ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค."
165
+ except Exception as e:
166
+ logger.error(f"์ด๋ฏธ์ง€ ์ƒ์„ฑ API ํ˜ธ์ถœ ์ค‘ ์˜ค๋ฅ˜: {str(e)}")
167
+ return None, f"์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
168
+
169
+ def process_images_with_prompt(image1, image2, image3, prompt, variation_index=0, max_retries=3):
170
+ """์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ๋ฉ”์ธ ํ•จ์ˆ˜"""
171
+ retry_count = 0
172
+ last_error = None
173
+
174
+ while retry_count < max_retries:
175
+ try:
176
+ images = [image1, image2, image3]
177
+ valid_images = [img for img in images if img is not None]
178
+
179
+ if not valid_images:
180
+ if prompt and prompt.strip():
181
+ logger.info("ํ…์ŠคํŠธ ํ”„๋กฌํ”„ํŠธ๋งŒ์œผ๋กœ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹œ๋„")
182
+ try:
183
+ result_img, status = generate_with_images(prompt, [], variation_index)
184
+ if result_img is not None:
185
+ return result_img, status, prompt
186
+ except:
187
+ pass
188
+ return None, "์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๊ฑฐ๋‚˜ ํ…์ŠคํŠธ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.", ""
189
+
190
+ if prompt and prompt.strip():
191
+ processed_prompt = preprocess_prompt(prompt, image1, image2, image3)
192
+ if re.search("[๊ฐ€-ํžฃ]", processed_prompt):
193
+ final_prompt = translate_prompt_to_english(processed_prompt)
194
+ else:
195
+ final_prompt = processed_prompt
196
+ else:
197
+ if len(valid_images) == 1:
198
+ final_prompt = "Please creatively transform this image into a more vivid and artistic version. Do not include any text or watermarks in the generated image."
199
+ logger.info("Default prompt generated for single image")
200
+ elif len(valid_images) == 2:
201
+ final_prompt = "Please seamlessly composite these two images, integrating their key elements harmoniously into a single image. Do not include any text or watermarks in the generated image."
202
+ logger.info("Default prompt generated for two images")
203
+ else:
204
+ final_prompt = "Please creatively composite these three images, combining their main elements into a cohesive and natural scene. Do not include any text or watermarks in the generated image."
205
+ logger.info("Default prompt generated for three images")
206
+
207
+ result_img, status = generate_with_images(final_prompt, valid_images, variation_index)
208
+ if result_img is not None:
209
+ return result_img, status, final_prompt
210
+ else:
211
+ last_error = status
212
+ retry_count += 1
213
+ logger.warning(f"์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹คํŒจ, ์žฌ์‹œ๋„ {retry_count}/{max_retries}: {status}")
214
+ time.sleep(1)
215
+ except Exception as e:
216
+ last_error = str(e)
217
+ retry_count += 1
218
+ logger.exception(f"์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ, ์žฌ์‹œ๋„ {retry_count}/{max_retries}:")
219
+ time.sleep(1)
220
+
221
+ return None, f"์ตœ๋Œ€ ์žฌ์‹œ๋„ ํšŸ์ˆ˜({max_retries}ํšŒ) ์ดˆ๊ณผ ํ›„ ์‹คํŒจ: {last_error}", prompt
222
+
223
+ def generate_multiple_images(image1, image2, image3, prompt, progress=gr.Progress()):
224
+ """๋‹ค์ค‘ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ•จ์ˆ˜"""
225
+ results = []
226
+ statuses = []
227
+ prompts = []
228
+
229
+ num_images = 2
230
+ max_retries = 3
231
+
232
+ progress(0, desc="์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค€๋น„ ์ค‘...")
233
+
234
+ for i in range(num_images):
235
+ progress((i / num_images), desc=f"{i+1}/{num_images} ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘...")
236
+ result_img, status, final_prompt = process_images_with_prompt(image1, image2, image3, prompt, i, max_retries)
237
+
238
+ if result_img is not None:
239
+ results.append(result_img)
240
+ statuses.append(f"์ด๋ฏธ์ง€ #{i+1}: {status}")
241
+ prompts.append(f"์ด๋ฏธ์ง€ #{i+1}: {final_prompt}")
242
+ else:
243
+ results.append(None)
244
+ statuses.append(f"์ด๋ฏธ์ง€ #{i+1} ์ƒ์„ฑ ์‹คํŒจ: {status}")
245
+ prompts.append(f"์ด๋ฏธ์ง€ #{i+1}: {final_prompt}")
246
+
247
+ time.sleep(1)
248
+
249
+ progress(1.0, desc="์ด๋ฏธ์ง€ ์ƒ์„ฑ ์™„๋ฃŒ!")
250
+
251
+ while len(results) < 2:
252
+ results.append(None)
253
+
254
+ combined_status = "\n".join(statuses)
255
+ combined_prompts = "\n".join(prompts)
256
+
257
+ return results[0], results[1], combined_status, combined_prompts
258
+
259
+ # ๋‹คํฌ๋ชจ๋“œ ์ž๋™ ์ ์šฉ ์ปค์Šคํ…€ CSS ์Šคํƒ€์ผ
260
+ custom_css = """
261
+ /* ============================================
262
+ ๋‹คํฌ๋ชจ๋“œ ์ž๋™ ๋ณ€๊ฒฝ ํ…œํ”Œ๋ฆฟ CSS
263
+ ============================================ */
264
+
265
+ /* 1. CSS ๋ณ€์ˆ˜ ์ •์˜ (๋ผ์ดํŠธ๋ชจ๋“œ - ๊ธฐ๋ณธ๊ฐ’) */
266
+ :root {
267
+ /* ๋ฉ”์ธ ์ปฌ๋Ÿฌ */
268
+ --primary-color: #FB7F0D;
269
+ --secondary-color: #ff9a8b;
270
+ --accent-color: #FF6B6B;
271
+
272
+ /* ๋ฐฐ๊ฒฝ ์ปฌ๋Ÿฌ */
273
+ --background-color: #FFFFFF;
274
+ --card-bg: #ffffff;
275
+ --input-bg: #ffffff;
276
+
277
+ /* ํ…์ŠคํŠธ ์ปฌ๋Ÿฌ */
278
+ --text-color: #334155;
279
+ --text-secondary: #64748b;
280
+
281
+ /* ๋ณด๋” ๋ฐ ๊ตฌ๋ถ„์„  */
282
+ --border-color: #dddddd;
283
+ --border-light: #e5e5e5;
284
+
285
+ /* ํ…Œ์ด๋ธ” ์ปฌ๋Ÿฌ */
286
+ --table-even-bg: #f3f3f3;
287
+ --table-hover-bg: #f0f0f0;
288
+
289
+ /* ๊ทธ๋ฆผ์ž */
290
+ --shadow: 0 8px 30px rgba(251, 127, 13, 0.08);
291
+ --shadow-light: 0 2px 4px rgba(0, 0, 0, 0.1);
292
+
293
+ /* ๊ธฐํƒ€ */
294
+ --border-radius: 18px;
295
+ }
296
+
297
+ /* 2. ๋‹คํฌ๋ชจ๋“œ ์ƒ‰์ƒ ๋ณ€์ˆ˜ (์ž๋™ ๊ฐ์ง€) */
298
+ @media (prefers-color-scheme: dark) {
299
+ :root {
300
+ /* ๋ฐฐ๊ฒฝ ์ปฌ๋Ÿฌ */
301
+ --background-color: #1a1a1a;
302
+ --card-bg: #2d2d2d;
303
+ --input-bg: #2d2d2d;
304
+
305
+ /* ํ…์ŠคํŠธ ์ปฌ๋Ÿฌ */
306
+ --text-color: #e5e5e5;
307
+ --text-secondary: #a1a1aa;
308
+
309
+ /* ๋ณด๋” ๋ฐ ๊ตฌ๋ถ„์„  */
310
+ --border-color: #404040;
311
+ --border-light: #525252;
312
+
313
+ /* ํ…Œ์ด๋ธ” ์ปฌ๋Ÿฌ */
314
+ --table-even-bg: #333333;
315
+ --table-hover-bg: #404040;
316
+
317
+ /* ๊ทธ๋ฆผ์ž */
318
+ --shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
319
+ --shadow-light: 0 2px 4px rgba(0, 0, 0, 0.2);
320
+ }
321
+ }
322
+
323
+ /* 3. ์ˆ˜๋™ ๋‹คํฌ๋ชจ๋“œ ํด๋ž˜์Šค (Gradio ํ† ๊ธ€์šฉ) */
324
+ [data-theme="dark"],
325
+ .dark,
326
+ .gr-theme-dark {
327
+ /* ๋ฐฐ๊ฒฝ ์ปฌ๋Ÿฌ */
328
+ --background-color: #1a1a1a;
329
+ --card-bg: #2d2d2d;
330
+ --input-bg: #2d2d2d;
331
+
332
+ /* ํ…์ŠคํŠธ ์ปฌ๋Ÿฌ */
333
+ --text-color: #e5e5e5;
334
+ --text-secondary: #a1a1aa;
335
+
336
+ /* ๋ณด๋” ๋ฐ ๊ตฌ๋ถ„์„  */
337
+ --border-color: #404040;
338
+ --border-light: #525252;
339
+
340
+ /* ํ…Œ์ด๋ธ” ์ปฌ๋Ÿฌ */
341
+ --table-even-bg: #333333;
342
+ --table-hover-bg: #404040;
343
+
344
+ /* ๊ทธ๋ฆผ์ž */
345
+ --shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
346
+ --shadow-light: 0 2px 4px rgba(0, 0, 0, 0.2);
347
+ }
348
+
349
+ /* 4. ๊ธฐ๋ณธ ์š”์†Œ ๋‹คํฌ๋ชจ๋“œ ์ ์šฉ */
350
+ body {
351
+ font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
352
+ background-color: var(--background-color) !important;
353
+ color: var(--text-color) !important;
354
+ line-height: 1.6;
355
+ transition: background-color 0.3s ease, color 0.3s ease;
356
+ }
357
+
358
+ /* 5. Gradio ์ปจํ…Œ์ด๋„ˆ ๊ฐ•์ œ ์ ์šฉ */
359
+ .gradio-container,
360
+ .gradio-container *,
361
+ .gr-app,
362
+ .gr-app *,
363
+ .gr-interface {
364
+ background-color: var(--background-color) !important;
365
+ color: var(--text-color) !important;
366
+ }
367
+
368
+ /* Gradio ์ปจํ…Œ์ด๋„ˆ ์˜ค๋ฒ„๋ผ์ด๋“œ */
369
+ .gradio-container {
370
+ max-width: 100% !important;
371
+ width: 100% !important;
372
+ margin: 0 auto !important;
373
+ padding: 0 !important;
374
+ background-color: var(--background-color) !important;
375
+ box-sizing: border-box !important;
376
+ }
377
+
378
+ /* ์ถ”๊ฐ€: ๋‚ด๋ถ€ ์ปจํ…Œ์ด๋„ˆ๋„ 100% ๋„ˆ๋น„๋กœ ์„ค์ • */
379
+ .contain {
380
+ max-width: 100% !important;
381
+ width: 100% !important;
382
+ }
383
+
384
+ /* ์ถ”๊ฐ€: ๊ฐ ํ–‰(Row)๋„ 100% ๋„ˆ๋น„๋กœ ์„ค์ • */
385
+ .gr-padded {
386
+ padding: 0 !important;
387
+ width: 100% !important;
388
+ max-width: 100% !important;
389
+ }
390
+
391
+ /* 6. ์นด๋“œ ๋ฐ ํŒจ๋„ ์Šคํƒ€์ผ */
392
+ .gr-group,
393
+ .gr-form,
394
+ .gr-box,
395
+ .gr-panel,
396
+ .custom-frame,
397
+ [class*="frame"],
398
+ [class*="card"],
399
+ [class*="panel"] {
400
+ background-color: var(--card-bg) !important;
401
+ border-radius: var(--border-radius) !important;
402
+ box-shadow: var(--shadow) !important;
403
+ padding: 1.5rem !important;
404
+ margin-bottom: 1.5rem !important;
405
+ border: 1px solid var(--border-color) !important;
406
+ transition: transform 0.3s ease, background-color 0.3s ease;
407
+ color: var(--text-color) !important;
408
+ }
409
+
410
+ .gr-group:hover {
411
+ transform: translateY(-5px);
412
+ }
413
+
414
+ /* 7. ์ž…๋ ฅ ํ•„๋“œ ์Šคํƒ€์ผ */
415
+ input[type="text"],
416
+ input[type="number"],
417
+ input[type="email"],
418
+ input[type="password"],
419
+ textarea,
420
+ select,
421
+ .gr-input,
422
+ .gr-text-input,
423
+ .gr-textarea,
424
+ .gr-dropdown {
425
+ background-color: var(--input-bg) !important;
426
+ color: var(--text-color) !important;
427
+ border: 1px solid var(--border-color) !important;
428
+ border-radius: var(--border-radius) !important;
429
+ padding: 12px !important;
430
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05) !important;
431
+ transition: all 0.3s ease !important;
432
+ }
433
+
434
+ input[type="text"]:focus,
435
+ input[type="number"]:focus,
436
+ input[type="email"]:focus,
437
+ input[type="password"]:focus,
438
+ textarea:focus,
439
+ select:focus,
440
+ .gr-input:focus,
441
+ .gr-text-input:focus,
442
+ .gr-textarea:focus,
443
+ .gr-dropdown:focus {
444
+ border-color: var(--primary-color) !important;
445
+ outline: none !important;
446
+ box-shadow: 0 0 0 2px rgba(251, 127, 13, 0.2) !important;
447
+ }
448
+
449
+ /* 8. ๋ผ๋ฒจ ๋ฐ ํ…์ŠคํŠธ ์š”์†Œ */
450
+ label,
451
+ .gr-label,
452
+ .gr-checkbox label,
453
+ .gr-radio label,
454
+ p, span, div {
455
+ color: var(--text-color) !important;
456
+ }
457
+
458
+ /* 9. ์„น์…˜ ์ œ๋ชฉ */
459
+ .section-title {
460
+ font-size: 22px !important;
461
+ font-weight: 700 !important;
462
+ color: var(--text-color) !important;
463
+ margin-bottom: 1rem !important;
464
+ padding-bottom: 0.5rem !important;
465
+ border-bottom: 2px solid var(--primary-color) !important;
466
+ display: flex;
467
+ align-items: center;
468
+ }
469
+
470
+ .section-title span {
471
+ color: var(--primary-color);
472
+ }
473
+
474
+ /* 10. ๋ฒ„ํŠผ ์Šคํƒ€์ผ๋ง */
475
+ .custom-button {
476
+ background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important;
477
+ color: white !important;
478
+ font-weight: 600 !important;
479
+ border: none !important;
480
+ border-radius: 30px !important;
481
+ padding: 12px 24px !important;
482
+ box-shadow: 0 4px 8px rgba(251, 127, 13, 0.25) !important;
483
+ transition: all 0.3s ease !important;
484
+ text-transform: none !important;
485
+ display: flex !important;
486
+ align-items: center !important;
487
+ justify-content: center !important;
488
+ }
489
+
490
+ .custom-button:hover {
491
+ transform: translateY(-2px) !important;
492
+ box-shadow: 0 6px 12px rgba(251, 127, 13, 0.3) !important;
493
+ }
494
+
495
+ .custom-button.primary {
496
+ background: linear-gradient(135deg, var(--accent-color), #ff9a8b) !important;
497
+ }
498
+
499
+ button:not([class*="custom"]):not([class*="primary"]):not([class*="secondary"]) {
500
+ background-color: var(--card-bg) !important;
501
+ color: var(--text-color) !important;
502
+ border: 1px solid var(--border-color) !important;
503
+ border-radius: var(--border-radius) !important;
504
+ transition: all 0.3s ease !important;
505
+ }
506
+
507
+ /* 11. ์ด๋ฏธ์ง€ ์ปจํ…Œ์ด๋„ˆ */
508
+ .image-container {
509
+ border-radius: var(--border-radius);
510
+ overflow: hidden;
511
+ border: 1px solid var(--border-color);
512
+ transition: all 0.3s ease;
513
+ background-color: var(--card-bg);
514
+ }
515
+
516
+ .image-container:hover {
517
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
518
+ }
519
+
520
+ /* 12. ํ…Œ์ด๋ธ” ์Šคํƒ€์ผ */
521
+ table {
522
+ background-color: var(--card-bg) !important;
523
+ color: var(--text-color) !important;
524
+ border-color: var(--border-color) !important;
525
+ }
526
+
527
+ table th {
528
+ background-color: var(--primary-color) !important;
529
+ color: white !important;
530
+ border-color: var(--border-color) !important;
531
+ }
532
+
533
+ table td {
534
+ background-color: var(--card-bg) !important;
535
+ color: var(--text-color) !important;
536
+ border-color: var(--border-color) !important;
537
+ }
538
+
539
+ table tbody tr:nth-child(even) {
540
+ background-color: var(--table-even-bg) !important;
541
+ }
542
+
543
+ table tbody tr:hover {
544
+ background-color: var(--table-hover-bg) !important;
545
+ }
546
+
547
+ /* 13. ์ฒดํฌ๋ฐ•์Šค ๋ฐ ๋ผ๋””์˜ค ๋ฒ„ํŠผ */
548
+ input[type="checkbox"],
549
+ input[type="radio"] {
550
+ accent-color: var(--primary-color) !important;
551
+ }
552
+
553
+ /* 14. ๋ฒ„ํŠผ ๊ทธ๋ฃน */
554
+ .button-grid {
555
+ display: grid;
556
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
557
+ gap: 0.8rem;
558
+ margin-bottom: 1.2rem;
559
+ }
560
+
561
+ /* 15. ์Šคํฌ๋กค๋ฐ” ์Šคํƒ€์ผ */
562
+ ::-webkit-scrollbar-thumb:hover {
563
+ background: var(--secondary-color);
564
+ }
565
+
566
+ /* 16. ์•„์ฝ”๋””์–ธ ๋ฐ ๋“œ๋กญ๋‹ค์šด */
567
+ details {
568
+ background-color: var(--card-bg) !important;
569
+ border-color: var(--border-color) !important;
570
+ color: var(--text-color) !important;
571
+ }
572
+
573
+ details summary {
574
+ background-color: var(--card-bg) !important;
575
+ color: var(--text-color) !important;
576
+ }
577
+
578
+ /* 17. ํˆดํŒ ๋ฐ ํŒ์—… */
579
+ [data-tooltip]:hover::after,
580
+ .tooltip,
581
+ .popup {
582
+ background-color: var(--card-bg) !important;
583
+ color: var(--text-color) !important;
584
+ border-color: var(--border-color) !important;
585
+ box-shadow: var(--shadow-light) !important;
586
+ }
587
+
588
+ /* 18. ๋ชจ๋‹ฌ ๋ฐ ์˜ค๋ฒ„๋ ˆ์ด */
589
+ .modal,
590
+ .overlay,
591
+ [class*="modal"],
592
+ [class*="overlay"] {
593
+ background-color: var(--card-bg) !important;
594
+ color: var(--text-color) !important;
595
+ border-color: var(--border-color) !important;
596
+ }
597
+
598
+ /* 19. ์ถ”๊ฐ€ Gradio ์ปดํฌ๋„ŒํŠธ๋“ค */
599
+ .gr-block,
600
+ .gr-group,
601
+ .gr-row,
602
+ .gr-column {
603
+ background-color: var(--background-color) !important;
604
+ color: var(--text-color) !important;
605
+ }
606
+
607
+ /* 20. ์ฝ”๋“œ ๋ธ”๋ก ๋ฐ pre ํƒœ๊ทธ */
608
+ code,
609
+ pre,
610
+ .code-block {
611
+ background-color: var(--table-even-bg) !important;
612
+ color: var(--text-color) !important;
613
+ border-color: var(--border-color) !important;
614
+ }
615
+
616
+ /* 21. ์•Œ๋ฆผ ๋ฐ ๋ฉ”์‹œ์ง€ */
617
+ .alert,
618
+ .message,
619
+ .notification,
620
+ [class*="alert"],
621
+ [class*="message"],
622
+ [class*="notification"] {
623
+ background-color: var(--card-bg) !important;
624
+ color: var(--text-color) !important;
625
+ border-color: var(--border-color) !important;
626
+ }
627
+
628
+ /* 22. ์• ๋‹ˆ๋ฉ”์ด์…˜ ์Šคํƒ€์ผ */
629
+ @keyframes fadeIn {
630
+ from { opacity: 0; transform: translateY(10px); }
631
+ to { opacity: 1; transform: translateY(0); }
632
+ }
633
+
634
+ .fade-in {
635
+ animation: fadeIn 0.5s ease-out;
636
+ }
637
+
638
+ /* 23. Examples ์„น์…˜ ์Šคํƒ€์ผ */
639
+ .examples-section {
640
+ display: grid;
641
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
642
+ gap: 1.5rem;
643
+ margin-top: 1rem;
644
+ }
645
+
646
+ .example-item {
647
+ background-color: var(--card-bg);
648
+ border-radius: var(--border-radius);
649
+ overflow: hidden;
650
+ box-shadow: var(--shadow);
651
+ transition: transform 0.3s ease, background-color 0.3s ease;
652
+ }
653
+
654
+ .example-item:hover {
655
+ transform: translateY(-5px);
656
+ }
657
+
658
+ /* 24. ์ „ํ™˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ */
659
+ * {
660
+ transition: background-color 0.3s ease,
661
+ color 0.3s ease,
662
+ border-color 0.3s ease !important;
663
+ }
664
+
665
+ /* 25. ๋ฐ˜์‘ํ˜• */
666
+ @media (max-width: 768px) {
667
+ .button-grid {
668
+ grid-template-columns: repeat(2, 1fr);
669
+ }
670
+ .examples-section {
671
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
672
+ }
673
+ }
674
+ """
675
+
676
+ # FontAwesome ์•„์ด์ฝ˜ ํฌํ•จ
677
+ fontawesome_link = """
678
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
679
+ """
680
+
681
+ # ์ œ๋ชฉ๊ณผ ์‚ฌ์šฉ ๊ฐ€์ด๋“œ ์ œ๊ฑฐ
682
+ header_html = ""
683
+ image_generator_guide_html = ""
684
+
685
+ # UI ๊ตฌ์„ฑ
686
+ with gr.Blocks(css=custom_css, theme=gr.themes.Default(
687
+ primary_hue="orange",
688
+ secondary_hue="orange",
689
+ font=[gr.themes.GoogleFont("Noto Sans KR"), "ui-sans-serif", "system-ui"]
690
+ )) as demo:
691
+ gr.HTML(fontawesome_link)
692
+
693
+ # ํƒญ ์ œ๊ฑฐํ•˜๊ณ  ์ด๋ฏธ์ง€ ์ƒ์„ฑ๊ธฐ ๋‚ด์šฉ์„ ์ง์ ‘ ๋ฐฐ์น˜
694
+ with gr.Row(equal_height=True):
695
+ with gr.Column(scale=1):
696
+ # ======== ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๋ฐ ์„ค์ • ์„น์…˜ ========
697
+ with gr.Group():
698
+ gr.HTML('<div class="section-title"><i class="fas fa-upload"></i> <span>์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๋ฐ ์„ค์ •</span></div>')
699
+ with gr.Row():
700
+ image1_input = gr.Image(type="pil", label="#1", image_mode="RGB", elem_classes="image-container", height=400)
701
+ image2_input = gr.Image(type="pil", label="#2", image_mode="RGB", elem_classes="image-container", height=400)
702
+ image3_input = gr.Image(type="pil", label="#3", image_mode="RGB", elem_classes="image-container", height=400)
703
+
704
+ # ํ”„๋กฌํ”„ํŠธ ์ž…๋ ฅ ํ•„๋“œ ์ถ”๊ฐ€
705
+ prompt_input = gr.Textbox(
706
+ lines=3,
707
+ placeholder="ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž…๋ ฅํ•˜๊ฑฐ๋‚˜ ๋น„์›Œ๋‘๋ฉด ์ž๋™ ํ•ฉ์„ฑ๋ฉ๋‹ˆ๋‹ค. '#1', '#2', '#3'์œผ๋กœ ๊ฐ ์ด๋ฏธ์ง€๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
708
+ label="ํ”„๋กฌํ”„ํŠธ (์„ ํƒ ์‚ฌํ•ญ)",
709
+ elem_classes="gr-text-input"
710
+ )
711
+
712
+ # ======== ๋ณ€ํ™˜ ์˜ต์…˜ ์„น์…˜ ========
713
+ with gr.Group():
714
+ gr.HTML('<div class="section-title"><i class="fas fa-sliders-h"></i> <span>ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ</span></div>')
715
+ with gr.Column(elem_classes="button-grid"):
716
+ image_change_btn1 = gr.Button('๐Ÿ”„ ๋ถ€๋ถ„๋ณ€๊ฒฝ-1', elem_classes="custom-button")
717
+ image_change_btn2 = gr.Button('๐Ÿ”„ ๋ถ€๋ถ„๋ณ€๊ฒฝ-2', elem_classes="custom-button")
718
+ image_change_btn3= gr.Button('๐Ÿ”„ ๋ถ€๋ถ„๋ณ€๊ฒฝ-3', elem_classes="custom-button")
719
+ text_remove_btn = gr.Button('๐Ÿงน ๊ธ€์ž์ง€์šฐ๊ธฐ', elem_classes="custom-button")
720
+ text_change_btn = gr.Button('๐Ÿ”ค ๊ธ€์ž๋ณ€๊ฒฝ', elem_classes="custom-button")
721
+ clothes_change_btn1 = gr.Button('๐Ÿ‘• ์ƒํ’ˆ์ฐฉ์šฉ-1', elem_classes="custom-button")
722
+ clothes_change_btn2 = gr.Button('๐Ÿ‘“ ์ƒํ’ˆ์ฐฉ์šฉ-2', elem_classes="custom-button")
723
+ holding_product_btn = gr.Button('๐Ÿท ์ƒํ’ˆ๋“ค๊ณ ', elem_classes="custom-button")
724
+ background_change_btn = gr.Button('๐Ÿ–ผ๏ธ ๋ฐฐ๊ฒฝ๋ฐ”๊พธ๊ธฐ', elem_classes="custom-button")
725
+ composite_product_btn = gr.Button('โœ‚๏ธ ๋ถ€๋ถ„์ง€์šฐ๊ธฐ', elem_classes="custom-button")
726
+ outpainting_btn = gr.Button('๐Ÿ” ์ด๋ฏธ์ง€ํ™•์žฅ', elem_classes="custom-button")
727
+ food_btn_1 = gr.Button('๐Ÿฝ๏ธ ํ”Œ๋ ˆ์ดํŒ…-1', elem_classes="custom-button")
728
+ food_btn_2 = gr.Button('๐Ÿฝ๏ธ ํ”Œ๋ ˆ์ดํŒ…-2', elem_classes="custom-button")
729
+ food_btn_3 = gr.Button('๐Ÿฝ๏ธ ํ”Œ๋ ˆ์ดํŒ…-3', elem_classes="custom-button")
730
+
731
+ # ======== ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์„น์…˜ ========
732
+ with gr.Group():
733
+ gr.HTML('<div class="section-title"><i class="fas fa-image"></i> <span>์ด๋ฏธ์ง€ ์ƒ์„ฑ</span></div>')
734
+ submit_single_btn = gr.Button('โœจ ์ด๋ฏธ์ง€ ์ƒ์„ฑ (1์žฅ)', elem_classes="custom-button primary")
735
+ submit_btn = gr.Button('โœจ ์ด๋ฏธ์ง€ ์ƒ์„ฑ (2์žฅ)', elem_classes="custom-button primary")
736
+
737
+ with gr.Column(scale=1):
738
+ # ======== ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€ ์„น์…˜ ========
739
+ with gr.Group():
740
+ gr.HTML('<div class="section-title"><i class="fas fa-images"></i> <span>์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€</span></div>')
741
+ with gr.Row():
742
+ with gr.Column():
743
+ output_image1 = gr.Image(label="์ด๋ฏธ์ง€ #1", elem_classes="image-container", height=400)
744
+ with gr.Column():
745
+ output_image2 = gr.Image(label="์ด๋ฏธ์ง€ #2", elem_classes="image-container", height=400)
746
+
747
+ # ======== ๊ฒฐ๊ณผ ์ •๋ณด ์„น์…˜ ========
748
+ with gr.Group():
749
+ gr.HTML('<div class="section-title"><i class="fas fa-info-circle"></i> <span>๊ฒฐ๊ณผ ์ •๋ณด</span></div>')
750
+ output_text = gr.Textbox(label="์ƒํƒœ ๋ฉ”์‹œ์ง€", lines=2, elem_classes="gr-text-input")
751
+ prompt_display = gr.Textbox(label="์‚ฌ์šฉ๋œ ํ”„๋กฌํ”„ํŠธ (์˜์–ด)", visible=True, lines=2, elem_classes="gr-text-input")
752
+
753
+ # ======== ์˜ˆ์ œ ์ด๋ฏธ์ง€ ์„น์…˜ ========
754
+ gr.HTML('<div class="section-title"><i class="fas fa-lightbulb"></i> <span>์˜ˆ์ œ ์ด๋ฏธ์ง€</span></div>')
755
+
756
+ # ๋ชจ๋“  ์˜ˆ์ œ ํ•œ ํŽ˜์ด์ง€์— ํ‘œ์‹œ
757
+ examples = [
758
+ ["down/๋ชจ๋ธ.jpg", None, None, "(#1์˜ ์—ฌ์„ฑ)์ด ์‚ด์ง ๋’ค๋กœ ๋Œ์•„๋ณด๋Š” ๋ชจ์Šต์œผ๋กœ ์ตœ๋Œ€ํ•œ ์ด์ „ seed๋ฅผ ์œ ์ง€ํ•œ์ฒด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ณ€๊ฒฝํ•˜๋ผ."],
759
+ ["down/์ƒ์–ด๋ ˆ๊ณ ๋ชจํ˜•.png", None, None, "(#1 ๋ ˆ๋ชจ๋ชจํ˜•)์—์„œ ์ฒญ์ƒ‰์ƒ์–ด๋ ˆ๊ณ ๋งŒ ๊ฒ€์€์ƒ‰ ๊ณ ๋ž˜๋ ˆ๊ณ ๋กœ ๋ณ€๊ฒฝํ•˜๊ณ  ๋‚˜๋จธ์ง€ ๋ถ€๋ถ„์€ seed๋ฅผ ๋ณ€๊ฒฝ์„ ํ•˜์ง€๋งˆ๋ผ."],
760
+ ["down/์–ผ์Œ๊ฐ€๋ฐฉ.png", None, None, "(#1 ์—ฌํ–‰์šฉ ์–ผ์Œ๋ฐ•์Šค)์•ž์— ์–ผ์Œ์ด ๋‹ด๊ธด 3์ž”์˜ ์ฝœ๋ผ๊ฐ€ ๋†“์—ฌ์žˆ๋Š” ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ."],
761
+ ["down/์ค‘๊ตญ์–ด.png", None, None, "(#1 ์ด๋ฏธ์ง€)์— ์žˆ๋Š” ์ค‘๊ตญ์–ด๋ฅผ ๋ชจ๋‘ ์ œ๊ฑฐํ•˜๋ผ."],
762
+ ["down/ํ…์ŠคํŠธ.webp", None, None, '(#1์˜ ํ…์ŠคํŠธ)๋ฅผ ์Šคํƒ€์ผ์„ ์œ ์ง€ํ•œ์ฒด ํ…์ŠคํŠธ๋งŒ "Hello"๋กœ ๋ฐ”๊ฟ”๋ผ'],
763
+ ["down/๋ชจ๋ธ2.png", "down/์„ ๊ธ€๋ผ์Šค.png", "down/์ฒญ๋ฐ”์ง€.png", "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด ์‹ ์ฒด ๋น„์œจ๊ณผ ํฌ์ฆˆ๋Š” ์œ ์ง€ํ•œ ์ฒด (#2์˜ ์„ ๊ธ€๋ผ์Šค)์™€ (#3์˜ ์ฒญ๋ฐ”์ง€)๋ฅผ ์ง์ ‘ ๋ชจ๋ธ์ด ์ฐฉ์šฉํ•œ๊ฒƒ ์ฒ˜๋Ÿผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ."],
764
+ ["down/๋ชจ๋ธ2.png", "down/์„ ๊ธ€๋ผ์Šค.png", "down/์นดํŽ˜์ „๊ฒฝ.png", "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด (#2์˜ ์„ ๊ธ€๋ผ์Šค)์„ ์ฐฉ์šฉํ•˜๊ณ  (#3์˜ ๋’ท๋ฐฐ๊ฒฝ์˜ ์นดํŽ˜์ „์ฒด๊ฐ€ ๋ณด์ด๋ฉฐ) ์˜์ž์— ์•‰์•„ ์žˆ๋Š” ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ."],
765
+ ["down/๋ชจ๋ธ2.png", "down/์™€์ธ์ž”.png", None, "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด(#2์˜ ์™€์ธ์ž”)์„ ๋“ค๊ณ  ์žˆ๋Š” ์ž์—ฐ์Šค๋Ÿฌ์šด ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ."],
766
+ ["down/๋ชจ๋ธ2.png", "down/์นดํŽ˜์ „๊ฒฝ.png", None, "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด (#2 ์นดํŽ˜)์—์„œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์žˆ๋Š” ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ."],
767
+ ["down/์ƒ์–ด๋ ˆ๊ณ ๋ชจํ˜•.png", None, None, "(#1์˜ ๋ ˆ๊ณ ๋ชจํ˜•)์—์„œ ์ฒญ์ƒ‰์ƒ์–ด๋ ˆ๊ณ ๋ฅผ ์ œ๊ฑฐํ•œ ํ›„, ๊ทธ ์ž๋ฆฌ๋ฅผ ์ฃผ๋ณ€ ๋ฐฐ๊ฒฝ๊ณผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์–ด์šฐ๋Ÿฌ์ง€๋„๋ก ์ฑ„์›Œ์ฃผ์„ธ์š”. ๋‹จ, ์ด๋ฏธ์ง€์˜ ๋‹ค๋ฅธ ๋ถ€๋ถ„์˜ ์ฃผ์š” ์š”์†Œ๋Š” ๋™์ผํ•˜๊ฒŒ ์œ ์ง€ํ•ด์•ผํ•œ๋‹ค."],
768
+ ["down/์นดํŽ˜์ „๊ฒฝ.png", None, None, "(#1 ์ด๋ฏธ์ง€)๋ฅผ ์›๋ณธ๊ทธ๋Œ€๋กœ ์ค‘์•™์— ๋‘๊ณ  ๋น„์œจ๋กœ ์œ ์ง€ํ•œ ์ฒด ์œ„์•„๋ž˜ ๋ฐ ์ขŒ์šฐ๋กœ ํฌ๊ฒŒ ํ™•์žฅํ•˜๋ผ."],
769
+ ["down/์ƒ๋Ÿฌ๋“œ.png", None, None, "(#1์…€๋Ÿฌ๋“œ)์— ๋‹ด์€ ์šฉ๊ธฐ๋Š” ๋ฒ„๋ฆฌ๊ณ  ๋„“๊ณ  ํฐ ์˜ˆ์œ ์ ‘์‹œ์— (#1์…€๋Ÿฌ๋“œ)์Œ์‹๋งŒ ๊ฐ€๋“ ์ฑ„์›Œ์„œ ์ƒ์—…์ ์ธ ๊ฐ๋„๋กœ ์–ด์šธ๋ฆฌ๋Š” ์†Œํ’ˆ๊ณผ ํ•จ๊ป˜ ํ”Œ๋ ˆ์ดํŒ… ํ•œ ๋ชจ์Šต์„ ์ด๋ฏธ์ง€๋กœ ์ƒ์„ฑํ•˜๋ผ. "],
770
+ ["down/์ƒ๋Ÿฌ๋“œ.png", "down/ํ”Œ๋ ˆ์ดํŒ….png", None, "(#2 ํ”Œ๋ ˆ์ดํŒ…ํ•œ ์ด๋ฏธ์ง€)์— ๋‹ด๊ธด ์Œ์‹์„ (#1 ์ƒ๋Ÿฌ๋“œ)๋กœ ๋ฐ”๊พธ๊ณ  ๋‚˜๋จธ์ง€๋Š” ์‹œ๋“œ๋ฅผ ์œ ์ง€ํ•œ ์ฒด ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ."],
771
+ ["down/์ปต.png", None, None, "(#1์ปต)์— ๋”ธ๊ธฐ, ๋ฐ”๋‹๋ผ, ์ดˆ์ฝ” ์•„์ด์Šคํฌ๋ฆผ์„ ๋‹ด๊ณ  ๊ทธ ์œ„์— ์ดˆ์ฝ” ์‹œ๋Ÿฝ์ด ํ๋ฅด๊ฒŒ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ."]
772
+ ]
773
+
774
+ # ๋ชจ๋“  ์˜ˆ์ œ๋ฅผ ํ•œ ํŽ˜์ด์ง€์— ํ‘œ์‹œํ•˜๋„๋ก ์ˆ˜์ •๋œ ๋ถ€๋ถ„
775
+ gr.Examples(
776
+ examples=examples,
777
+ inputs=[image1_input, image2_input, image3_input, prompt_input],
778
+ examples_per_page=len(examples) # ๋ชจ๋“  ์˜ˆ์ œ๋ฅผ ํ•œ ํŽ˜์ด์ง€์— ํ‘œ์‹œ
779
+ )
780
+
781
+ # ========== ์ด๋ฏธ์ง€ ์ƒ์„ฑ๊ธฐ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ ==========
782
+ # ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
783
+ image_change_btn1.click(
784
+ fn=lambda: "(#1์˜ ์—ฌ์„ฑ)์ด ์‚ด์ง ๋’ค๋กœ ๋Œ์•„๋ณด๋Š” ๋ชจ์Šต์œผ๋กœ ์ตœ๋Œ€ํ•œ ์ด์ „ seed๋ฅผ ์œ ์ง€ํ•œํ…Œ ์ž์—ฐ๏ฟฝ๏ฟฝ๋Ÿฝ๊ฒŒ ๋ณ€๊ฒฝํ•˜๋ผ.",
785
+ inputs=[],
786
+ outputs=prompt_input
787
+ )
788
+ image_change_btn2.click(
789
+ fn=lambda: "(#1 ๋ ˆ๋ชจ๋ชจํ˜•)์—์„œ ์ฒญ์ƒ‰์ƒ์–ด๋ ˆ๊ณ ๋งŒ ๊ฒ€์€์ƒ‰ ๊ณ ๋ž˜๋ ˆ๊ณ ๋กœ ๋ณ€๊ฒฝํ•˜๊ณ  ๋‚˜๋จธ์ง€ ๋ถ€๋ถ„์€ seed๋ฅผ ๋ณ€๊ฒฝ์„ ํ•˜์ง€๋งˆ๋ผ.",
790
+ inputs=[],
791
+ outputs=prompt_input
792
+ )
793
+ image_change_btn3.click(
794
+ fn=lambda: "(#1 ์—ฌํ–‰์šฉ ์–ผ์Œ๋ฐ•์Šค)์•ž์— ์–ผ์Œ์ด ๋‹ด๊ธด 3์ž”์˜ ์ฝœ๋ผ๊ฐ€ ๋†“์—ฌ์žˆ๋Š” ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ.",
795
+ inputs=[],
796
+ outputs=prompt_input
797
+ )
798
+
799
+ text_remove_btn.click(
800
+ fn=lambda: "(#1 ์ด๋ฏธ์ง€)์— ์žˆ๋Š” ์ค‘๊ตญ์–ด๋ฅผ ๋ชจ๋‘ ์ œ๊ฑฐํ•˜๋ผ.",
801
+ inputs=[],
802
+ outputs=prompt_input
803
+ )
804
+ text_change_btn.click(
805
+ fn=lambda: '(#1์˜ ํ…์ŠคํŠธ)๋ฅผ ์Šคํƒ€์ผ์„ ์œ ์ง€ํ•œ์ฒด ํ…์ŠคํŠธ๋งŒ "Hello"๋กœ ๋ฐ”๊ฟ”๋ผ',
806
+ inputs=[],
807
+ outputs=prompt_input
808
+ )
809
+ clothes_change_btn1.click(
810
+ fn=lambda: "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด ์‹ ์ฒด ๋น„์œจ๊ณผ ํฌ์ฆˆ๋Š” ์œ ์ง€ํ•œ ์ฒด (#2์˜ ์„ ๊ธ€๋ผ์Šค)์™€ (#3์˜ ์ฒญ๋ฐ”์ง€)๋ฅผ ์ง์ ‘ ๋ชจ๋ธ์ด ์ฐฉ์šฉํ•œ๊ฒƒ ์ฒ˜๋Ÿผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ.",
811
+ inputs=[],
812
+ outputs=prompt_input
813
+ )
814
+ clothes_change_btn2.click(
815
+ fn=lambda: "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด (#2์˜ ์„ ๊ธ€๋ผ์Šค)์„ ์ฐฉ์šฉํ•˜๊ณ  (#3์˜ ๋’ท๋ฐฐ๊ฒฝ์˜ ์นดํŽ˜์ „์ฒด๊ฐ€ ๋ณด์ด๋ฉฐ) ์˜์ž์— ์•‰์•„ ์žˆ๋Š” ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ.",
816
+ inputs=[],
817
+ outputs=prompt_input
818
+ )
819
+ holding_product_btn.click(
820
+ fn=lambda: "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด(#2์˜ ์™€์ธ์ž”)์„ ๋“ค๊ณ  ์žˆ๋Š” ์ž์—ฐ์Šค๋Ÿฌ์šด ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ.",
821
+ inputs=[],
822
+ outputs=prompt_input
823
+ )
824
+ background_change_btn.click(
825
+ fn=lambda: "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด (#2 ์นดํŽ˜)์—์„œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์žˆ๋Š” ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ.",
826
+ inputs=[],
827
+ outputs=prompt_input
828
+ )
829
+ composite_product_btn.click(
830
+ fn=lambda: "(#1์˜ ๋ ˆ๊ณ ๋ชจํ˜•)์—์„œ ์ฒญ์ƒ‰์ƒ์–ด๋ ˆ๊ณ ๋ฅผ ์ œ๊ฑฐํ•œ ํ›„, ๊ทธ ์ž๋ฆฌ๋ฅผ ์ฃผ๋ณ€ ๋ฐฐ๊ฒฝ๊ณผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์–ด์šฐ๋Ÿฌ์ง€๋„๋ก ์ฑ„์›Œ์ฃผ์„ธ์š”. ๋‹จ, ์ด๋ฏธ์ง€์˜ ๋‹ค๋ฅธ ๋ถ€๋ถ„์˜ ์ฃผ์š” ์š”์†Œ๋Š” ๋™์ผํ•˜๊ฒŒ ์œ ์ง€ ํ•ด์•ผํ•œ๋‹ค.",
831
+ inputs=[],
832
+ outputs=prompt_input
833
+ )
834
+ outpainting_btn.click(
835
+ fn=lambda: "(#1 ์ด๋ฏธ์ง€)๋ฅผ ์›๋ณธ๊ทธ๋Œ€๋กœ ์ค‘์•™์— ๋‘๊ณ  ๋น„์œจ๋กœ ์œ ์ง€ํ•œ ์ฒด ์œ„์•„๋ž˜ ๋ฐ ์ขŒ์šฐ๋กœ ํฌ๊ฒŒ ํ™•์žฅํ•˜๋ผ.",
836
+ inputs=[],
837
+ outputs=prompt_input
838
+
839
+ )
840
+ food_btn_1.click(
841
+ fn=lambda: "(#1์…€๋Ÿฌ๋“œ)์— ๋‹ด์€ ์šฉ๊ธฐ๋Š” ๋ฒ„๋ฆฌ๊ณ  ๋„“๊ณ  ํฐ ์˜ˆ์œ ์ ‘์‹œ์— (#1์…€๋Ÿฌ๋“œ)์Œ์‹๋งŒ ๊ฐ€๋“ ์ฑ„์›Œ์„œ ์ƒ์—…์ ์ธ ๊ฐ๋„๋กœ ์–ด์šธ๋ฆฌ๋Š” ์†Œํ’ˆ๊ณผ ํ•จ๊ป˜ ํ”Œ๋ ˆ์ดํŒ… ํ•œ ๋ชจ์Šต์„ ์ด๋ฏธ์ง€๋กœ ์ƒ์„ฑํ•˜๋ผ. ",
842
+ inputs=[],
843
+ outputs=prompt_input
844
+ )
845
+ food_btn_2.click(
846
+ fn=lambda: "(#2 ํ”Œ๋ ˆ์ดํŒ…ํ•œ ์ด๋ฏธ์ง€)์— ๋‹ด๊ธด ์Œ์‹์„ (#1 ์ƒ๋Ÿฌ๋“œ)๋กœ ๋ฐ”๊พธ๊ณ  ๋‚˜๋จธ์ง€๋Š” ์‹œ๋“œ๋ฅผ ์œ ์ง€ํ•œ ์ฒด ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ.",
847
+ inputs=[],
848
+ outputs=prompt_input
849
+ )
850
+
851
+ food_btn_3.click(
852
+ fn=lambda: "(#1์ปต)์— ๋”ธ๊ธฐ, ๋ฐ”๋‹๋ผ, ์ดˆ์ฝ” ์•„์ด์Šคํฌ๋ฆผ์„ ๋‹ด๊ณ  ๊ทธ ์œ„์— ์ดˆ์ฝ” ์‹œ๋Ÿฝ์ด ํ๋ฅด๊ฒŒ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ.",
853
+ inputs=[],
854
+ outputs=prompt_input
855
+ )
856
+
857
+ # ๋‹จ์ผ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
858
+ def generate_single_image(image1, image2, image3, prompt):
859
+ return process_images_with_prompt(image1, image2, image3, prompt, 0, 3)
860
+
861
+ submit_single_btn.click(
862
+ fn=generate_single_image,
863
+ inputs=[image1_input, image2_input, image3_input, prompt_input],
864
+ outputs=[output_image1, output_text, prompt_display],
865
+ )
866
+
867
+ # 2์žฅ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
868
+ submit_btn.click(
869
+ fn=generate_multiple_images,
870
+ inputs=[image1_input, image2_input, image3_input, prompt_input],
871
+ outputs=[output_image1, output_image2, output_text, prompt_display],
872
+ )
873
+
874
+ # ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰
875
+ if __name__ == "__main__":
876
+ # API ์—”๋“œํฌ์ธํŠธ ํ™•์ธ
877
+ endpoint = get_api_endpoint()
878
+ if endpoint:
879
+ logger.info("์ปจํŠธ๋กค ํƒ€์›Œ๊ฐ€ ์‹œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
880
+ else:
881
+ logger.warning("API_ENDPOINT ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•ด์ฃผ์„ธ์š”.")
882
+
883
+ demo.queue()
884
+ demo.launch(share=False, inbrowser=True, width="100%")