ssboost commited on
Commit
3368484
ยท
verified ยท
1 Parent(s): c6a791f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1223 -1
app.py CHANGED
@@ -1,2 +1,1224 @@
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
+
13
+ from google import genai
14
+ from google.genai import types
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
+ # ========== ์ด๋ฏธ์ง€ ์ƒ์„ฑ๊ธฐ ๊ด€๋ จ ํ•จ์ˆ˜ ==========
24
+ def save_binary_file(file_name, data):
25
+ with open(file_name, "wb") as f:
26
+ f.write(data)
27
+
28
+ def translate_prompt_to_english(prompt, api_key=None):
29
+ # ๊ธฐ์กด ํ•จ์ˆ˜ ์œ ์ง€ํ•˜๋˜ API ํ‚ค ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€
30
+ if not re.search("[๊ฐ€-ํžฃ]", prompt):
31
+ return prompt
32
+
33
+ prompt = prompt.replace("#1", "IMAGE_TAG_ONE")
34
+ prompt = prompt.replace("#2", "IMAGE_TAG_TWO")
35
+ prompt = prompt.replace("#3", "IMAGE_TAG_THREE")
36
+
37
+ try:
38
+ # ์‚ฌ์šฉ์ž ์ž…๋ ฅ API ํ‚ค ๋˜๋Š” ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์‚ฌ์šฉ
39
+ used_api_key = api_key if api_key else os.environ.get("GEMINI_API_KEY")
40
+ if not used_api_key:
41
+ logger.error("Gemini API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
42
+ prompt = prompt.replace("IMAGE_TAG_ONE", "#1")
43
+ prompt = prompt.replace("IMAGE_TAG_TWO", "#2")
44
+ prompt = prompt.replace("IMAGE_TAG_THREE", "#3")
45
+ return prompt
46
+
47
+ client = genai.Client(api_key=used_api_key)
48
+ translation_prompt = f"""
49
+ Translate the following Korean text to English:
50
+
51
+ {prompt}
52
+
53
+ IMPORTANT: The tokens IMAGE_TAG_ONE, IMAGE_TAG_TWO, and IMAGE_TAG_THREE are special tags
54
+ and must be preserved exactly as is in your translation. Do not translate these tokens.
55
+ """
56
+
57
+ logger.info(f"Translation prompt: {translation_prompt}")
58
+ response = client.models.generate_content(
59
+ model="gemini-2.0-flash",
60
+ contents=[translation_prompt],
61
+ config=types.GenerateContentConfig(
62
+ response_modalities=['Text'],
63
+ temperature=0.2,
64
+ top_p=0.95,
65
+ top_k=40,
66
+ max_output_tokens=512
67
+ )
68
+ )
69
+
70
+ translated_text = ""
71
+ for part in response.candidates[0].content.parts:
72
+ if hasattr(part, 'text') and part.text:
73
+ translated_text += part.text
74
+
75
+ if translated_text.strip():
76
+ translated_text = translated_text.replace("IMAGE_TAG_ONE", "#1")
77
+ translated_text = translated_text.replace("IMAGE_TAG_TWO", "#2")
78
+ translated_text = translated_text.replace("IMAGE_TAG_THREE", "#3")
79
+ logger.info(f"Translated text: {translated_text.strip()}")
80
+ return translated_text.strip()
81
+ else:
82
+ logger.warning("๋ฒˆ์—ญ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์›๋ณธ ํ”„๋กฌํ”„ํŠธ ์‚ฌ์šฉ")
83
+ prompt = prompt.replace("IMAGE_TAG_ONE", "#1")
84
+ prompt = prompt.replace("IMAGE_TAG_TWO", "#2")
85
+ prompt = prompt.replace("IMAGE_TAG_THREE", "#3")
86
+ return prompt
87
+ except Exception as e:
88
+ logger.exception("๋ฒˆ์—ญ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:")
89
+ prompt = prompt.replace("IMAGE_TAG_ONE", "#1")
90
+ prompt = prompt.replace("IMAGE_TAG_TWO", "#2")
91
+ prompt = prompt.replace("IMAGE_TAG_THREE", "#3")
92
+ return prompt
93
+
94
+ def preprocess_prompt(prompt, image1, image2, image3):
95
+ # ๊ธฐ์กด ํ•จ์ˆ˜ ์œ ์ง€
96
+ has_img1 = image1 is not None
97
+ has_img2 = image2 is not None
98
+ has_img3 = image3 is not None
99
+
100
+ if "#1" in prompt and not has_img1:
101
+ prompt = prompt.replace("#1", "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€(์—†์Œ)")
102
+ else:
103
+ prompt = prompt.replace("#1", "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€")
104
+
105
+ if "#2" in prompt and not has_img2:
106
+ prompt = prompt.replace("#2", "๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€(์—†์Œ)")
107
+ else:
108
+ prompt = prompt.replace("#2", "๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€")
109
+
110
+ if "#3" in prompt and not has_img3:
111
+ prompt = prompt.replace("#3", "์„ธ ๋ฒˆ์งธ ์ด๋ฏธ์ง€(์—†์Œ)")
112
+ else:
113
+ prompt = prompt.replace("#3", "์„ธ ๋ฒˆ์งธ ์ด๋ฏธ์ง€")
114
+
115
+ if "1. ์ด๋ฏธ์ง€ ๋ณ€๊ฒฝ" in prompt:
116
+ desc_match = re.search(r'#1์„ "(.*?)"์œผ๋กœ ๋ฐ”๊ฟ”๋ผ', prompt)
117
+ if desc_match:
118
+ description = desc_match.group(1)
119
+ prompt = f"์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€๋ฅผ {description}์œผ๋กœ ๋ณ€๊ฒฝํ•ด์ฃผ์„ธ์š”. ์›๋ณธ ์ด๋ฏธ์ง€์˜ ์ฃผ์š” ๋‚ด์šฉ์€ ์œ ์ง€ํ•˜๋˜ ์ƒˆ๋กœ์šด ์Šคํƒ€์ผ๊ณผ ๋ถ„์œ„๊ธฐ๋กœ ์žฌํ•ด์„ํ•ด์ฃผ์„ธ์š”."
120
+ else:
121
+ prompt = "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€๋ฅผ ์ฐฝ์˜์ ์œผ๋กœ ๋ณ€ํ˜•ํ•ด์ฃผ์„ธ์š”. ๋” ์ƒ์ƒํ•˜๊ณ  ์˜ˆ์ˆ ์ ์ธ ๋ฒ„์ „์œผ๋กœ ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”."
122
+
123
+ elif "2. ๊ธ€์ž์ง€์šฐ๊ธฐ" in prompt:
124
+ text_match = re.search(r'#1์—์„œ "(.*?)"๋ฅผ ์ง€์›Œ๋ผ', prompt)
125
+ if text_match:
126
+ text_to_remove = text_match.group(1)
127
+ prompt = f"์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์—์„œ '{text_to_remove}' ํ…์ŠคํŠธ๋ฅผ ์ฐพ์•„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ œ๊ฑฐํ•ด์ฃผ์„ธ์š”. ํ…์ŠคํŠธ๊ฐ€ ์žˆ๋˜ ๋ถ€๋ถ„์„ ๋ฐฐ๊ฒฝ๊ณผ ์กฐํ™”๋กญ๊ฒŒ ์ฑ„์›Œ์ฃผ์„ธ์š”."
128
+ else:
129
+ prompt = "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์—์„œ ๋ชจ๋“  ํ…์ŠคํŠธ๋ฅผ ์ฐพ์•„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ œ๊ฑฐํ•ด์ฃผ์„ธ์š”. ๊น”๋”ํ•œ ์ด๋ฏธ์ง€๋กœ ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”."
130
+
131
+ elif "4. ์˜ท๋ฐ”๊พธ๊ธฐ" in prompt:
132
+ prompt = "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์˜ ์ธ๋ฌผ ์˜์ƒ์„ ๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์˜ ์˜์ƒ์œผ๋กœ ๋ณ€๊ฒฝํ•ด์ฃผ์„ธ์š”. ์˜์ƒ์˜ ์Šคํƒ€์ผ๊ณผ ์ƒ‰์ƒ์€ ๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€๋ฅผ ๋”ฐ๋ฅด๋˜, ์‹ ์ฒด ๋น„์œจ๊ณผ ํฌ์ฆˆ๋Š” ์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€๋ฅผ ์œ ์ง€ํ•ด์ฃผ์„ธ์š”."
133
+
134
+ elif "5. ๋ฐฐ๊ฒฝ๋ฐ”๊พธ๊ธฐ" in prompt:
135
+ prompt = "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์˜ ๋ฐฐ๊ฒฝ์„ ๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์˜ ๋ฐฐ๊ฒฝ์œผ๋กœ ๋ณ€๊ฒฝํ•ด์ฃผ์„ธ์š”. ์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์˜ ์ฃผ์š” ํ”ผ์‚ฌ์ฒด๋Š” ์œ ์ง€ํ•˜๊ณ , ๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์˜ ๋ฐฐ๊ฒฝ๊ณผ ์กฐํ™”๋กญ๊ฒŒ ํ•ฉ์„ฑํ•ด์ฃผ์„ธ์š”."
136
+
137
+ elif "6. ์ด๋ฏธ์ง€ ํ•ฉ์„ฑ(์ƒํ’ˆํฌํ•จ)" in prompt:
138
+ prompt = "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์™€ ๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€(๋˜๋Š” ์„ธ ๋ฒˆ์งธ ์ด๋ฏธ์ง€)๋ฅผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํ•ฉ์„ฑํ•ด์ฃผ์„ธ์š”. ๋ชจ๋“  ์ด๋ฏธ์ง€์˜ ์ฃผ์š” ์š”์†Œ๋ฅผ ํฌํ•จํ•˜๊ณ , ํŠนํžˆ ์ƒํ’ˆ์ด ๋‹๋ณด์ด๋„๋ก ์กฐํ™”๋กญ๊ฒŒ ํ†ตํ•ฉํ•ด์ฃผ์„ธ์š”."
139
+
140
+ prompt += " ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”. ์ด๋ฏธ์ง€์— ํ…์ŠคํŠธ๋‚˜ ๊ธ€์ž๋ฅผ ํฌํ•จํ•˜์ง€ ๋งˆ์„ธ์š”."
141
+ return prompt
142
+
143
+ def generate_with_images(prompt, images, variation_index=0, api_key=None):
144
+ # API ํ‚ค ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€
145
+ try:
146
+ # ์‚ฌ์šฉ์ž ์ž…๋ ฅ API ํ‚ค ๋˜๋Š” ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์‚ฌ์šฉ
147
+ used_api_key = api_key if api_key else os.environ.get("GEMINI_API_KEY")
148
+ if not used_api_key:
149
+ return None, "API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. API ํ‚ค๋ฅผ ์ž…๋ ฅํ•˜๊ฑฐ๋‚˜ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”."
150
+
151
+ client = genai.Client(api_key=used_api_key)
152
+ logger.info(f"Gemini API ์š”์ฒญ ์‹œ์ž‘ - ํ”„๋กฌํ”„ํŠธ: {prompt}, ๋ณ€ํ˜• ์ธ๋ฑ์Šค: {variation_index}")
153
+
154
+ variation_suffixes = [
155
+ " Create this as the first variation. Do not add any text, watermarks, or labels to the image.",
156
+ " Create this as the second variation with more vivid colors. Do not add any text, watermarks, or labels to the image.",
157
+ " Create this as the third variation with a more creative style. Do not add any text, watermarks, or labels to the image.",
158
+ " Create this as the fourth variation with enhanced details. Do not add any text, watermarks, or labels to the image."
159
+ ]
160
+
161
+ if variation_index < len(variation_suffixes):
162
+ prompt = prompt + variation_suffixes[variation_index]
163
+ else:
164
+ prompt = prompt + " Do not add any text, watermarks, or labels to the image."
165
+
166
+ contents = [prompt]
167
+ for idx, img in enumerate(images, 1):
168
+ if img is not None:
169
+ contents.append(img)
170
+ logger.info(f"์ด๋ฏธ์ง€ #{idx} ์ถ”๊ฐ€๋จ")
171
+
172
+ response = client.models.generate_content(
173
+ model="gemini-2.0-flash-exp-image-generation",
174
+ contents=contents,
175
+ config=types.GenerateContentConfig(
176
+ response_modalities=['Text', 'Image'],
177
+ temperature=1,
178
+ top_p=0.95,
179
+ top_k=40,
180
+ max_output_tokens=8192
181
+ )
182
+ )
183
+
184
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
185
+ temp_path = tmp.name
186
+ result_text = ""
187
+ image_found = False
188
+ for part in response.candidates[0].content.parts:
189
+ if hasattr(part, 'text') and part.text:
190
+ result_text += part.text
191
+ logger.info(f"์‘๋‹ต ํ…์ŠคํŠธ: {part.text}")
192
+ elif hasattr(part, 'inline_data') and part.inline_data:
193
+ save_binary_file(temp_path, part.inline_data.data)
194
+ image_found = True
195
+ logger.info("์‘๋‹ต์—์„œ ์ด๋ฏธ์ง€ ์ถ”์ถœ ์„ฑ๊ณต")
196
+ if not image_found:
197
+ return None, f"API์—์„œ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. ์‘๋‹ต ํ…์ŠคํŠธ: {result_text}"
198
+ result_img = Image.open(temp_path)
199
+ if result_img.mode == "RGBA":
200
+ result_img = result_img.convert("RGB")
201
+ return result_img, f"์ด๋ฏธ์ง€๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. {result_text}"
202
+ except Exception as e:
203
+ logger.exception("์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:")
204
+ return None, f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
205
+
206
+ def process_images_with_prompt(image1, image2, image3, prompt, variation_index=0, max_retries=3, api_key=None):
207
+ # API ํ‚ค ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€
208
+ retry_count = 0
209
+ last_error = None
210
+
211
+ while retry_count < max_retries:
212
+ try:
213
+ images = [image1, image2, image3]
214
+ valid_images = [img for img in images if img is not None]
215
+ if not valid_images:
216
+ return None, "์ ์–ด๋„ ํ•˜๋‚˜์˜ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”.", ""
217
+
218
+ if prompt and prompt.strip():
219
+ processed_prompt = preprocess_prompt(prompt, image1, image2, image3)
220
+ if re.search("[๊ฐ€-ํžฃ]", processed_prompt):
221
+ final_prompt = translate_prompt_to_english(processed_prompt, api_key)
222
+ else:
223
+ final_prompt = processed_prompt
224
+ else:
225
+ if len(valid_images) == 1:
226
+ 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."
227
+ logger.info("Default prompt generated for single image")
228
+ elif len(valid_images) == 2:
229
+ 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."
230
+ logger.info("Default prompt generated for two images")
231
+ else:
232
+ 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."
233
+ logger.info("Default prompt generated for three images")
234
+
235
+ result_img, status = generate_with_images(final_prompt, valid_images, variation_index, api_key)
236
+ if result_img is not None:
237
+ return result_img, status, final_prompt
238
+ else:
239
+ last_error = status
240
+ retry_count += 1
241
+ logger.warning(f"์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹คํŒจ, ์žฌ์‹œ๋„ {retry_count}/{max_retries}: {status}")
242
+ time.sleep(1)
243
+ except Exception as e:
244
+ last_error = str(e)
245
+ retry_count += 1
246
+ logger.exception(f"์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ, ์žฌ์‹œ๋„ {retry_count}/{max_retries}:")
247
+ time.sleep(1)
248
+
249
+ return None, f"์ตœ๋Œ€ ์žฌ์‹œ๋„ ํšŸ์ˆ˜({max_retries}ํšŒ) ์ดˆ๊ณผ ํ›„ ์‹คํŒจ: {last_error}", prompt
250
+
251
+ def generate_multiple_images(image1, image2, image3, prompt, api_key=None, progress=gr.Progress()):
252
+ # API ํ‚ค ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€
253
+ results = []
254
+ statuses = []
255
+ prompts = []
256
+
257
+ num_images = 4
258
+ max_retries = 3
259
+
260
+ progress(0, desc="์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค€๋น„ ์ค‘...")
261
+
262
+ for i in range(num_images):
263
+ progress((i / num_images), desc=f"{i+1}/{num_images} ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘...")
264
+ result_img, status, final_prompt = process_images_with_prompt(image1, image2, image3, prompt, i, max_retries, api_key)
265
+
266
+ if result_img is not None:
267
+ results.append(result_img)
268
+ statuses.append(f"์ด๋ฏธ์ง€ #{i+1}: {status}")
269
+ prompts.append(f"์ด๋ฏธ์ง€ #{i+1}: {final_prompt}")
270
+ else:
271
+ results.append(None)
272
+ statuses.append(f"์ด๋ฏธ์ง€ #{i+1} ์ƒ์„ฑ ์‹คํŒจ: {status}")
273
+ prompts.append(f"์ด๋ฏธ์ง€ #{i+1}: {final_prompt}")
274
+
275
+ time.sleep(1)
276
+
277
+ progress(1.0, desc="์ด๋ฏธ์ง€ ์ƒ์„ฑ ์™„๋ฃŒ!")
278
+
279
+ while len(results) < 4:
280
+ results.append(None)
281
+
282
+ combined_status = "\n".join(statuses)
283
+ combined_prompts = "\n".join(prompts)
284
+
285
+ return results[0], results[1], results[2], results[3], combined_status, combined_prompts
286
+
287
+ # ========== ์ด๋ฏธ์ง€ ํŽธ์ง‘๊ธฐ ๊ด€๋ จ ํ•จ์ˆ˜ ==========
288
+ def adjust_brightness(image, value):
289
+ """์ด๋ฏธ์ง€ ๋ฐ๊ธฐ ์กฐ์ ˆ"""
290
+ value = float(value - 1) * 100 # 0-2 ๋ฒ”์œ„๋ฅผ -100์—์„œ +100์œผ๋กœ ๋ณ€ํ™˜
291
+ hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
292
+ h, s, v = cv2.split(hsv)
293
+ v = cv2.add(v, value)
294
+ v = np.clip(v, 0, 255)
295
+ final_hsv = cv2.merge((h, s, v))
296
+ return cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR)
297
+
298
+ def adjust_contrast(image, value):
299
+ """์ด๋ฏธ์ง€ ๋Œ€๋น„ ์กฐ์ ˆ"""
300
+ value = float(value)
301
+ return np.clip(image * value, 0, 255).astype(np.uint8)
302
+
303
+ def adjust_saturation(image, value):
304
+ """์ด๋ฏธ์ง€ ์ฑ„๋„ ์กฐ์ ˆ"""
305
+ value = float(value - 1) * 100 # 0-2 ๋ฒ”์œ„๋ฅผ -100์—์„œ +100์œผ๋กœ ๋ณ€ํ™˜
306
+ hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
307
+ h, s, v = cv2.split(hsv)
308
+ s = cv2.add(s, value)
309
+ s = np.clip(s, 0, 255)
310
+ final_hsv = cv2.merge((h, s, v))
311
+ return cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR)
312
+
313
+ def adjust_temperature(image, value):
314
+ """์ด๋ฏธ์ง€ ์ƒ‰์˜จ๋„ ์กฐ์ ˆ (์ƒ‰์ƒ ๋ฐธ๋Ÿฐ์Šค)"""
315
+ value = float(value) * 30 # ํšจ๊ณผ ์Šค์ผ€์ผ ์กฐ์ ˆ
316
+ b, g, r = cv2.split(image)
317
+ if value > 0: # ๋”ฐ๋œปํ•˜๊ฒŒ
318
+ r = cv2.add(r, value)
319
+ b = cv2.subtract(b, value)
320
+ else: # ์ฐจ๊ฐ‘๊ฒŒ
321
+ r = cv2.add(r, value)
322
+ b = cv2.subtract(b, value)
323
+
324
+ r = np.clip(r, 0, 255)
325
+ b = np.clip(b, 0, 255)
326
+ return cv2.merge([b, g, r])
327
+
328
+ def adjust_tint(image, value):
329
+ """์ด๋ฏธ์ง€ ์ƒ‰์กฐ ์กฐ์ ˆ"""
330
+ hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
331
+ h, s, v = cv2.split(hsv_image)
332
+ h = cv2.add(h, int(value))
333
+ h = np.clip(h, 0, 179) # Hue ๊ฐ’์€ 0-179 ๋ฒ”์œ„
334
+ final_hsv = cv2.merge((h, s, v))
335
+ return cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR)
336
+
337
+ def adjust_exposure(image, value):
338
+ """์ด๋ฏธ์ง€ ๋…ธ์ถœ ์กฐ์ ˆ"""
339
+ enhancer = ImageEnhance.Brightness(Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)))
340
+ img_enhanced = enhancer.enhance(1 + float(value) / 5.0)
341
+ return cv2.cvtColor(np.array(img_enhanced), cv2.COLOR_RGB2BGR)
342
+
343
+ def adjust_vibrance(image, value):
344
+ """์ด๋ฏธ์ง€ ํ™œ๊ธฐ ์กฐ์ ˆ"""
345
+ img = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
346
+ converter = ImageEnhance.Color(img)
347
+ factor = 1 + (float(value) / 100.0)
348
+ img = converter.enhance(factor)
349
+ return cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
350
+
351
+ def adjust_color_mixer_blues(image, value):
352
+ """์ด๋ฏธ์ง€ ์ปฌ๋Ÿฌ ๋ฏน์„œ (๋ธ”๋ฃจ) ์กฐ์ ˆ"""
353
+ b, g, r = cv2.split(image)
354
+ b = cv2.add(b, float(value))
355
+ b = np.clip(b, 0, 255)
356
+ return cv2.merge([b, g, r])
357
+
358
+ def adjust_shadows(image, value):
359
+ """์ด๋ฏธ์ง€ ๊ทธ๋ฆผ์ž ์กฐ์ ˆ"""
360
+ pil_image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
361
+ enhancer = ImageEnhance.Brightness(pil_image)
362
+ factor = 1 + (float(value) / 100.0)
363
+ pil_image = enhancer.enhance(factor)
364
+ return cv2.cvtColor(np.array(pil_image), cv2.COLOR_BGR2RGB)
365
+
366
+ def process_image(image, brightness, contrast, saturation, temperature, tint, exposure, vibrance, color_mixer_blues, shadows):
367
+ """๋ชจ๋“  ์กฐ์ • ์‚ฌํ•ญ์„ ์ด๋ฏธ์ง€์— ์ ์šฉ"""
368
+ if image is None:
369
+ return None
370
+
371
+ # PIL ์ด๋ฏธ์ง€๋ฅผ OpenCV ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜
372
+ image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
373
+
374
+ # ์กฐ์ • ์‚ฌํ•ญ ์ˆœ์ฐจ ์ ์šฉ
375
+ image = adjust_brightness(image, brightness)
376
+ image = adjust_contrast(image, contrast)
377
+ image = adjust_saturation(image, saturation)
378
+ image = adjust_temperature(image, temperature)
379
+ image = adjust_tint(image, tint)
380
+ image = adjust_exposure(image, exposure)
381
+ image = adjust_vibrance(image, vibrance)
382
+ image = adjust_color_mixer_blues(image, color_mixer_blues)
383
+ image = adjust_shadows(image, shadows)
384
+
385
+ # PIL ์ด๋ฏธ์ง€๋กœ ๋‹ค์‹œ ๋ณ€ํ™˜
386
+ return Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
387
+
388
+ def download_edited_image(image, input_image_name):
389
+ """์ด๋ฏธ์ง€๋ฅผ JPG ํ˜•์‹์œผ๋กœ ์ €์žฅํ•˜๊ณ  ๊ฒฝ๋กœ ๋ฐ˜ํ™˜"""
390
+ if image is None:
391
+ return None
392
+
393
+ # ํ•œ๊ตญ ์‹œ๊ฐ„ ํƒ€์ž„์Šคํƒฌํ”„ ์ƒ์„ฑ
394
+ def get_korean_timestamp():
395
+ korea_time = datetime.utcnow() + timedelta(hours=9)
396
+ return korea_time.strftime('%Y%m%d_%H%M%S')
397
+
398
+ timestamp = get_korean_timestamp()
399
+ if input_image_name and hasattr(input_image_name, 'name'):
400
+ base_name = input_image_name.name.split('.')[0] # ํŒŒ์ผ ๊ฐ์ฒด์—์„œ ์ด๋ฆ„ ์ถ”์ถœ
401
+ else:
402
+ base_name = "์ด๋ฏธ์ง€"
403
+
404
+ file_name = f"[๋์žฅAI]๋์žฅํ•„ํ„ฐ_{base_name}_{timestamp}.jpg"
405
+
406
+ # ํŒŒ์ผ ์ €์žฅ
407
+ temp_file_path = tempfile.gettempdir() + "/" + file_name
408
+ image.save(temp_file_path, format="JPEG")
409
+ return temp_file_path
410
+
411
+ # ์ปค์Šคํ…€ CSS ์Šคํƒ€์ผ - ์ƒˆ๋กœ์šด ์Šคํƒ€์ผ ์ ์šฉ
412
+ custom_css = """
413
+ :root {
414
+ --primary-color: #FB7F0D;
415
+ --secondary-color: #ff9a8b;
416
+ --accent-color: #FF6B6B;
417
+ --background-color: #FFF3E9;
418
+ --card-bg: #ffffff;
419
+ --text-color: #334155;
420
+ --border-radius: 18px;
421
+ --shadow: 0 8px 30px rgba(251, 127, 13, 0.08);
422
+ }
423
+
424
+ body {
425
+ font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
426
+ background-color: var(--background-color);
427
+ color: var(--text-color);
428
+ line-height: 1.6;
429
+ }
430
+
431
+ /* Gradio ์ปจํ…Œ์ด๋„ˆ ์˜ค๋ฒ„๋ผ์ด๋“œ */
432
+ .gradio-container {
433
+ max-width: 100% !important; /* 200%์—์„œ 100%๋กœ ๋ณ€๊ฒฝ */
434
+ width: 100% !important; /* ์ถ”๊ฐ€: ๋„ˆ๋น„ 100% ์ง€์ • */
435
+ margin: 0 auto !important;
436
+ padding: 0 !important;
437
+ background-color: var(--background-color) !important;
438
+ box-sizing: border-box !important; /* ์ถ”๊ฐ€: ํŒจ๋”ฉ์ด ๋„ˆ๋น„์— ํฌํ•จ๋˜๋„๋ก ์„ค์ • */
439
+ }
440
+
441
+ /* ์ถ”๊ฐ€: ๋‚ด๋ถ€ ์ปจํ…Œ์ด๋„ˆ๋„ 100% ๋„ˆ๋น„๋กœ ์„ค์ • */
442
+ .contain {
443
+ max-width: 100% !important;
444
+ width: 100% !important;
445
+ }
446
+
447
+ /* ์ถ”๊ฐ€: ๊ฐ ํ–‰(Row)๋„ 100% ๋„ˆ๋น„๋กœ ์„ค์ • */
448
+ .gr-padded {
449
+ padding: 0 !important;
450
+ width: 100% !important;
451
+ max-width: 100% !important;
452
+ }
453
+
454
+ /* ์ƒ๋‹จ ํ—ค๋” */
455
+ .app-header {
456
+ background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
457
+ color: white;
458
+ padding: 2rem;
459
+ border-radius: var(--border-radius);
460
+ margin-bottom: 1.5rem;
461
+ box-shadow: var(--shadow);
462
+ text-align: center;
463
+ }
464
+
465
+ .app-header h1 {
466
+ margin: 0;
467
+ font-size: 2.5rem;
468
+ font-weight: 700;
469
+ letter-spacing: -0.5px;
470
+ }
471
+
472
+ .app-header p {
473
+ margin: 0.75rem 0 0;
474
+ font-size: 1.1rem;
475
+ opacity: 0.9;
476
+ }
477
+
478
+ /* ํŒจ๋„ ์Šคํƒ€์ผ๋ง */
479
+ .gr-group {
480
+ background-color: var(--card-bg);
481
+ border-radius: var(--border-radius) !important;
482
+ box-shadow: var(--shadow) !important;
483
+ padding: 1.5rem !important;
484
+ margin-bottom: 1.5rem !important;
485
+ border: 1px solid rgba(0, 0, 0, 0.04) !important;
486
+ transition: transform 0.3s ease;
487
+ }
488
+
489
+ .gr-group:hover {
490
+ transform: translateY(-5px);
491
+ }
492
+
493
+ /* ์„น์…˜ ์ œ๋ชฉ */
494
+ .section-title {
495
+ font-size: 22px !important;
496
+ font-weight: 700 !important;
497
+ color: #333333 !important;
498
+ margin-bottom: 1rem !important;
499
+ padding-bottom: 0.5rem !important;
500
+ border-bottom: 2px solid var(--primary-color) !important;
501
+ display: flex;
502
+ align-items: center;
503
+ }
504
+
505
+ .section-title span {
506
+ color: var(--primary-color);
507
+ }
508
+
509
+ /* ๋ฒ„ํŠผ ์Šคํƒ€์ผ๋ง */
510
+ .custom-button {
511
+ background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important;
512
+ color: white !important;
513
+ font-weight: 600 !important;
514
+ border: none !important;
515
+ border-radius: 30px !important;
516
+ padding: 12px 24px !important;
517
+ box-shadow: 0 4px 8px rgba(251, 127, 13, 0.25) !important;
518
+ transition: all 0.3s ease !important;
519
+ text-transform: none !important;
520
+ display: flex !important;
521
+ align-items: center !important;
522
+ justify-content: center !important;
523
+ }
524
+
525
+ .custom-button:hover {
526
+ transform: translateY(-2px) !important;
527
+ box-shadow: 0 6px 12px rgba(251, 127, 13, 0.3) !important;
528
+ }
529
+
530
+ .custom-button.primary {
531
+ background: linear-gradient(135deg, var(--accent-color), #ff9a8b) !important;
532
+ }
533
+
534
+ /* ์ด๋ฏธ์ง€ ์ปจํ…Œ์ด๋„ˆ */
535
+ .image-container {
536
+ border-radius: var(--border-radius);
537
+ overflow: hidden;
538
+ border: 1px solid rgba(0, 0, 0, 0.08);
539
+ transition: all 0.3s ease;
540
+ background-color: white;
541
+ }
542
+
543
+ .image-container:hover {
544
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
545
+ }
546
+
547
+ /* ํƒญ ์Šคํƒ€์ผ ๊ฐœ์„  */
548
+ .tabs {
549
+ border-bottom: none !important;
550
+ }
551
+
552
+ .tab-nav {
553
+ background-color: transparent !important;
554
+ border-bottom: 1px solid #eeeeee !important;
555
+ padding: 0 !important;
556
+ }
557
+
558
+ .tab-nav button {
559
+ border-radius: var(--border-radius) var(--border-radius) 0 0 !important;
560
+ margin-right: 5px !important;
561
+ padding: 12px 20px !important;
562
+ font-size: 18px !important;
563
+ font-weight: 600 !important;
564
+ border: 1px solid #eeeeee !important;
565
+ border-bottom: none !important;
566
+ background-color: rgba(255, 255, 255, 0.7) !important;
567
+ color: var(--text-color) !important;
568
+ transition: all 0.3s ease !important;
569
+ min-width: 150px !important;
570
+ text-align: center !important;
571
+ }
572
+
573
+ .tab-nav button.selected {
574
+ background-color: var(--primary-color) !important;
575
+ color: white !important;
576
+ border-color: var(--primary-color) !important;
577
+ box-shadow: 0 -2px 6px rgba(251, 127, 13, 0.2) !important;
578
+ }
579
+
580
+ .tab-nav button:hover:not(.selected) {
581
+ background-color: var(--background-color) !important;
582
+ border-bottom: none !important;
583
+ }
584
+
585
+ /* ์ž…๋ ฅ ํ•„๋“œ ์Šคํƒ€์ผ */
586
+ .gr-input, .gr-text-input, .gr-sample-inputs {
587
+ border-radius: var(--border-radius) !important;
588
+ border: 1px solid #dddddd !important;
589
+ padding: 12px !important;
590
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05) !important;
591
+ transition: all 0.3s ease !important;
592
+ }
593
+
594
+ .gr-input:focus, .gr-text-input:focus {
595
+ border-color: var(--primary-color) !important;
596
+ outline: none !important;
597
+ box-shadow: 0 0 0 2px rgba(251, 127, 13, 0.2) !important;
598
+ }
599
+
600
+ /* ๋ฒ„ํŠผ ๊ทธ๋ฃน */
601
+ .button-grid {
602
+ display: grid;
603
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
604
+ gap: 0.8rem;
605
+ margin-bottom: 1.2rem;
606
+ }
607
+
608
+ /* ์‚ฌ์šฉ์ž ๋งค๋‰ด์–ผ */
609
+ .user-manual {
610
+ background-color: white;
611
+ padding: 2rem;
612
+ border-radius: var(--border-radius);
613
+ box-shadow: var(--shadow);
614
+ margin-top: 2rem;
615
+ }
616
+
617
+ .manual-title {
618
+ font-size: 1.8rem;
619
+ font-weight: 700;
620
+ color: var(--primary-color);
621
+ margin-bottom: 1.5rem;
622
+ text-align: center;
623
+ display: flex;
624
+ align-items: center;
625
+ justify-content: center;
626
+ }
627
+
628
+ .manual-title i {
629
+ margin-right: 0.5rem;
630
+ font-size: 1.8rem;
631
+ }
632
+
633
+ .manual-section {
634
+ margin-bottom: 1.5rem;
635
+ padding: 1.2rem;
636
+ background-color: #f8faff;
637
+ border-radius: calc(var(--border-radius) - 5px);
638
+ }
639
+
640
+ .manual-section-title {
641
+ font-size: 1.3rem;
642
+ font-weight: 700;
643
+ margin-bottom: 1rem;
644
+ color: var(--primary-color);
645
+ display: flex;
646
+ align-items: center;
647
+ }
648
+
649
+ .manual-section-title i {
650
+ margin-right: 0.5rem;
651
+ font-size: 1.2rem;
652
+ }
653
+
654
+ .manual-text {
655
+ font-size: 1rem;
656
+ line-height: 1.7;
657
+ }
658
+
659
+ .manual-text strong {
660
+ color: var(--accent-color);
661
+ }
662
+
663
+ .tip-box {
664
+ background-color: rgba(255, 107, 107, 0.1);
665
+ border-left: 3px solid var(--accent-color);
666
+ padding: 1rem 1.2rem;
667
+ margin: 1rem 0;
668
+ border-radius: 8px;
669
+ }
670
+
671
+ /* ๋กœ๋”ฉ ์• ๋‹ˆ๋ฉ”์ด์…˜ */
672
+ .progress-container {
673
+ background-color: rgba(255, 255, 255, 0.9);
674
+ border-radius: var(--border-radius);
675
+ padding: 2rem;
676
+ box-shadow: var(--shadow);
677
+ text-align: center;
678
+ }
679
+
680
+ .progress-bar {
681
+ height: 8px;
682
+ border-radius: 4px;
683
+ background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
684
+ margin: 1rem 0;
685
+ }
686
+
687
+ /* ๋ฉ”์ธ ์ปจํ…์ธ  ์Šคํฌ๋กค๋ฐ” */
688
+ ::-webkit-scrollbar {
689
+ width: 8px;
690
+ height: 8px;
691
+ }
692
+
693
+ ::-webkit-scrollbar-track {
694
+ background: rgba(0, 0, 0, 0.05);
695
+ border-radius: 10px;
696
+ }
697
+
698
+ ::-webkit-scrollbar-thumb {
699
+ background: var(--primary-color);
700
+ border-radius: 10px;
701
+ }
702
+
703
+ /* ํŽธ์ง‘๊ธฐ ํŠน๋ณ„ ์Šคํƒ€์ผ */
704
+ .editor-section {
705
+ background-color: var(--card-bg);
706
+ border-radius: var(--border-radius);
707
+ box-shadow: var(--shadow);
708
+ padding: 1.5rem;
709
+ margin-bottom: 1.5rem;
710
+ }
711
+
712
+ .editor-title {
713
+ font-size: 1.5rem;
714
+ font-weight: 700;
715
+ color: var(--primary-color);
716
+ margin-bottom: 1rem;
717
+ display: flex;
718
+ align-items: center;
719
+ }
720
+
721
+ .editor-title i {
722
+ margin-right: 0.5rem;
723
+ }
724
+
725
+ .download-button {
726
+ background-color: var(--accent-color) !important;
727
+ color: white !important;
728
+ border: none !important;
729
+ padding: 10px !important;
730
+ font-size: 12px !important;
731
+ border-radius: var(--border-radius) !important;
732
+ font-weight: 600 !important;
733
+ transition: all 0.3s ease !important;
734
+ }
735
+
736
+ .download-button:hover {
737
+ transform: translateY(-2px) !important;
738
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15) !important;
739
+ }
740
+
741
+ .download-container {
742
+ display: flex;
743
+ flex-direction: column;
744
+ align-items: center;
745
+ width: 100%;
746
+ }
747
+
748
+ .download-output {
749
+ width: 100%;
750
+ margin-top: 1rem;
751
+ }
752
+
753
+ /* ์• ๋‹ˆ๋ฉ”์ด์…˜ ์Šคํƒ€์ผ */
754
+ @keyframes fadeIn {
755
+ from { opacity: 0; transform: translateY(10px); }
756
+ to { opacity: 1; transform: translateY(0); }
757
+ }
758
+
759
+ .fade-in {
760
+ animation: fadeIn 0.5s ease-out;
761
+ }
762
+
763
+ /* ์‚ฌ์šฉ ๊ฐ€์ด๋“œ ์Šคํƒ€์ผ */
764
+ .guide-container {
765
+ background-color: var(--card-bg);
766
+ border-radius: var(--border-radius);
767
+ box-shadow: var(--shadow);
768
+ padding: 1.5rem;
769
+ margin-bottom: 1.5rem;
770
+ border: 1px solid rgba(0, 0, 0, 0.04);
771
+ }
772
+
773
+ .guide-title {
774
+ font-size: 1.5rem;
775
+ font-weight: 700;
776
+ color: var(--primary-color);
777
+ margin-bottom: 1.5rem;
778
+ padding-bottom: 0.5rem;
779
+ border-bottom: 2px solid var(--primary-color);
780
+ display: flex;
781
+ align-items: center;
782
+ }
783
+
784
+ .guide-title i {
785
+ margin-right: 0.8rem;
786
+ font-size: 1.5rem;
787
+ }
788
+
789
+ .guide-item {
790
+ display: flex;
791
+ margin-bottom: 1rem;
792
+ align-items: flex-start;
793
+ }
794
+
795
+ .guide-number {
796
+ background-color: var(--primary-color);
797
+ color: white;
798
+ width: 25px;
799
+ height: 25px;
800
+ border-radius: 50%;
801
+ display: flex;
802
+ align-items: center;
803
+ justify-content: center;
804
+ font-weight: bold;
805
+ margin-right: 10px;
806
+ flex-shrink: 0;
807
+ }
808
+
809
+ .guide-text {
810
+ flex: 1;
811
+ line-height: 1.6;
812
+ }
813
+
814
+ .guide-text a {
815
+ color: var(--primary-color);
816
+ text-decoration: underline;
817
+ font-weight: 600;
818
+ }
819
+
820
+ .guide-text a:hover {
821
+ color: var(--accent-color);
822
+ }
823
+
824
+ .guide-highlight {
825
+ background-color: rgba(251, 127, 13, 0.1);
826
+ padding: 2px 5px;
827
+ border-radius: 4px;
828
+ font-weight: 500;
829
+ }
830
+
831
+ /* Examples ์„น์…˜ ์Šคํƒ€์ผ */
832
+ .examples-section {
833
+ display: grid;
834
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
835
+ gap: 1.5rem;
836
+ margin-top: 1rem;
837
+ }
838
+
839
+ .example-item {
840
+ background-color: white;
841
+ border-radius: var(--border-radius);
842
+ overflow: hidden;
843
+ box-shadow: var(--shadow);
844
+ transition: transform 0.3s ease;
845
+ }
846
+
847
+ .example-item:hover {
848
+ transform: translateY(-5px);
849
+ }
850
+
851
+ /* ๋ฐ˜์‘ํ˜• */
852
+ @media (max-width: 768px) {
853
+ .button-grid {
854
+ grid-template-columns: repeat(2, 1fr);
855
+ }
856
+
857
+ .examples-section {
858
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
859
+ }
860
+ }
861
+ """ # CSS ๋ฌธ์ž์—ด ์ข…๋ฃŒ
862
+
863
+ # FontAwesome ์•„์ด์ฝ˜ ํฌํ•จ
864
+ fontawesome_link = """
865
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
866
+ """
867
+
868
+ # ์•ฑ ํ—ค๋” HTML
869
+ header_html = """
870
+ <div class="app-header">
871
+ <h1>โœจ ๋์žฅAI - ์ด์ปค๋จธ์Šค์ „๋ฌธ ์ด๋ฏธ์ง€์ƒ์„ฑ๊ธฐ(Ver 1.3) โœจ</h1>
872
+ <p style="font-size:18px; margin-top:10px;">
873
+ ์ด์ปค๋จธ์Šค์— ์ตœ์ ํ™”๋œ ์ด๋ฏธ์ง€์ƒ์„ฑ ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค. ๋‹ค์–‘ํ•œ ๋ฉ”๋‰ด๋ฅผ ์ œ๊ณตํ•˜์˜ค๋‹ˆ ์—…๋ฌด์— ์ž˜ ํ™œ์šฉํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.
874
+ </p>
875
+ </div>
876
+ """
877
+
878
+ # ์„น์…˜ ๊ฐ„ ์—ฌ๋ฐฑ์šฉ HTML
879
+ section_spacing_html = """
880
+ <div style="margin-bottom: 2rem;"></div>
881
+ """
882
+
883
+ # ์ด๋ฏธ์ง€ ์ƒ์„ฑ๊ธฐ ์‚ฌ์šฉ ๊ฐ€์ด๋“œ HTML
884
+ image_generator_guide_html = """
885
+ <div class="guide-container">
886
+ <div class="guide-title"><i class="fas fa-book"></i> ์ด๋ฏธ์ง€ ์ƒ์„ฑ๊ธฐ ์‚ฌ์šฉ ๊ฐ€์ด๋“œ</div>
887
+ <div class="guide-item">
888
+ <div class="guide-number">1</div>
889
+ <div class="guide-text">
890
+ <strong class="guide-highlight">Gemini API ํ‚ค๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.</strong> ๋ฌด๋ฃŒ์ด๋‹ˆ <a href="https://aistudio.google.com/apikey" target="_blank">Google AI Studio</a>๋ฅผ ํ†ตํ•ด ๋ฐœ๊ธ‰๋ฐ›์•„ ์‚ฌ์šฉํ•ด์ฃผ์„ธ์š”.
891
+ </div>
892
+ </div>
893
+ <div class="guide-item">
894
+ <div class="guide-number">2</div>
895
+ <div class="guide-text">
896
+ ๊ตฌ๊ธ€ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ชจ๋ธ์˜ ํŠน์„ฑ์ƒ ์›๋ณธ ์œ ์ง€๊ฐ€ ์ž˜๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์˜ˆ์‹œ์™€ ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์—ฌ๋Ÿฌ๋ฒˆ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”.
897
+ </div>
898
+ </div>
899
+ <div class="guide-item">
900
+ <div class="guide-number">3</div>
901
+ <div class="guide-text">
902
+ ์ž์„ธํ•œ ์‚ฌ์šฉ๋ฒ•์€ <a href="http://xn--ai-7i4i628e.com/๋งํฌ" target="_blank">ํ™œ์šฉ๋ฒ• ๋ณด๋Ÿฌ๊ฐ€๊ธฐ</a>์—์„œ ํ™•์ธํ•ด์ฃผ์„ธ์š”.
903
+ </div>
904
+ </div>
905
+ </div>
906
+ """
907
+
908
+ # ์ด๋ฏธ์ง€ ํŽธ์ง‘๊ธฐ ์‚ฌ์šฉ ๊ฐ€์ด๋“œ HTML
909
+ image_editor_guide_html = """
910
+ <div class="guide-container">
911
+ <div class="guide-title"><i class="fas fa-book"></i> ์ด๋ฏธ์ง€ ํŽธ์ง‘๊ธฐ ์‚ฌ์šฉ ๊ฐ€์ด๋“œ</div>
912
+ <div class="guide-item">
913
+ <div class="guide-number">1</div>
914
+ <div class="guide-text">
915
+ ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€๋ฅผ ์ถ”๊ฐ€์ ์œผ๋กœ ๋‹ค์–‘ํ•œ ํ•„ํ„ฐ๋ฅผ ์ ์šฉํ•˜์—ฌ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํŽธ์ง‘์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
916
+ </div>
917
+ </div>
918
+ <div class="guide-item">
919
+ <div class="guide-number">2</div>
920
+ <div class="guide-text">
921
+ ์ž์„ธํ•œ ์‚ฌ์šฉ๋ฒ•์€ <a href="http://xn--ai-7i4i628e.com/๋งํฌ" target="_blank">ํ™œ์šฉ๋ฒ• ๋ณด๋Ÿฌ๊ฐ€๊ธฐ</a>์—์„œ ํ™•์ธํ•ด์ฃผ์„ธ์š”.
922
+ </div>
923
+ </div>
924
+ </div>
925
+ """
926
+
927
+ # UI ๊ตฌ์„ฑ
928
+ with gr.Blocks(css=custom_css, theme=gr.themes.Default(
929
+ primary_hue="orange",
930
+ secondary_hue="orange",
931
+ font=[gr.themes.GoogleFont("Noto Sans KR"), "ui-sans-serif", "system-ui"]
932
+ )) as demo:
933
+ gr.HTML(fontawesome_link)
934
+ gr.HTML(header_html)
935
+
936
+ with gr.Tabs(elem_classes="tabs") as tabs:
937
+ # ์ด๋ฏธ์ง€ ์ƒ์„ฑ๊ธฐ ํƒญ
938
+ with gr.Tab("โœจ ์ด๋ฏธ์ง€ ์ƒ์„ฑ๊ธฐ", elem_classes="tab-content"):
939
+ # ์‚ฌ์šฉ ๊ฐ€์ด๋“œ ์„น์…˜ ์ถ”๊ฐ€
940
+ gr.HTML(image_generator_guide_html)
941
+ with gr.Row(equal_height=True):
942
+ with gr.Column(scale=1):
943
+ # ======== API ํ‚ค ์„ค์ • ์„น์…˜ ========
944
+ with gr.Group():
945
+ gr.HTML('<div class="section-title"><i class="fas fa-key"></i> <span>API ํ‚ค ์„ค์ •</span></div>')
946
+ api_key_input = gr.Textbox(
947
+ type="password",
948
+ label="Gemini API ํ‚ค (์„ ํƒ์‚ฌํ•ญ)",
949
+ placeholder="API ํ‚ค๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”",
950
+ elem_classes="gr-text-input"
951
+ )
952
+
953
+ # ======== ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๋ฐ ์„ค์ • ์„น์…˜ ========
954
+ with gr.Group():
955
+ gr.HTML('<div class="section-title"><i class="fas fa-upload"></i> <span>์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๋ฐ ์„ค์ •</span></div>')
956
+ with gr.Row():
957
+ image1_input = gr.Image(type="pil", label="#1", image_mode="RGB", elem_classes="image-container", height=400)
958
+ image2_input = gr.Image(type="pil", label="#2", image_mode="RGB", elem_classes="image-container", height=400)
959
+ image3_input = gr.Image(type="pil", label="#3", image_mode="RGB", elem_classes="image-container", height=400)
960
+
961
+ # ํ”„๋กฌํ”„ํŠธ ์ž…๋ ฅ ํ•„๋“œ ์ถ”๊ฐ€
962
+ prompt_input = gr.Textbox(
963
+ lines=3,
964
+ placeholder="ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž…๋ ฅํ•˜๊ฑฐ๋‚˜ ๋น„์›Œ๋‘๋ฉด ์ž๋™ ํ•ฉ์„ฑ๋ฉ๋‹ˆ๋‹ค. '#1', '#2', '#3'์œผ๋กœ ๊ฐ ์ด๋ฏธ์ง€๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
965
+ label="ํ”„๋กฌํ”„ํŠธ (์„ ํƒ ์‚ฌํ•ญ)",
966
+ elem_classes="gr-text-input"
967
+ )
968
+
969
+ # ======== ๋ณ€ํ™˜ ์˜ต์…˜ ์„น์…˜ ========
970
+ with gr.Group():
971
+ gr.HTML('<div class="section-title"><i class="fas fa-sliders-h"></i> <span>ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ</span></div>')
972
+ with gr.Column(elem_classes="button-grid"):
973
+ image_change_btn1 = gr.Button('๐Ÿ”„ ๋ถ€๋ถ„๋ณ€๊ฒฝ-1', elem_classes="custom-button")
974
+ image_change_btn2 = gr.Button('๐Ÿ”„ ๋ถ€๋ถ„๋ณ€๊ฒฝ-2', elem_classes="custom-button")
975
+ image_change_btn3= gr.Button('๐Ÿ”„ ๋ถ€๋ถ„๋ณ€๊ฒฝ-3', elem_classes="custom-button")
976
+ text_remove_btn = gr.Button('๐Ÿงน ๊ธ€์ž์ง€์šฐ๊ธฐ', elem_classes="custom-button")
977
+ text_change_btn = gr.Button('๐Ÿ”ค ๊ธ€์ž๋ณ€๊ฒฝ', elem_classes="custom-button")
978
+ clothes_change_btn1 = gr.Button('๐Ÿ‘• ์ƒํ’ˆ์ฐฉ์šฉ-1', elem_classes="custom-button")
979
+ clothes_change_btn2 = gr.Button('๐Ÿ‘“ ์ƒํ’ˆ์ฐฉ์šฉ-2', elem_classes="custom-button")
980
+ holding_product_btn = gr.Button('๐Ÿท ์ƒํ’ˆ๋“ค๊ณ ', elem_classes="custom-button")
981
+ background_change_btn = gr.Button('๐Ÿ–ผ๏ธ ๋ฐฐ๊ฒฝ๋ฐ”๊พธ๊ธฐ', elem_classes="custom-button")
982
+ composite_product_btn = gr.Button('โœ‚๏ธ ๋ถ€๋ถ„์ง€์šฐ๊ธฐ', elem_classes="custom-button")
983
+ outpainting_btn = gr.Button('๐Ÿ” ์ด๋ฏธ์ง€ํ™•์žฅ', elem_classes="custom-button")
984
+ food_btn_1 = gr.Button('๐Ÿฝ๏ธ ํ”Œ๋ ˆ์ดํŒ…-1', elem_classes="custom-button")
985
+ food_btn_2 = gr.Button('๐Ÿฝ๏ธ ํ”Œ๋ ˆ์ดํŒ…-2', elem_classes="custom-button")
986
+ food_btn_3 = gr.Button('๐Ÿฝ๏ธ ํ”Œ๋ ˆ์ดํŒ…-3', elem_classes="custom-button")
987
+
988
+ # ======== ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์„น์…˜ ========
989
+ with gr.Group():
990
+ gr.HTML('<div class="section-title"><i class="fas fa-image"></i> <span>์ด๋ฏธ์ง€ ์ƒ์„ฑ</span></div>')
991
+ submit_single_btn = gr.Button('โœจ ์ด๋ฏธ์ง€ ์ƒ์„ฑ (1์žฅ)', elem_classes="custom-button primary")
992
+ submit_btn = gr.Button('โœจ ์ด๋ฏธ์ง€ ์ƒ์„ฑ (4์žฅ)', elem_classes="custom-button primary")
993
+
994
+ with gr.Column(scale=1):
995
+ # ======== ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€ ์„น์…˜ ========
996
+ with gr.Group():
997
+ gr.HTML('<div class="section-title"><i class="fas fa-images"></i> <span>์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€</span></div>')
998
+ with gr.Row():
999
+ with gr.Column():
1000
+ output_image1 = gr.Image(label="์ด๋ฏธ์ง€ #1", elem_classes="image-container", height=400)
1001
+ output_image3 = gr.Image(label="์ด๋ฏธ์ง€ #3", elem_classes="image-container", height=400)
1002
+ with gr.Column():
1003
+ output_image2 = gr.Image(label="์ด๋ฏธ์ง€ #2", elem_classes="image-container", height=400)
1004
+ output_image4 = gr.Image(label="์ด๋ฏธ์ง€ #4", elem_classes="image-container", height=400)
1005
+
1006
+ # ======== ๊ฒฐ๊ณผ ์ •๋ณด ์„น์…˜ ========
1007
+ with gr.Group():
1008
+ gr.HTML('<div class="section-title"><i class="fas fa-info-circle"></i> <span>๊ฒฐ๊ณผ ์ •๋ณด</span></div>')
1009
+ output_text = gr.Textbox(label="์ƒํƒœ ๋ฉ”์‹œ์ง€", lines=2, elem_classes="gr-text-input")
1010
+ prompt_display = gr.Textbox(label="์‚ฌ์šฉ๋œ ํ”„๋กฌํ”„ํŠธ (์˜์–ด)", visible=True, lines=2, elem_classes="gr-text-input")
1011
+
1012
+ # ======== ์˜ˆ์ œ ์ด๋ฏธ์ง€ ์„น์…˜ ========
1013
+ gr.HTML('<div class="section-title"><i class="fas fa-lightbulb"></i> <span>์˜ˆ์ œ ์ด๋ฏธ์ง€</span></div>')
1014
+
1015
+ # ๋ชจ๋“  ์˜ˆ์ œ ํ•œ ํŽ˜์ด์ง€์— ํ‘œ์‹œ
1016
+ examples = [
1017
+ ["down/๋ชจ๋ธ.jpg", None, None, "(#1์˜ ์—ฌ์„ฑ)์ด ์‚ด์ง ๋’ค๋กœ ๋Œ์•„๋ณด๋Š” ๋ชจ์Šต์œผ๋กœ ์ตœ๋Œ€ํ•œ ์ด์ „ seed๋ฅผ ์œ ์ง€ํ•œ์ฒด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ณ€๊ฒฝํ•˜๋ผ."],
1018
+ ["down/์ƒ์–ด๋ ˆ๊ณ ๋ชจํ˜•.png", None, None, "(#1 ๋ ˆ๋ชจ๋ชจํ˜•)์—์„œ ์ฒญ์ƒ‰์ƒ์–ด๋ ˆ๊ณ ๋งŒ ๊ฒ€์€์ƒ‰ ๊ณ ๋ž˜๋ ˆ๊ณ ๋กœ ๋ณ€๊ฒฝํ•˜๊ณ  ๋‚˜๋จธ์ง€ ๋ถ€๋ถ„์€ seed๋ฅผ ๋ณ€๊ฒฝ์„ ํ•˜์ง€๋งˆ๋ผ."],
1019
+ ["down/์–ผ์Œ๊ฐ€๋ฐฉ.png", None, None, "(#1 ์—ฌํ–‰์šฉ ์–ผ์Œ๋ฐ•์Šค)์•ž์— ์–ผ์Œ์ด ๋‹ด๊ธด 3์ž”์˜ ์ฝœ๋ผ๊ฐ€ ๋†“์—ฌ์žˆ๋Š” ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ."],
1020
+ ["down/์ค‘๊ตญ์–ด.png", None, None, "(#1 ์ด๋ฏธ์ง€)์— ์žˆ๋Š” ์ค‘๊ตญ์–ด๋ฅผ ๋ชจ๋‘ ์ œ๊ฑฐํ•˜๋ผ."],
1021
+ ["down/ํ…์ŠคํŠธ.webp", None, None, '(#1์˜ ํ…์ŠคํŠธ)๋ฅผ ์Šคํƒ€์ผ์„ ์œ ์ง€ํ•œ์ฒด ํ…์ŠคํŠธ๋งŒ "Hello"๋กœ ๋ฐ”๊ฟ”๋ผ'],
1022
+ ["down/๋ชจ๋ธ2.png", "down/์„ ๊ธ€๋ผ์Šค.png", "down/์ฒญ๋ฐ”์ง€.png", "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด ์‹ ์ฒด ๋น„์œจ๊ณผ ํฌ์ฆˆ๋Š” ์œ ์ง€ํ•œ ์ฒด (#2์˜ ์„ ๊ธ€๋ผ์Šค)์™€ (#3์˜ ์ฒญ๋ฐ”์ง€)๋ฅผ ์ง์ ‘ ๋ชจ๋ธ์ด ์ฐฉ์šฉํ•œ๊ฒƒ ์ฒ˜๋Ÿผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ."],
1023
+ ["down/๋ชจ๋ธ2.png", "down/์„ ๊ธ€๋ผ์Šค.png", "down/์นดํŽ˜์ „๊ฒฝ.png", "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด (#2์˜ ์„ ๊ธ€๋ผ์Šค)์„ ์ฐฉ์šฉํ•˜๊ณ  (#3์˜ ๋’ท๋ฐฐ๊ฒฝ์˜ ์นดํŽ˜์ „์ฒด๊ฐ€ ๋ณด์ด๋ฉฐ) ์˜์ž์— ์•‰์•„ ์žˆ๋Š” ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ."],
1024
+ ["down/๋ชจ๋ธ2.png", "down/์™€์ธ์ž”.png", None, "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด(#2์˜ ์™€์ธ์ž”)์„ ๋“ค๊ณ  ์žˆ๋Š” ์ž์—ฐ์Šค๋Ÿฌ์šด ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ."],
1025
+ ["down/๋ชจ๋ธ2.png", "down/์นดํŽ˜์ „๊ฒฝ.png", None, "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด (#2 ์นดํŽ˜)์—์„œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์žˆ๋Š” ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ."],
1026
+ ["down/์ƒ์–ด๋ ˆ๊ณ ๋ชจํ˜•.png", None, None, "(#1์˜ ๋ ˆ๊ณ ๋ชจํ˜•)์—์„œ ์ฒญ์ƒ‰์ƒ์–ด๋ ˆ๊ณ ๋ฅผ ์ œ๊ฑฐํ•œ ํ›„, ๊ทธ ์ž๋ฆฌ๋ฅผ ์ฃผ๋ณ€ ๋ฐฐ๊ฒฝ๊ณผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์–ด์šฐ๋Ÿฌ์ง€๋„๋ก ์ฑ„์›Œ์ฃผ์„ธ์š”. ๋‹จ, ์ด๋ฏธ์ง€์˜ ๋‹ค๋ฅธ ๋ถ€๋ถ„์˜ ์ฃผ์š” ์š”์†Œ๋Š” ๋™์ผํ•˜๊ฒŒ ์œ ์ง€ํ•ด์•ผํ•œ๋‹ค."],
1027
+ ["down/์นดํŽ˜์ „๊ฒฝ.png", None, None, "(#1 ์ด๋ฏธ์ง€)๋ฅผ ์›๋ณธ๊ทธ๋Œ€๋กœ ์ค‘์•™์— ๋‘๊ณ  ๋น„์œจ๋กœ ์œ ์ง€ํ•œ ์ฒด ์œ„์•„๋ž˜ ๋ฐ ์ขŒ์šฐ๋กœ ํฌ๊ฒŒ ํ™•์žฅํ•˜๋ผ."],
1028
+ ["down/์ƒ๋Ÿฌ๋“œ.png", None, None, "(#1์…€๋Ÿฌ๋“œ)์— ๋‹ด์€ ์šฉ๊ธฐ๋Š” ๋ฒ„๋ฆฌ๊ณ  ๋„“๊ณ  ํฐ ์˜ˆ์œ ์ ‘์‹œ์— (#1์…€๋Ÿฌ๋“œ)์Œ์‹๋งŒ ๊ฐ€๋“ ์ฑ„์›Œ์„œ ์ƒ์—…์ ์ธ ๊ฐ๋„๋กœ ์–ด์šธ๋ฆฌ๋Š” ์†Œํ’ˆ๊ณผ ํ•จ๊ป˜ ํ”Œ๋ ˆ์ดํŒ… ํ•œ ๋ชจ์Šต์„ ์ด๋ฏธ์ง€๋กœ ์ƒ์„ฑํ•˜๋ผ. "],
1029
+ ["down/์ƒ๋Ÿฌ๋“œ.png", "down/ํ”Œ๋ ˆ์ดํŒ….png", None, "(#2 ํ”Œ๏ฟฝ๏ฟฝ์ดํŒ…ํ•œ ์ด๋ฏธ์ง€)์— ๋‹ด๊ธด ์Œ์‹์„ (#1 ์ƒ๋Ÿฌ๋“œ)๋กœ ๋ฐ”๊พธ๊ณ  ๋‚˜๋จธ์ง€๋Š” ์‹œ๋“œ๋ฅผ ์œ ์ง€ํ•œ ์ฒด ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ."],
1030
+ ["down/์ปต.png", None, None, "(#1์ปต)์— ๋”ธ๊ธฐ, ๋ฐ”๋‹๋ผ, ์ดˆ์ฝ” ์•„์ด์Šคํฌ๋ฆผ์„ ๋‹ด๊ณ  ๊ทธ ์œ„์— ์ดˆ์ฝ” ์‹œ๋Ÿฝ์ด ํ๋ฅด๊ฒŒ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ."]
1031
+ ]
1032
+
1033
+ # ๋ชจ๋“  ์˜ˆ์ œ๋ฅผ ํ•œ ํŽ˜์ด์ง€์— ํ‘œ์‹œํ•˜๋„๋ก ์ˆ˜์ •๋œ ๋ถ€๋ถ„
1034
+ gr.Examples(
1035
+ examples=examples,
1036
+ inputs=[image1_input, image2_input, image3_input, prompt_input],
1037
+ examples_per_page=len(examples) # ๋ชจ๋“  ์˜ˆ์ œ๋ฅผ ํ•œ ํŽ˜์ด์ง€์— ํ‘œ์‹œ
1038
+ )
1039
+
1040
+ # ์ด๋ฏธ์ง€ ํŽธ์ง‘๊ธฐ ํƒญ ์ถ”๊ฐ€
1041
+ with gr.Tab("๐ŸŽจ ์ด๋ฏธ์ง€ ํŽธ์ง‘๊ธฐ", elem_classes="tab-content"):
1042
+ # ์‚ฌ์šฉ ๊ฐ€์ด๋“œ ์„น์…˜ ์ถ”๊ฐ€
1043
+ gr.HTML(image_editor_guide_html)
1044
+
1045
+ with gr.Row():
1046
+ # ์™ผ์ชฝ ์—ด: ๋น„์œจ 1
1047
+ with gr.Column(scale=1):
1048
+ # ======== ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์„น์…˜ ========
1049
+ with gr.Group():
1050
+ gr.HTML('<div class="section-title"><i class="fas fa-upload"></i> <span>์ด๋ฏธ์ง€ ์—…๋กœ๋“œ</span></div>')
1051
+ edit_input_image = gr.Image(type="pil", label="ํŽธ์ง‘ํ•  ์ด๋ฏธ์ง€", elem_classes="image-container")
1052
+
1053
+ # ======== ์ด๋ฏธ์ง€ ์กฐ์ • ์„น์…˜ ========
1054
+ with gr.Group():
1055
+ gr.HTML('<div class="section-title"><i class="fas fa-sliders-h"></i> <span>์ด๋ฏธ์ง€ ์กฐ์ •</span></div>')
1056
+ brightness_slider = gr.Slider(0.0, 2.0, value=1.0, step=0.1, label="๋ฐ๊ธฐ ์กฐ์ ˆ")
1057
+ contrast_slider = gr.Slider(0.5, 1.5, value=1.0, step=0.1, label="๋Œ€๋น„ ์กฐ์ ˆ")
1058
+ saturation_slider = gr.Slider(0.0, 2.0, value=1.0, step=0.1, label="์ฑ„๋„ ์กฐ์ ˆ")
1059
+ temperature_slider = gr.Slider(-1.0, 1.0, value=0.0, step=0.1, label="์ƒ‰์˜จ๋„ ์กฐ์ ˆ")
1060
+ tint_slider = gr.Slider(-100, 100, value=0, step=1, label="์ƒ‰์กฐ ์กฐ์ ˆ")
1061
+ exposure_slider = gr.Slider(-5.0, 5.0, value=0.0, step=0.1, label="๋…ธ์ถœ ์กฐ์ ˆ")
1062
+ vibrance_slider = gr.Slider(-100.0, 100.0, value=0.0, step=1.0, label="ํ™œ๊ธฐ ์กฐ์ ˆ")
1063
+ color_mixer_blues_slider = gr.Slider(-100.0, 100.0, value=0.0, step=1.0, label="์ปฌ๋Ÿฌ ๋ฏน์„œ (๋ธ”๋ฃจ)")
1064
+ shadows_slider = gr.Slider(-100.0, 100.0, value=0.0, step=1.0, label="๊ทธ๋ฆผ์ž ์กฐ์ ˆ")
1065
+
1066
+ # ์˜ค๋ฅธ์ชฝ ์—ด: ๋น„์œจ 1
1067
+ with gr.Column(scale=1):
1068
+ # ======== ํŽธ์ง‘๋œ ์ด๋ฏธ์ง€ ์„น์…˜ ========
1069
+ with gr.Group():
1070
+ gr.HTML('<div class="section-title"><i class="fas fa-images"></i> <span>ํŽธ์ง‘๋œ ์ด๋ฏธ์ง€</span></div>')
1071
+ edit_output_image = gr.Image(type="pil", label="ํŽธ์ง‘๋œ ์ด๋ฏธ์ง€", elem_classes="image-container")
1072
+
1073
+ # ======== ์ €์žฅ ์„น์…˜ ========
1074
+ with gr.Group():
1075
+ gr.HTML('<div class="section-title"><i class="fas fa-download"></i> <span>์ €์žฅ</span></div>')
1076
+ with gr.Row(elem_classes="download-container"):
1077
+ download_button = gr.Button("JPG๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ", elem_classes="download-button")
1078
+ with gr.Row(elem_classes="download-container"):
1079
+ download_output = gr.File(label="JPG ์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ", elem_classes="download-output")
1080
+
1081
+ # ========== ์ด๋ฏธ์ง€ ์ƒ์„ฑ๊ธฐ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ ==========
1082
+ # ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
1083
+ image_change_btn1.click(
1084
+ fn=lambda: "(#1์˜ ์—ฌ์„ฑ)์ด ์‚ด์ง ๋’ค๋กœ ๋Œ์•„๋ณด๋Š” ๋ชจ์Šต์œผ๋กœ ์ตœ๋Œ€ํ•œ ์ด์ „ seed๋ฅผ ์œ ์ง€ํ•œํ…Œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ณ€๊ฒฝํ•˜๋ผ.",
1085
+ inputs=[],
1086
+ outputs=prompt_input
1087
+ )
1088
+ image_change_btn2.click(
1089
+ fn=lambda: "(#1 ๋ ˆ๋ชจ๋ชจํ˜•)์—์„œ ์ฒญ์ƒ‰์ƒ์–ด๋ ˆ๊ณ ๋งŒ ๊ฒ€์€์ƒ‰ ๊ณ ๋ž˜๋ ˆ๊ณ ๋กœ ๋ณ€๊ฒฝํ•˜๊ณ  ๋‚˜๋จธ์ง€ ๋ถ€๋ถ„์€ seed๋ฅผ ๋ณ€๊ฒฝ์„ ํ•˜์ง€๋งˆ๋ผ.",
1090
+ inputs=[],
1091
+ outputs=prompt_input
1092
+ )
1093
+ image_change_btn3.click(
1094
+ fn=lambda: "(#1 ์—ฌํ–‰์šฉ ์–ผ์Œ๋ฐ•์Šค)์•ž์— ์–ผ์Œ์ด ๋‹ด๊ธด 3์ž”์˜ ์ฝœ๋ผ๊ฐ€ ๋†“์—ฌ์žˆ๋Š” ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ.",
1095
+ inputs=[],
1096
+ outputs=prompt_input
1097
+ )
1098
+
1099
+ text_remove_btn.click(
1100
+ fn=lambda: "(#1 ์ด๋ฏธ์ง€)์— ์žˆ๋Š” ์ค‘๊ตญ์–ด๋ฅผ ๋ชจ๋‘ ์ œ๊ฑฐํ•˜๋ผ.",
1101
+ inputs=[],
1102
+ outputs=prompt_input
1103
+ )
1104
+ text_change_btn.click(
1105
+ fn=lambda: '(#1์˜ ํ…์ŠคํŠธ)๋ฅผ ์Šคํƒ€์ผ์„ ์œ ์ง€ํ•œ์ฒด ํ…์ŠคํŠธ๋งŒ "Hello"๋กœ ๋ฐ”๊ฟ”๋ผ',
1106
+ inputs=[],
1107
+ outputs=prompt_input
1108
+ )
1109
+ clothes_change_btn1.click(
1110
+ fn=lambda: "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด ์‹ ์ฒด ๋น„์œจ๊ณผ ํฌ์ฆˆ๋Š” ์œ ์ง€ํ•œ ์ฒด (#2์˜ ์„ ๊ธ€๋ผ์Šค)์™€ (#3์˜ ์ฒญ๋ฐ”์ง€)๋ฅผ ์ง์ ‘ ๋ชจ๋ธ์ด ์ฐฉ์šฉํ•œ๊ฒƒ ์ฒ˜๋Ÿผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ.",
1111
+ inputs=[],
1112
+ outputs=prompt_input
1113
+ )
1114
+ clothes_change_btn2.click(
1115
+ fn=lambda: "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด (#2์˜ ์„ ๊ธ€๋ผ์Šค)์„ ์ฐฉ์šฉํ•˜๊ณ  (#3์˜ ๋’ท๋ฐฐ๊ฒฝ์˜ ์นดํŽ˜์ „์ฒด๊ฐ€ ๋ณด์ด๋ฉฐ) ์˜์ž์— ์•‰์•„ ์žˆ๋Š” ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ.",
1116
+ inputs=[],
1117
+ outputs=prompt_input
1118
+ )
1119
+ holding_product_btn.click(
1120
+ fn=lambda: "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด(#2์˜ ์™€์ธ์ž”)์„ ๋“ค๊ณ  ์žˆ๋Š” ์ž์—ฐ์Šค๋Ÿฌ์šด ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ.",
1121
+ inputs=[],
1122
+ outputs=prompt_input
1123
+ )
1124
+ background_change_btn.click(
1125
+ fn=lambda: "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด (#2 ์นดํŽ˜)์—์„œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์žˆ๋Š” ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ.",
1126
+ inputs=[],
1127
+ outputs=prompt_input
1128
+ )
1129
+ composite_product_btn.click(
1130
+ fn=lambda: "(#1์˜ ๋ ˆ๊ณ ๋ชจํ˜•)์—์„œ ์ฒญ์ƒ‰์ƒ์–ด๋ ˆ๊ณ ๋ฅผ ์ œ๊ฑฐํ•œ ํ›„, ๊ทธ ์ž๋ฆฌ๋ฅผ ์ฃผ๋ณ€ ๋ฐฐ๊ฒฝ๊ณผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์–ด์šฐ๋Ÿฌ์ง€๋„๋ก ์ฑ„์›Œ์ฃผ์„ธ์š”. ๋‹จ, ์ด๋ฏธ์ง€์˜ ๋‹ค๋ฅธ ๋ถ€๋ถ„์˜ ์ฃผ์š” ์š”์†Œ๋Š” ๋™์ผํ•˜๊ฒŒ ์œ ์ง€ ํ•ด์•ผํ•œ๋‹ค.",
1131
+ inputs=[],
1132
+ outputs=prompt_input
1133
+ )
1134
+ outpainting_btn.click(
1135
+ fn=lambda: "(#1 ์ด๋ฏธ์ง€)๋ฅผ ์›๋ณธ๊ทธ๋Œ€๋กœ ์ค‘์•™์— ๋‘๊ณ  ๋น„์œจ๋กœ ์œ ์ง€ํ•œ ์ฒด ์œ„์•„๋ž˜ ๋ฐ ์ขŒ์šฐ๋กœ ํฌ๊ฒŒ ํ™•์žฅํ•˜๋ผ.",
1136
+ inputs=[],
1137
+ outputs=prompt_input
1138
+
1139
+ )
1140
+ food_btn_1.click(
1141
+ fn=lambda: "(#1์…€๋Ÿฌ๋“œ)์— ๋‹ด์€ ์šฉ๊ธฐ๋Š” ๋ฒ„๋ฆฌ๊ณ  ๋„“๊ณ  ํฐ ์˜ˆ์œ ์ ‘์‹œ์— (#1์…€๋Ÿฌ๋“œ)์Œ์‹๋งŒ ๊ฐ€๋“ ์ฑ„์›Œ์„œ ์ƒ์—…์ ์ธ ๊ฐ๋„๋กœ ์–ด์šธ๋ฆฌ๋Š” ์†Œํ’ˆ๊ณผ ํ•จ๊ป˜ ํ”Œ๋ ˆ์ดํŒ… ํ•œ ๋ชจ์Šต์„ ์ด๋ฏธ์ง€๋กœ ์ƒ์„ฑํ•˜๋ผ. ",
1142
+ inputs=[],
1143
+ outputs=prompt_input
1144
+ )
1145
+ food_btn_2.click(
1146
+ fn=lambda: "(#2 ํ”Œ๋ ˆ์ดํŒ…ํ•œ ์ด๋ฏธ์ง€)์— ๋‹ด๊ธด ์Œ์‹์„ (#1 ์ƒ๋Ÿฌ๋“œ)๋กœ ๋ฐ”๊พธ๊ณ  ๋‚˜๋จธ์ง€๋Š” ์‹œ๋“œ๋ฅผ ์œ ์ง€ํ•œ ์ฒด ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ.",
1147
+ inputs=[],
1148
+ outputs=prompt_input
1149
+ )
1150
+
1151
+ food_btn_3.click(
1152
+ fn=lambda: "(#1์ปต)์— ๋”ธ๊ธฐ, ๋ฐ”๋‹๋ผ, ์ดˆ์ฝ” ์•„์ด์Šคํฌ๋ฆผ์„ ๋‹ด๊ณ  ๊ทธ ์œ„์— ์ดˆ์ฝ” ์‹œ๋Ÿฝ์ด ํ๋ฅด๊ฒŒ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ.",
1153
+ inputs=[],
1154
+ outputs=prompt_input
1155
+ )
1156
+
1157
+
1158
+ # ๋‹จ์ผ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ - API ํ‚ค ์ž…๋ ฅ๊ฐ’ ํ™œ์šฉ
1159
+ def generate_single_image_with_key(image1, image2, image3, prompt, api_key):
1160
+ return process_images_with_prompt(image1, image2, image3, prompt, 0, 3, api_key)
1161
+
1162
+ submit_single_btn.click(
1163
+ fn=lambda image1, image2, image3, prompt, api_key: generate_single_image_with_key(image1, image2, image3, prompt, api_key),
1164
+ inputs=[image1_input, image2_input, image3_input, prompt_input, api_key_input],
1165
+ outputs=[output_image1, output_text, prompt_display],
1166
+ )
1167
+
1168
+ # 4์žฅ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ - API ํ‚ค ์ž…๋ ฅ๊ฐ’ ํ™œ์šฉ
1169
+ submit_btn.click(
1170
+ fn=lambda image1, image2, image3, prompt, api_key: generate_multiple_images(image1, image2, image3, prompt, api_key),
1171
+ inputs=[image1_input, image2_input, image3_input, prompt_input, api_key_input],
1172
+ outputs=[output_image1, output_image2, output_image3, output_image4, output_text, prompt_display],
1173
+ )
1174
+
1175
+ # ========== ์ด๋ฏธ์ง€ ํŽธ์ง‘๊ธฐ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ ==========
1176
+ # ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜ ์—ฐ๊ฒฐ
1177
+ edit_inputs = [
1178
+ edit_input_image,
1179
+ brightness_slider,
1180
+ contrast_slider,
1181
+ saturation_slider,
1182
+ temperature_slider,
1183
+ tint_slider,
1184
+ exposure_slider,
1185
+ vibrance_slider,
1186
+ color_mixer_blues_slider,
1187
+ shadows_slider
1188
+ ]
1189
+
1190
+ edit_input_components = [
1191
+ brightness_slider,
1192
+ contrast_slider,
1193
+ saturation_slider,
1194
+ temperature_slider,
1195
+ tint_slider,
1196
+ exposure_slider,
1197
+ vibrance_slider,
1198
+ color_mixer_blues_slider,
1199
+ shadows_slider
1200
+ ]
1201
+
1202
+ for input_component in edit_input_components:
1203
+ input_component.change(
1204
+ fn=process_image,
1205
+ inputs=edit_inputs,
1206
+ outputs=edit_output_image
1207
+ )
1208
+
1209
+ # ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์‹œ ์ž๋™์œผ๋กœ ํŽธ์ง‘ ์—…๋ฐ์ดํŠธ
1210
+ edit_input_image.change(
1211
+ fn=process_image,
1212
+ inputs=edit_inputs,
1213
+ outputs=edit_output_image
1214
+ )
1215
+
1216
+ # ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ๊ธฐ๋Šฅ
1217
+ download_button.click(
1218
+ fn=download_edited_image,
1219
+ inputs=[edit_output_image, edit_input_image],
1220
+ outputs=download_output
1221
+ )
1222
+
1223
+ demo.queue()
1224
+ demo.launch(share=False, inbrowser=True, width="100%") # width ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€