ssboost commited on
Commit
a0d9647
ยท
verified ยท
1 Parent(s): c6cc218

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +574 -150
app.py CHANGED
@@ -1,166 +1,590 @@
1
  import gradio as gr
2
- import numpy as np
3
- import spaces
4
- import torch
5
- import random
6
  from PIL import Image
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
- from diffusers import FluxKontextPipeline
9
- from diffusers.utils import load_image
 
 
10
 
11
- MAX_SEED = np.iinfo(np.int32).max
12
-
13
- pipe = FluxKontextPipeline.from_pretrained("black-forest-labs/FLUX.1-Kontext-dev", torch_dtype=torch.bfloat16).to("cuda")
14
-
15
- @spaces.GPU
16
- def infer(input_image, prompt, seed=42, randomize_seed=False, guidance_scale=2.5, steps=28, progress=gr.Progress(track_tqdm=True)):
17
- """
18
- Perform image editing using the FLUX.1 Kontext pipeline.
19
-
20
- This function takes an input image and a text prompt to generate a modified version
21
- of the image based on the provided instructions. It uses the FLUX.1 Kontext model
22
- for contextual image editing tasks.
23
-
24
- Args:
25
- input_image (PIL.Image.Image): The input image to be edited. Will be converted
26
- to RGB format if not already in that format.
27
- prompt (str): Text description of the desired edit to apply to the image.
28
- Examples: "Remove glasses", "Add a hat", "Change background to beach".
29
- seed (int, optional): Random seed for reproducible generation. Defaults to 42.
30
- Must be between 0 and MAX_SEED (2^31 - 1).
31
- randomize_seed (bool, optional): If True, generates a random seed instead of
32
- using the provided seed value. Defaults to False.
33
- guidance_scale (float, optional): Controls how closely the model follows the
34
- prompt. Higher values mean stronger adherence to the prompt but may reduce
35
- image quality. Range: 1.0-10.0. Defaults to 2.5.
36
- steps (int, optional): Controls how many steps to run the diffusion model for.
37
- Range: 1-30. Defaults to 28.
38
- progress (gr.Progress, optional): Gradio progress tracker for monitoring
39
- generation progress. Defaults to gr.Progress(track_tqdm=True).
40
-
41
- Returns:
42
- tuple: A 3-tuple containing:
43
- - PIL.Image.Image: The generated/edited image
44
- - int: The seed value used for generation (useful when randomize_seed=True)
45
- - gr.update: Gradio update object to make the reuse button visible
46
-
47
- Example:
48
- >>> edited_image, used_seed, button_update = infer(
49
- ... input_image=my_image,
50
- ... prompt="Add sunglasses",
51
- ... seed=123,
52
- ... randomize_seed=False,
53
- ... guidance_scale=2.5
54
- ... )
55
- """
56
- if randomize_seed:
57
- seed = random.randint(0, MAX_SEED)
58
-
59
- if input_image:
60
- input_image = input_image.convert("RGB")
61
- image = pipe(
62
- image=input_image,
63
- prompt=prompt,
64
- guidance_scale=guidance_scale,
65
- width = input_image.size[0],
66
- height = input_image.size[1],
67
- num_inference_steps=steps,
68
- generator=torch.Generator().manual_seed(seed),
69
- ).images[0]
70
- else:
71
- image = pipe(
72
- prompt=prompt,
73
- guidance_scale=guidance_scale,
74
- num_inference_steps=steps,
75
- generator=torch.Generator().manual_seed(seed),
76
- ).images[0]
77
- return image, seed, gr.Button(visible=True)
78
 
79
- @spaces.GPU
80
- def infer_example(input_image, prompt):
81
- image, seed, _ = infer(input_image, prompt)
82
- return image, seed
 
 
 
 
83
 
84
- css="""
85
- #col-container {
86
- margin: 0 auto;
87
- max-width: 960px;
88
- }
89
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
- with gr.Blocks(css=css) as demo:
92
-
93
- with gr.Column(elem_id="col-container"):
94
- gr.Markdown(f"""# FLUX.1 Kontext [dev]
95
- Image editing and manipulation model guidance-distilled from FLUX.1 Kontext [pro], [[blog]](https://bfl.ai/announcements/flux-1-kontext-dev) [[model]](https://huggingface.co/black-forest-labs/FLUX.1-Kontext-dev)
96
- """)
97
- with gr.Row():
98
- with gr.Column():
99
- input_image = gr.Image(label="Upload the image for editing", type="pil")
100
- with gr.Row():
101
- prompt = gr.Text(
102
- label="Prompt",
103
- show_label=False,
104
- max_lines=1,
105
- placeholder="Enter your prompt for editing (e.g., 'Remove glasses', 'Add a hat')",
106
- container=False,
107
- )
108
- run_button = gr.Button("Run", scale=0)
109
- with gr.Accordion("Advanced Settings", open=False):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
- seed = gr.Slider(
112
- label="Seed",
113
- minimum=0,
114
- maximum=MAX_SEED,
115
- step=1,
116
- value=0,
117
- )
118
 
119
- randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
 
 
 
120
 
121
- guidance_scale = gr.Slider(
122
- label="Guidance Scale",
123
- minimum=1,
124
- maximum=10,
125
- step=0.1,
126
- value=2.5,
127
- )
128
 
129
- steps = gr.Slider(
130
- label="Steps",
131
- minimum=1,
132
- maximum=30,
133
- value=28,
134
- step=1
135
- )
136
 
137
- with gr.Column():
138
- result = gr.Image(label="Result", show_label=False, interactive=False)
139
- reuse_button = gr.Button("Reuse this image", visible=False)
140
-
141
-
142
- examples = gr.Examples(
143
- examples=[
144
- ["flowers.png", "turn the flowers into sunflowers"],
145
- ["monster.png", "make this monster ride a skateboard on the beach"],
146
- ["cat.png", "make this cat happy"]
147
- ],
148
- inputs=[input_image, prompt],
149
- outputs=[result, seed],
150
- fn=infer_example,
151
- cache_examples="lazy"
152
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
- gr.on(
155
- triggers=[run_button.click, prompt.submit],
156
- fn = infer,
157
- inputs = [input_image, prompt, seed, randomize_seed, guidance_scale, steps],
158
- outputs = [result, seed, reuse_button]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  )
160
- reuse_button.click(
161
- fn = lambda image: image,
162
- inputs = [result],
163
- outputs = [input_image]
 
 
 
 
 
 
 
 
164
  )
165
 
166
- demo.launch(mcp_server=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
+ import replicate
 
 
 
3
  from PIL import Image
4
+ from google import genai
5
+ from google.genai import types
6
+ import io
7
+ import base64
8
+ import tempfile
9
+ import os
10
+ import uuid
11
+ import requests
12
+ from io import BytesIO
13
+ import time
14
+ import json
15
+ import datetime
16
 
17
+ # ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ API ํ† ํฐ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฐ€์ ธ์˜ค๊ธฐ
18
+ REPLICATE_API_TOKEN = os.getenv("REPLICATE_API_TOKEN")
19
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
20
+ PASSWORD = os.getenv("APP_PASSWORD") # ๊ธฐ๋ณธ๊ฐ’ ์„ค์ •
21
 
22
+ # API ํ‚ค ๊ฒ€์ฆ
23
+ def validate_api_keys():
24
+ """API ํ‚ค๋“ค์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์„ค์ •๋˜์—ˆ๋Š”์ง€ ํ™•์ธ"""
25
+ missing_keys = []
26
+
27
+ if not REPLICATE_API_TOKEN:
28
+ missing_keys.append("REPLICATE_API_TOKEN")
29
+
30
+ if not GEMINI_API_KEY:
31
+ missing_keys.append("GEMINI_API_KEY")
32
+
33
+ if missing_keys:
34
+ error_msg = f"๋‹ค์Œ ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค: {', '.join(missing_keys)}"
35
+ raise ValueError(error_msg)
36
+
37
+ return True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
+ def log_message(message, log_list=None):
40
+ """๋กœ๊ทธ ๋ฉ”์‹œ์ง€๋ฅผ ํƒ€์ž„์Šคํƒฌํ”„์™€ ํ•จ๊ป˜ ๊ธฐ๋ก"""
41
+ timestamp = datetime.datetime.now().strftime("%H:%M:%S")
42
+ formatted_message = f"[{timestamp}] {message}"
43
+ print(formatted_message) # ์ฝ˜์†”์—๋„ ์ถœ๋ ฅ
44
+ if log_list is not None:
45
+ log_list.append(formatted_message)
46
+ return formatted_message
47
 
48
+ def translate_to_english(korean_prompt, log_list):
49
+ """ํ•œ๊ตญ์–ด ํ”„๋กฌํ”„ํŠธ๋ฅผ FLUX ์ด๋ฏธ์ง€ ์ƒ์„ฑ์— ์ตœ์ ํ™”๋œ ์˜์–ด๋กœ ๋ฒˆ์—ญ"""
50
+ log_message("=== ๋ฒˆ์—ญ ํ”„๋กœ์„ธ์Šค ์‹œ์ž‘ ===", log_list)
51
+ log_message(f"์ž…๋ ฅ๋œ ํ•œ๊ตญ์–ด ํ”„๋กฌํ”„ํŠธ: '{korean_prompt}'", log_list)
52
+
53
+ try:
54
+ log_message("Gemini API ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” ์‹œ์ž‘...", log_list)
55
+ client = genai.Client(api_key=GEMINI_API_KEY)
56
+ log_message("Gemini API ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ", log_list)
57
+
58
+ system_instruction = """You are a professional prompt translator for FLUX image generation models.
59
+ Translate Korean image editing prompts to optimized English prompts that work best with FLUX models.
60
+ Keep the translation:
61
+ - Clear and specific
62
+ - Descriptive but concise
63
+ - Focused on visual elements
64
+ - Suitable for AI image generation
65
+
66
+ Only return the translated English prompt, nothing else."""
67
+
68
+ log_message("๋ฒˆ์—ญ ์š”์ฒญ ์ค€๋น„ ์™„๋ฃŒ", log_list)
69
+ log_message("Gemini API ํ˜ธ์ถœ ์‹œ์ž‘...", log_list)
70
+
71
+ start_time = time.time()
72
+ response = client.models.generate_content(
73
+ model="gemini-2.0-flash",
74
+ config=types.GenerateContentConfig(
75
+ system_instruction=system_instruction,
76
+ temperature=0.3
77
+ ),
78
+ contents=[f"Translate this Korean prompt to English for FLUX image editing: {korean_prompt}"]
79
+ )
80
+ end_time = time.time()
81
+
82
+ log_message(f"Gemini API ์‘๋‹ต ์ˆ˜์‹  ์™„๋ฃŒ (์†Œ์š”์‹œ๊ฐ„: {end_time - start_time:.2f}์ดˆ)", log_list)
83
+
84
+ translated_text = response.text.strip()
85
+ log_message(f"๋ฒˆ์—ญ ๊ฒฐ๊ณผ: '{translated_text}'", log_list)
86
+ log_message("=== ๋ฒˆ์—ญ ํ”„๋กœ์„ธ์Šค ์™„๋ฃŒ ===", log_list)
87
+
88
+ return translated_text
89
+
90
+ except Exception as e:
91
+ error_msg = f"๋ฒˆ์—ญ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
92
+ log_message(error_msg, log_list)
93
+ log_message("์›๋ณธ ํ•œ๊ตญ์–ด ํ”„๋กฌํ”„ํŠธ๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค", log_list)
94
+ log_message("=== ๋ฒˆ์—ญ ํ”„๋กœ์„ธ์Šค ์‹คํŒจ ===", log_list)
95
+ return korean_prompt
96
 
97
+ def upscale_image(image_path, output_format, english_prompt, log_list):
98
+ """Clarity Upscaler๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฏธ์ง€ ์—…์Šค์ผ€์ผ๋ง (๋ชจ๋“  ๋งค๊ฐœ๋ณ€์ˆ˜ ๊ณ ์ •)"""
99
+ log_message("=== ํ™”์งˆ ๊ฐœ์„ (์—…์Šค์ผ€์ผ๋ง) ํ”„๋กœ์„ธ์Šค ์‹œ์ž‘ ===", log_list)
100
+ log_message(f"์ž…๋ ฅ ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ: {image_path}", log_list)
101
+ log_message(f"์ถœ๋ ฅ ํฌ๋งท: {output_format}", log_list)
102
+ log_message(f"์‚ฌ์šฉํ•  ํ”„๋กฌํ”„ํŠธ: '{english_prompt}'", log_list)
103
+
104
+ try:
105
+ # ํŒŒ์ผ ์กด์žฌ ํ™•์ธ
106
+ if not os.path.exists(image_path):
107
+ error_msg = f"์ž…๋ ฅ ์ด๋ฏธ์ง€ ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: {image_path}"
108
+ log_message(error_msg, log_list)
109
+ return None
110
+
111
+ # ํŒŒ์ผ ํฌ๊ธฐ ํ™•์ธ
112
+ file_size = os.path.getsize(image_path)
113
+ log_message(f"์ž…๋ ฅ ํŒŒ์ผ ํฌ๊ธฐ: {file_size} bytes ({file_size/1024/1024:.2f} MB)", log_list)
114
+
115
+ log_message("Replicate API ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” ์‹œ์ž‘...", log_list)
116
+ client = replicate.Client(api_token=REPLICATE_API_TOKEN)
117
+ log_message("Replicate API ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ", log_list)
118
+
119
+ log_message("์ด๋ฏธ์ง€ ํŒŒ์ผ ์ฝ๊ธฐ ์‹œ์ž‘...", log_list)
120
+ with open(image_path, "rb") as file:
121
+ log_message("์ด๋ฏธ์ง€ ํŒŒ์ผ ์ฝ๊ธฐ ์™„๋ฃŒ", log_list)
122
+
123
+ # ์—…์Šค์ผ€์ผ ํŒŒ๋ผ๋ฏธํ„ฐ ์„ค์ •
124
+ input_data = {
125
+ "image": file,
126
+ "scale_factor": 2, # ๊ณ ์ •: 2๋ฐฐ ํ™•๋Œ€
127
+ "resemblance": 0.8, # ๊ณ ์ •: ๋†’์€ ์›๋ณธ ์œ ์‚ฌ๋„
128
+ "creativity": 0.2, # ๊ณ ์ •: ๋‚ฎ์€ ์ฐฝ์˜์„ฑ
129
+ "output_format": output_format.lower(),
130
+ "prompt": english_prompt,
131
+ "negative_prompt": "(worst quality, low quality, normal quality:2)"
132
+ }
133
+
134
+ log_message("์—…์Šค์ผ€์ผ ํŒŒ๋ผ๋ฏธํ„ฐ ์„ค์ •:", log_list)
135
+ log_message(f" - scale_factor: 2", log_list)
136
+ log_message(f" - resemblance: 0.8", log_list)
137
+ log_message(f" - creativity: 0.2", log_list)
138
+ log_message(f" - output_format: {output_format.lower()}", log_list)
139
+ log_message(f" - negative_prompt: (worst quality, low quality, normal quality:2)", log_list)
140
+
141
+ log_message("Clarity Upscaler API ํ˜ธ์ถœ ์‹œ์ž‘...", log_list)
142
+ log_message("๋ชจ๋ธ: philz1337x/clarity-upscaler", log_list)
143
+
144
+ start_time = time.time()
145
+ try:
146
+ output = client.run(
147
+ "philz1337x/clarity-upscaler:dfad41707589d68ecdccd1dfa600d55a208f9310748e44bfe35b4a6291453d5e",
148
+ input=input_data
149
+ )
150
+ end_time = time.time()
151
+ log_message(f"Clarity Upscaler API ์‘๋‹ต ์ˆ˜์‹  ์™„๋ฃŒ (์†Œ์š”์‹œ๊ฐ„: {end_time - start_time:.2f}์ดˆ)", log_list)
152
+
153
+ except Exception as api_error:
154
+ error_msg = f"Clarity Upscaler API ํ˜ธ์ถœ ์‹คํŒจ: {str(api_error)}"
155
+ log_message(error_msg, log_list)
156
+ log_message("=== ํ™”์งˆ ๊ฐœ์„  ํ”„๋กœ์„ธ์Šค ์‹คํŒจ ===", log_list)
157
+ return None
158
+
159
+ # API ์‘๋‹ต ๊ฒ€์ฆ
160
+ log_message(f"API ์‘๋‹ต ํƒ€์ž…: {type(output)}", log_list)
161
+ log_message(f"API ์‘๋‹ต ๋‚ด์šฉ: {output}", log_list)
162
+
163
+ # ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ
164
+ if output and isinstance(output, list) and len(output) > 0:
165
+ result_url = output[0]
166
+ log_message(f"๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ URL: {result_url}", log_list)
167
+ log_message("๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ ์‹œ์ž‘...", log_list)
168
+
169
+ try:
170
+ download_start = time.time()
171
+ response = requests.get(result_url, timeout=30)
172
+ download_end = time.time()
173
+
174
+ log_message(f"๋‹ค์šด๋กœ๋“œ ์‘๋‹ต ์ƒํƒœ: {response.status_code}", log_list)
175
+ log_message(f"๋‹ค์šด๋กœ๋“œ ์†Œ์š”์‹œ๊ฐ„: {download_end - download_start:.2f}์ดˆ", log_list)
176
+ log_message(f"๋‹ค์šด๋กœ๋“œ๋œ ๋ฐ์ดํ„ฐ ํฌ๊ธฐ: {len(response.content)} bytes ({len(response.content)/1024/1024:.2f} MB)", log_list)
177
+
178
+ if response.status_code == 200:
179
+ log_message("์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ๋ฅผ PIL Image๋กœ ๋ณ€ํ™˜ ์ค‘...", log_list)
180
+ result_image = Image.open(BytesIO(response.content))
181
+ log_message(f"๋ณ€ํ™˜๋œ ์ด๋ฏธ์ง€ ํฌ๊ธฐ: {result_image.size}", log_list)
182
+ log_message(f"๋ณ€ํ™˜๋œ ์ด๋ฏธ์ง€ ๋ชจ๋“œ: {result_image.mode}", log_list)
183
 
184
+ # ์ž„์‹œ ํŒŒ์ผ๋กœ ์ €์žฅ
185
+ ext = output_format.lower()
186
+ upscaled_filename = f"upscaled_temp_{uuid.uuid4()}.{ext}"
187
+ log_message(f"์—…์Šค์ผ€์ผ๋œ ์ด๋ฏธ์ง€ ์ €์žฅ ํŒŒ์ผ๋ช…: {upscaled_filename}", log_list)
 
 
 
188
 
189
+ # RGBA ๋ชจ๋“œ ์ฒ˜๋ฆฌ
190
+ if ext == 'jpg' and result_image.mode == 'RGBA':
191
+ log_message("RGBA ๋ชจ๋“œ๋ฅผ RGB๋กœ ๋ณ€ํ™˜ ์ค‘ (JPG ์ €์žฅ์„ ์œ„ํ•ด)...", log_list)
192
+ result_image = result_image.convert('RGB')
193
 
194
+ # ์ด๋ฏธ์ง€ ์ €์žฅ
195
+ save_start = time.time()
196
+ if ext == 'jpg':
197
+ result_image.save(upscaled_filename, format='JPEG', quality=95)
198
+ else:
199
+ result_image.save(upscaled_filename, format='PNG')
200
+ save_end = time.time()
201
 
202
+ log_message(f"์ด๋ฏธ์ง€ ์ €์žฅ ์™„๋ฃŒ (์†Œ์š”์‹œ๊ฐ„: {save_end - save_start:.2f}์ดˆ)", log_list)
 
 
 
 
 
 
203
 
204
+ # ์ €์žฅ๋œ ํŒŒ์ผ ํฌ๊ธฐ ํ™•์ธ
205
+ saved_size = os.path.getsize(upscaled_filename)
206
+ log_message(f"์ €์žฅ๋œ ํŒŒ์ผ ํฌ๊ธฐ: {saved_size} bytes ({saved_size/1024/1024:.2f} MB)", log_list)
207
+
208
+ log_message("=== ํ™”์งˆ ๊ฐœ์„  ํ”„๋กœ์„ธ์Šค ์™„๋ฃŒ ===", log_list)
209
+ return upscaled_filename
210
+ else:
211
+ error_msg = f"์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ ์‹คํŒจ - HTTP ์ƒํƒœ: {response.status_code}"
212
+ log_message(error_msg, log_list)
213
+ log_message("=== ํ™”์งˆ ๊ฐœ์„  ํ”„๋กœ์„ธ์Šค ์‹คํŒจ ===", log_list)
214
+ return None
215
+
216
+ except requests.exceptions.Timeout:
217
+ log_message("์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ ํƒ€์ž„์•„์›ƒ (30์ดˆ)", log_list)
218
+ log_message("=== ํ™”์งˆ ๊ฐœ์„  ํ”„๋กœ์„ธ์Šค ์‹คํŒจ ===", log_list)
219
+ return None
220
+ except Exception as download_error:
221
+ error_msg = f"์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜: {str(download_error)}"
222
+ log_message(error_msg, log_list)
223
+ log_message("=== ํ™”์งˆ ๊ฐœ์„  ํ”„๋กœ์„ธ์Šค ์‹คํŒจ ===", log_list)
224
+ return None
225
+ else:
226
+ log_message("API ์‘๋‹ต์ด ๋น„์–ด์žˆ๊ฑฐ๋‚˜ ์˜ˆ์ƒ ํ˜•์‹๊ณผ ๋‹ค๋ฆ…๋‹ˆ๋‹ค", log_list)
227
+ if output:
228
+ log_message(f"์‹ค์ œ ์‘๋‹ต: {json.dumps(output, indent=2) if isinstance(output, dict) else str(output)}", log_list)
229
+ log_message("=== ํ™”์งˆ ๊ฐœ์„  ํ”„๋กœ์„ธ์Šค ์‹คํŒจ ===", log_list)
230
+ return None
231
+
232
+ except Exception as e:
233
+ error_msg = f"์—…์Šค์ผ€์ผ๋ง ํ”„๋กœ์„ธ์Šค ์ค‘ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜: {str(e)}"
234
+ log_message(error_msg, log_list)
235
+ log_message("=== ํ™”์งˆ ๊ฐœ์„  ํ”„๋กœ์„ธ์Šค ์‹คํŒจ ===", log_list)
236
+ return None
237
+
238
+ def edit_image(input_image, password, korean_prompt, output_format, aspect_ratio, upscale_option, current_images, current_downloads):
239
+ log_list = []
240
+ log_message("=" * 60, log_list)
241
+ log_message("์ƒˆ๋กœ์šด ์ด๋ฏธ์ง€ ํŽธ์ง‘ ์ž‘์—… ์‹œ์ž‘", log_list)
242
+ log_message("=" * 60, log_list)
243
+
244
+ # API ํ‚ค ๊ฒ€์ฆ
245
+ try:
246
+ validate_api_keys()
247
+ log_message("API ํ‚ค ๊ฒ€์ฆ ์™„๋ฃŒ", log_list)
248
+ except ValueError as e:
249
+ error_msg = str(e)
250
+ log_message(f"API ํ‚ค ๊ฒ€์ฆ ์‹คํŒจ: {error_msg}", log_list)
251
+ return None, None, "\n".join(log_list) + f"\n์˜ค๋ฅ˜: {error_msg}", current_images, current_downloads
252
+
253
+ # ์ž…๋ ฅ ๊ฒ€์ฆ
254
+ log_message("=== ์ž…๋ ฅ ๊ฒ€์ฆ ๋‹จ๊ณ„ ===", log_list)
255
+
256
+ if not password:
257
+ error_msg = "๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”."
258
+ log_message(f"๊ฒ€์ฆ ์‹คํŒจ: {error_msg}", log_list)
259
+ return None, None, "\n".join(log_list) + f"\n์˜ค๋ฅ˜: {error_msg}", current_images, current_downloads
260
+
261
+ if password != PASSWORD:
262
+ error_msg = "๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค."
263
+ log_message(f"๊ฒ€์ฆ ์‹คํŒจ: {error_msg}", log_list)
264
+ return None, None, "\n".join(log_list) + f"\n์˜ค๋ฅ˜: {error_msg}", current_images, current_downloads
265
+
266
+ log_message("๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฒ€์ฆ ํ†ต๊ณผ", log_list)
267
+
268
+ if input_image is None:
269
+ error_msg = "์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜์„ธ์š”."
270
+ log_message(f"๊ฒ€์ฆ ์‹คํŒจ: {error_msg}", log_list)
271
+ return None, None, "\n".join(log_list) + f"\n์˜ค๋ฅ˜: {error_msg}", current_images, current_downloads
272
+
273
+ log_message(f"์ž…๋ ฅ ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ: {input_image}", log_list)
274
+
275
+ if not korean_prompt.strip():
276
+ error_msg = "ํŽธ์ง‘ ์ง€์‹œ์‚ฌํ•ญ์„ ์ž…๋ ฅํ•˜์„ธ์š”."
277
+ log_message(f"๊ฒ€์ฆ ์‹คํŒจ: {error_msg}", log_list)
278
+ return None, None, "\n".join(log_list) + f"\n์˜ค๋ฅ˜: {error_msg}", current_images, current_downloads
279
+
280
+ log_message(f"ํŽธ์ง‘ ์ง€์‹œ์‚ฌํ•ญ: '{korean_prompt.strip()}'", log_list)
281
+ log_message(f"์ถœ๋ ฅ ํฌ๋งท: {output_format}", log_list)
282
+ log_message(f"ํ™”๋ฉด ๋น„์œจ: {aspect_ratio}", log_list)
283
+ log_message(f"ํ™”์งˆ ๊ฐœ์„  ์˜ต์…˜: {upscale_option}", log_list)
284
+ log_message("๋ชจ๋“  ์ž…๋ ฅ ๊ฒ€์ฆ ํ†ต๊ณผ", log_list)
285
+
286
+ try:
287
+ # Replicate ํด๋ผ์ด์–ธํŠธ ์„ค์ •
288
+ log_message("=== Replicate ์„ค์ • ๋‹จ๊ณ„ ===", log_list)
289
+ log_message("Replicate ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” ์‹œ์ž‘...", log_list)
290
+ client = replicate.Client(api_token=REPLICATE_API_TOKEN)
291
+ log_message("Replicate ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ", log_list)
292
+
293
+ # ํ•œ๊ตญ์–ด ํ”„๋กฌํ”„ํŠธ๋ฅผ ์˜์–ด๋กœ ๋ฒˆ์—ญ
294
+ english_prompt = translate_to_english(korean_prompt, log_list)
295
+
296
+ # ์ž…๋ ฅ ์ด๋ฏธ์ง€ ์ •๋ณด ํ™•์ธ
297
+ log_message("=== ์ž…๋ ฅ ์ด๋ฏธ์ง€ ๋ถ„์„ ===", log_list)
298
+ try:
299
+ with Image.open(input_image) as img:
300
+ log_message(f"์ด๋ฏธ์ง€ ํฌ๊ธฐ: {img.size}", log_list)
301
+ log_message(f"์ด๋ฏธ๏ฟฝ๏ฟฝ ๋ชจ๋“œ: {img.mode}", log_list)
302
+ log_message(f"์ด๋ฏธ์ง€ ํฌ๋งท: {img.format}", log_list)
303
+ except Exception as img_error:
304
+ log_message(f"์ด๋ฏธ์ง€ ์ •๋ณด ์ฝ๊ธฐ ์˜ค๋ฅ˜: {str(img_error)}", log_list)
305
+
306
+ # ํŒŒ์ผ ํฌ๊ธฐ ํ™•์ธ
307
+ file_size = os.path.getsize(input_image)
308
+ log_message(f"์ž…๋ ฅ ํŒŒ์ผ ํฌ๊ธฐ: {file_size} bytes ({file_size/1024/1024:.2f} MB)", log_list)
309
+
310
+ # FLUX ์ด๋ฏธ์ง€ ํŽธ์ง‘ ์‹œ์ž‘
311
+ log_message("=== FLUX ์ด๋ฏธ์ง€ ํŽธ์ง‘ ๋‹จ๊ณ„ ===", log_list)
312
+ log_message("์ด๋ฏธ์ง€ ํŒŒ์ผ ์ฝ๊ธฐ ์‹œ์ž‘...", log_list)
313
+
314
+ with open(input_image, "rb") as file:
315
+ input_file = file
316
+ log_message("ํŒŒ์ผ ์ฝ๊ธฐ ์™„๋ฃŒ", log_list)
317
 
318
+ # Replicate API ํ˜ธ์ถœ ํŒŒ๋ผ๋ฏธํ„ฐ ์„ค์ •
319
+ input_data = {
320
+ "prompt": english_prompt,
321
+ "input_image": input_file,
322
+ "output_format": output_format.lower(),
323
+ "aspect_ratio": aspect_ratio
324
+ }
325
+
326
+
327
+ flux_start_time = time.time()
328
+ try:
329
+ output = client.run(
330
+ "black-forest-labs/flux-kontext-pro",
331
+ input=input_data
332
+ )
333
+ flux_end_time = time.time()
334
+ log_message(f"FLUX API ์‘๋‹ต ์ˆ˜์‹  ์™„๋ฃŒ (์†Œ์š”์‹œ๊ฐ„: {flux_end_time - flux_start_time:.2f}์ดˆ)", log_list)
335
+
336
+ except Exception as flux_error:
337
+ error_msg = f"FLUX API ํ˜ธ์ถœ ์‹คํŒจ: {str(flux_error)}"
338
+ log_message(error_msg, log_list)
339
+ return None, None, "\n".join(log_list) + f"\n์˜ค๋ฅ˜: {error_msg}", current_images, current_downloads
340
+
341
+ log_message(f"FLUX API ์‘๋‹ต ํƒ€์ž…: {type(output)}", log_list)
342
+
343
+ # ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ
344
+ log_message("=== ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ๋‹จ๊ณ„ ===", log_list)
345
+ temp_filename = f"temp_output_{uuid.uuid4()}.{output_format.lower()}"
346
+ log_message(f"์ž„์‹œ ํŒŒ์ผ๋ช…: {temp_filename}", log_list)
347
+
348
+ try:
349
+ log_message("๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ์ €์žฅ ์‹œ์ž‘...", log_list)
350
+ with open(temp_filename, "wb") as file:
351
+ file.write(output.read())
352
+
353
+ # ์ €์žฅ๋œ ํŒŒ์ผ ํฌ๊ธฐ ํ™•์ธ
354
+ saved_size = os.path.getsize(temp_filename)
355
+ log_message(f"FLUX ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ์ €์žฅ ์™„๋ฃŒ", log_list)
356
+ log_message(f"์ €์žฅ๋œ ํŒŒ์ผ ํฌ๊ธฐ: {saved_size} bytes ({saved_size/1024/1024:.2f} MB)", log_list)
357
+
358
+ except Exception as save_error:
359
+ error_msg = f"๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ์ €์žฅ ์‹คํŒจ: {str(save_error)}"
360
+ log_message(error_msg, log_list)
361
+ return None, None, "\n".join(log_list) + f"\n์˜ค๋ฅ˜: {error_msg}", current_images, current_downloads
362
+
363
+ # ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ๋กœ๋“œ
364
+ try:
365
+ log_message("๊ฒฐ๊ณผ ์ด๋ฏธ์ง€๋ฅผ PIL Image๋กœ ๋กœ๋“œ ์ค‘...", log_list)
366
+ result_image = Image.open(temp_filename)
367
+ log_message(f"๋กœ๋“œ๋œ ์ด๋ฏธ์ง€ ํฌ๊ธฐ: {result_image.size}", log_list)
368
+ log_message(f"๋กœ๋“œ๋œ ์ด๋ฏธ์ง€ ๋ชจ๋“œ: {result_image.mode}", log_list)
369
+
370
+ except Exception as load_error:
371
+ error_msg = f"๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ๋กœ๋“œ ์‹คํŒจ: {str(load_error)}"
372
+ log_message(error_msg, log_list)
373
+ return None, None, "\n".join(log_list) + f"\n์˜ค๋ฅ˜: {error_msg}", current_images, current_downloads
374
+
375
+ # ์—…์Šค์ผ€์ผ๋ง ์ ์šฉ (์„ ํƒ์‚ฌํ•ญ)
376
+ final_image = result_image
377
+ if upscale_option == "์ ์šฉ":
378
+ upscaled_path = upscale_image(temp_filename, output_format, english_prompt, log_list)
379
+
380
+ if upscaled_path:
381
+ try:
382
+ log_message("์—…์Šค์ผ€์ผ๋œ ์ด๋ฏธ์ง€ ๋กœ๋“œ ์ค‘...", log_list)
383
+ final_image = Image.open(upscaled_path)
384
+ log_message(f"์ตœ์ข… ์ด๋ฏธ์ง€ ํฌ๊ธฐ: {final_image.size}", log_list)
385
+ log_message("ํ™”์งˆ ๊ฐœ์„ ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค", log_list)
386
+
387
+ # ์—…์Šค์ผ€์ผ ์ž„์‹œ ํŒŒ์ผ ์ •๋ฆฌ
388
+ try:
389
+ os.remove(upscaled_path)
390
+ log_message("์—…์Šค์ผ€์ผ ์ž„์‹œ ํŒŒ์ผ ์ •๋ฆฌ ์™„๋ฃŒ", log_list)
391
+ except:
392
+ pass
393
+
394
+ except Exception as upscale_load_error:
395
+ log_message(f"์—…์Šค์ผ€์ผ๋œ ์ด๋ฏธ์ง€ ๋กœ๋“œ ์‹คํŒจ: {str(upscale_load_error)}", log_list)
396
+ log_message("์›๋ณธ FLUX ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค", log_list)
397
+ else:
398
+ log_message("ํ™”์งˆ ๊ฐœ์„  ์‹คํŒจ - ์›๋ณธ FLUX ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค", log_list)
399
+ else:
400
+ log_message("ํ™”์งˆ ๊ฐœ์„  ์˜ต์…˜์ด ์„ ํƒ๋˜์ง€ ์•Š์Œ - ์›๋ณธ FLUX ๊ฒฐ๊ณผ ์‚ฌ์šฉ", log_list)
401
+
402
+ # ์ž๋™ ์ €์žฅ ์ฒ˜๋ฆฌ
403
+ log_message("=== ๊ฒฐ๊ณผ ์ €์žฅ ๋‹จ๊ณ„ ===", log_list)
404
+
405
+ if current_images is None:
406
+ current_images = []
407
+ if current_downloads is None:
408
+ current_downloads = []
409
+
410
+ # ๊ฐค๋Ÿฌ๋ฆฌ์šฉ ์ž„์‹œ ํŒŒ์ผ ์ƒ์„ฑ
411
+ gallery_temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png')
412
+ final_image.save(gallery_temp_file.name, 'png')
413
+ current_images.append(gallery_temp_file.name)
414
+ log_message(f"๊ฐค๋Ÿฌ๋ฆฌ์šฉ ์ด๋ฏธ์ง€ ์ €์žฅ: {gallery_temp_file.name}", log_list)
415
+
416
+ # ๋‹ค์šด๋กœ๋“œ์šฉ ํŒŒ์ผ ์ƒ์„ฑ
417
+ download_filename = f"edited_image_{len(current_downloads) + 1}.{output_format.lower()}"
418
+ final_image.save(download_filename)
419
+ current_downloads.append(download_filename)
420
+ log_message(f"๋‹ค์šด๋กœ๋“œ์šฉ ์ด๋ฏธ์ง€ ์ €์žฅ: {download_filename}", log_list)
421
+
422
+ # ์ž„์‹œ ํŒŒ์ผ ์ •๋ฆฌ
423
+ try:
424
+ os.remove(temp_filename)
425
+ log_message("FLUX ์ž„์‹œ ํŒŒ์ผ ์ •๋ฆฌ ์™„๋ฃŒ", log_list)
426
+ except:
427
+ pass
428
+
429
+ log_message("=" * 60, log_list)
430
+ log_message("์ด๋ฏธ์ง€ ํŽธ์ง‘ ์ž‘์—… ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ!", log_list)
431
+ log_message("=" * 60, log_list)
432
+
433
+ return final_image, current_downloads, "\n".join(log_list), current_images, current_downloads
434
+
435
+ except Exception as e:
436
+ error_msg = f"์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
437
+ log_message(error_msg, log_list)
438
+ log_message("=" * 60, log_list)
439
+ log_message("์ด๋ฏธ์ง€ ํŽธ์ง‘ ์ž‘์—… ์‹คํŒจ", log_list)
440
+ log_message("=" * 60, log_list)
441
+ return None, current_downloads, "\n".join(log_list), current_images, current_downloads
442
+
443
+ def save_output_as_input(output_image, current_images, current_downloads):
444
+ """์ถœ๋ ฅ ์ด๋ฏธ์ง€๋ฅผ ์ž…๋ ฅ์œผ๋กœ ์ €์žฅ"""
445
+ log_list = []
446
+ log_message("=== ์ถœ๋ ฅ ์ด๋ฏธ์ง€๋ฅผ ์ž…๋ ฅ์œผ๋กœ ์ €์žฅ ์‹œ์ž‘ ===", log_list)
447
+
448
+ if output_image is None:
449
+ log_message("์ €์žฅํ•  ์ถœ๋ ฅ ์ด๋ฏธ์ง€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค", log_list)
450
+ return None, current_images, current_downloads
451
+
452
+ try:
453
+ log_message(f"์ถœ๋ ฅ ์ด๋ฏธ์ง€ ํƒ€์ž…: {type(output_image)}", log_list)
454
+
455
+ # numpy array๋ฅผ PIL Image๋กœ ๋ณ€ํ™˜
456
+ if hasattr(output_image, 'shape'): # numpy array์ธ ๊ฒฝ์šฐ
457
+ log_message("numpy array๋ฅผ PIL Image๋กœ ๋ณ€ํ™˜ ์ค‘...", log_list)
458
+ output_image = Image.fromarray(output_image)
459
+
460
+ log_message(f"๋ณ€ํ™˜๋œ ์ด๋ฏธ์ง€ ํฌ๊ธฐ: {output_image.size}", log_list)
461
+ log_message(f"๋ณ€ํ™˜๋œ ์ด๋ฏธ์ง€ ๋ชจ๋“œ: {output_image.mode}", log_list)
462
+
463
+ # ์ž„์‹œ ํŒŒ์ผ๋กœ ์ €์žฅ (์ž…๋ ฅ์šฉ)
464
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png')
465
+ output_image.save(temp_file.name, 'png')
466
+
467
+ file_size = os.path.getsize(temp_file.name)
468
+ log_message(f"์ž…๋ ฅ์šฉ ์ด๋ฏธ์ง€ ์ €์žฅ ์™„๋ฃŒ: {temp_file.name}", log_list)
469
+ log_message(f"์ €์žฅ๋œ ํŒŒ์ผ ํฌ๊ธฐ: {file_size} bytes ({file_size/1024/1024:.2f} MB)", log_list)
470
+ log_message("=== ์ €์žฅ ํ”„๋กœ์„ธ์Šค ์™„๋ฃŒ ===", log_list)
471
+
472
+ print("\n".join(log_list)) # ์ฝ˜์†”์— ๋กœ๊ทธ ์ถœ๋ ฅ
473
+ return temp_file.name, current_images, current_downloads
474
+
475
+ except Exception as e:
476
+ error_msg = f"์ด๋ฏธ์ง€ ์ €์žฅ ์ค‘ ์˜ค๋ฅ˜: {str(e)}"
477
+ log_message(error_msg, log_list)
478
+ log_message("=== ์ €์žฅ ํ”„๋กœ์„ธ์Šค ์‹คํŒจ ===", log_list)
479
+ print("\n".join(log_list)) # ์ฝ˜์†”์— ๋กœ๊ทธ ์ถœ๋ ฅ
480
+ return None, current_images, current_downloads
481
+
482
+ # ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์‹œ API ํ‚ค ํ™•์ธ
483
+ try:
484
+ validate_api_keys()
485
+ print("โœ… ๋ชจ๋“  API ํ‚ค๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
486
+ except ValueError as e:
487
+ print(f"โŒ {e}")
488
+
489
+ # Gradio ์ธํ„ฐํŽ˜์ด์Šค
490
+ with gr.Blocks(title="์ด๋ฏธ์ง€ ํŽธ์ง‘๊ธฐ (๋ณด์•ˆ ๊ฐ•ํ™” ๋ฒ„์ „)") as demo:
491
+ # ์ƒํƒœ ๋ณ€์ˆ˜๋“ค
492
+ saved_images = gr.State([])
493
+ saved_downloads = gr.State([])
494
+
495
+ gr.Markdown("# ๐ŸŽจ AI ์ด๋ฏธ์ง€ ํŽธ์ง‘๊ธฐ (๋ณด์•ˆ ๊ฐ•ํ™” ๋ฒ„์ „)")
496
+
497
+ with gr.Row():
498
+ with gr.Column():
499
+ input_image = gr.Image(type="filepath", label="๐Ÿ“ค ์ž…๋ ฅ ์ด๋ฏธ์ง€")
500
+ password = gr.Textbox(
501
+ label="๐Ÿ” ๋น„๋ฐ€๋ฒˆํ˜ธ",
502
+ type="password",
503
+ placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”"
504
+ )
505
+ korean_prompt = gr.Textbox(
506
+ label="โœ๏ธ ํŽธ์ง‘ ์ง€์‹œ์‚ฌํ•ญ (ํ•œ๊ตญ์–ด)",
507
+ placeholder="์˜ˆ: ์ด ์‚ฌ๋žŒ์„ ๋งŒํ™” ์บ๋ฆญํ„ฐ๋กœ ๋ฐ”๊ฟ”์ค˜",
508
+ lines=3
509
+ )
510
+
511
+ with gr.Row():
512
+ output_format = gr.Radio(
513
+ choices=["jpg", "png"],
514
+ value="jpg",
515
+ label="๐Ÿ“ ์ถœ๋ ฅ ํฌ๋งท"
516
+ )
517
+ aspect_ratio = gr.Dropdown(
518
+ choices=["match_input_image", "1:1", "3:2", "2:3"],
519
+ value="match_input_image",
520
+ label="๐Ÿ“ ํ™”๋ฉด ๋น„์œจ"
521
+ )
522
+
523
+ upscale_option = gr.Radio(
524
+ choices=["์—†์Œ", "์ ์šฉ"],
525
+ value="์—†์Œ",
526
+ label="๐Ÿ” ํ™”์งˆ ๊ฐœ์„  (2๋ฐฐ ํ™•๋Œ€)"
527
+ )
528
+
529
+ edit_btn = gr.Button("๐Ÿš€ ์ด๋ฏธ์ง€ ํŽธ์ง‘", variant="primary", size="lg")
530
+
531
+ with gr.Column():
532
+ output_image = gr.Image(label="โœจ ํŽธ์ง‘๋œ ์ด๋ฏธ์ง€")
533
+ log_output = gr.Textbox(
534
+ label="๐Ÿ“‹ ์ƒ์„ธ ๋กœ๊ทธ",
535
+ lines=15,
536
+ max_lines=20,
537
+ show_copy_button=True
538
+ )
539
+ download_files = gr.File(label="๐Ÿ’พ ๋‹ค์šด๋กœ๋“œ", file_count="multiple")
540
+
541
+ save_btn = gr.Button("๐Ÿ”„ ์ถœ๋ ฅ ์ด๋ฏธ์ง€๋ฅผ ์ž…๋ ฅ์œผ๋กœ ์ €์žฅ", variant="secondary")
542
+
543
+ # ์ €์žฅ๋œ ์ด๋ฏธ์ง€๋“ค์„ ํ‘œ์‹œํ•˜๋Š” ๊ฐค๋Ÿฌ๋ฆฌ
544
+ with gr.Row():
545
+ output_gallery = gr.Gallery(
546
+ label="๐Ÿ“ธ ํŽธ์ง‘ ๊ธฐ๋ก",
547
+ show_label=True,
548
+ elem_id="gallery",
549
+ columns=3,
550
+ rows=2,
551
+ height="auto"
552
+ )
553
+
554
+ # ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
555
+ edit_btn.click(
556
+ fn=edit_image,
557
+ inputs=[input_image, password, korean_prompt, output_format, aspect_ratio, upscale_option, saved_images, saved_downloads],
558
+ outputs=[output_image, download_files, log_output, saved_images, saved_downloads]
559
  )
560
+
561
+ save_btn.click(
562
+ fn=save_output_as_input,
563
+ inputs=[output_image, saved_images, saved_downloads],
564
+ outputs=[input_image, saved_images, saved_downloads]
565
+ )
566
+
567
+ # ๊ฐค๋Ÿฌ๋ฆฌ ์—…๋ฐ์ดํŠธ
568
+ saved_images.change(
569
+ fn=lambda images: images if images else [],
570
+ inputs=[saved_images],
571
+ outputs=[output_gallery]
572
  )
573
 
574
+ if __name__ == "__main__":
575
+ print("๐Ÿš€ ์ด๋ฏธ์ง€ ํŽธ์ง‘ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์ค‘...")
576
+
577
+ # API ํ‚ค ์ƒํƒœ ํ™•์ธ
578
+ try:
579
+ validate_api_keys()
580
+ print("โœ… ๋ชจ๋“  API ํ‚ค๊ฐ€ ์„ค์ •๋˜์–ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.")
581
+ except ValueError as e:
582
+ print(f"โš ๏ธ ๊ฒฝ๊ณ : {e}")
583
+ print("์ผ๋ถ€ ๊ธฐ๋Šฅ์ด ์ œํ•œ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.")
584
+
585
+ demo.launch(
586
+ server_name="0.0.0.0",
587
+ server_port=7860,
588
+ share=False,
589
+ debug=True
590
+ )