Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -20,13 +20,60 @@ load_dotenv()
|
|
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
|
29 |
-
# 기존 함수 유지하되 API 키 파라미터 추가
|
30 |
if not re.search("[가-힣]", prompt):
|
31 |
return prompt
|
32 |
|
@@ -35,16 +82,15 @@ def translate_prompt_to_english(prompt, api_key=None):
|
|
35 |
prompt = prompt.replace("#3", "IMAGE_TAG_THREE")
|
36 |
|
37 |
try:
|
38 |
-
|
39 |
-
|
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=
|
48 |
translation_prompt = f"""
|
49 |
Translate the following Korean text to English:
|
50 |
|
@@ -140,15 +186,13 @@ def preprocess_prompt(prompt, image1, image2, image3):
|
|
140 |
prompt += " 이미지를 생성해주세요. 이미지에 텍스트나 글자를 포함하지 마세요."
|
141 |
return prompt
|
142 |
|
143 |
-
def generate_with_images(prompt, images, variation_index=0
|
144 |
-
# API 키 파라미터 추가
|
145 |
try:
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
return None, "API 키가 설정되지 않았습니다. API 키를 입력하거나 환경변수를 확인해주세요."
|
150 |
|
151 |
-
client = genai.Client(api_key=
|
152 |
logger.info(f"Gemini API 요청 시작 - 프롬프트: {prompt}, 변형 인덱스: {variation_index}")
|
153 |
|
154 |
variation_suffixes = [
|
@@ -203,8 +247,7 @@ def generate_with_images(prompt, images, variation_index=0, api_key=None):
|
|
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
|
207 |
-
# API 키 파라미터 추가
|
208 |
retry_count = 0
|
209 |
last_error = None
|
210 |
|
@@ -218,7 +261,7 @@ def process_images_with_prompt(image1, image2, image3, prompt, variation_index=0
|
|
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
|
222 |
else:
|
223 |
final_prompt = processed_prompt
|
224 |
else:
|
@@ -232,7 +275,7 @@ def process_images_with_prompt(image1, image2, image3, prompt, variation_index=0
|
|
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
|
236 |
if result_img is not None:
|
237 |
return result_img, status, final_prompt
|
238 |
else:
|
@@ -248,8 +291,7 @@ def process_images_with_prompt(image1, image2, image3, prompt, variation_index=0
|
|
248 |
|
249 |
return None, f"최대 재시도 횟수({max_retries}회) 초과 후 실패: {last_error}", prompt
|
250 |
|
251 |
-
def generate_multiple_images(image1, image2, image3, prompt,
|
252 |
-
# API 키 파라미터 추가
|
253 |
results = []
|
254 |
statuses = []
|
255 |
prompts = []
|
@@ -261,7 +303,7 @@ def generate_multiple_images(image1, image2, image3, prompt, api_key=None, progr
|
|
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
|
265 |
|
266 |
if result_img is not None:
|
267 |
results.append(result_img)
|
@@ -451,30 +493,6 @@ body {
|
|
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);
|
@@ -605,85 +623,6 @@ body {
|
|
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;
|
@@ -760,74 +699,6 @@ body {
|
|
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;
|
@@ -865,64 +736,10 @@ 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 |
-
#
|
869 |
-
header_html = ""
|
870 |
-
|
871 |
-
|
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(
|
@@ -931,24 +748,17 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Default(
|
|
931 |
font=[gr.themes.GoogleFont("Noto Sans KR"), "ui-sans-serif", "system-ui"]
|
932 |
)) as demo:
|
933 |
gr.HTML(fontawesome_link)
|
934 |
-
|
|
|
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 |
-
#
|
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():
|
@@ -1039,8 +849,8 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Default(
|
|
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
|
@@ -1154,21 +964,20 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Default(
|
|
1154 |
outputs=prompt_input
|
1155 |
)
|
1156 |
|
1157 |
-
|
1158 |
-
|
1159 |
-
|
1160 |
-
return process_images_with_prompt(image1, image2, image3, prompt, 0, 3, api_key)
|
1161 |
|
1162 |
submit_single_btn.click(
|
1163 |
-
fn=
|
1164 |
-
inputs=[image1_input, image2_input, image3_input, prompt_input
|
1165 |
outputs=[output_image1, output_text, prompt_display],
|
1166 |
)
|
1167 |
|
1168 |
-
# 4장 이미지 생성 버튼 이벤트 연결 - API 키 입력값
|
1169 |
submit_btn.click(
|
1170 |
-
fn=
|
1171 |
-
inputs=[image1_input, image2_input, image3_input, prompt_input
|
1172 |
outputs=[output_image1, output_image2, output_image3, output_image4, output_text, prompt_display],
|
1173 |
)
|
1174 |
|
@@ -1220,5 +1029,7 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Default(
|
|
1220 |
outputs=download_output
|
1221 |
)
|
1222 |
|
|
|
|
|
1223 |
demo.queue()
|
1224 |
demo.launch(share=False, inbrowser=True, width="100%") # width 파라미터 추가
|
|
|
20 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
21 |
logger = logging.getLogger(__name__)
|
22 |
|
23 |
+
# ------------------- API 키 순환 시스템 -------------------
|
24 |
+
API_KEYS = [] # API 키 목록
|
25 |
+
current_key_index = 0 # 현재 사용 중인 키 인덱스
|
26 |
+
|
27 |
+
def initialize_api_keys():
|
28 |
+
"""API 키 목록을 초기화하는 함수"""
|
29 |
+
global API_KEYS
|
30 |
+
# 환경 변수에서 API 키 가져오기
|
31 |
+
key1 = os.environ.get("GEMINI_API_KEY_1", "")
|
32 |
+
key2 = os.environ.get("GEMINI_API_KEY_2", "")
|
33 |
+
key3 = os.environ.get("GEMINI_API_KEY_3", "")
|
34 |
+
key4 = os.environ.get("GEMINI_API_KEY_4", "")
|
35 |
+
key5 = os.environ.get("GEMINI_API_KEY_5", "")
|
36 |
+
|
37 |
+
# 빈 문자열이 아닌 키만 추가
|
38 |
+
if key1:
|
39 |
+
API_KEYS.append(key1)
|
40 |
+
if key2:
|
41 |
+
API_KEYS.append(key2)
|
42 |
+
if key3:
|
43 |
+
API_KEYS.append(key3)
|
44 |
+
if key4:
|
45 |
+
API_KEYS.append(key4)
|
46 |
+
if key5:
|
47 |
+
API_KEYS.append(key5)
|
48 |
+
|
49 |
+
# 기존 GEMINI_API_KEY가 있으면 추가
|
50 |
+
default_key = os.environ.get("GEMINI_API_KEY", "")
|
51 |
+
if default_key and default_key not in API_KEYS:
|
52 |
+
API_KEYS.append(default_key)
|
53 |
+
|
54 |
+
logger.info(f"API 키 {len(API_KEYS)}개가 로드되었습니다.")
|
55 |
+
|
56 |
+
def get_next_api_key():
|
57 |
+
"""다음 API 키를 가져오는 함수"""
|
58 |
+
global current_key_index
|
59 |
+
|
60 |
+
if not API_KEYS:
|
61 |
+
return None
|
62 |
+
|
63 |
+
# 현재 키 가져오기
|
64 |
+
api_key = API_KEYS[current_key_index]
|
65 |
+
|
66 |
+
# 다음 키 인덱스로 업데이트
|
67 |
+
current_key_index = (current_key_index + 1) % len(API_KEYS)
|
68 |
+
|
69 |
+
return api_key
|
70 |
+
|
71 |
# ========== 이미지 생성기 관련 함수 ==========
|
72 |
def save_binary_file(file_name, data):
|
73 |
with open(file_name, "wb") as f:
|
74 |
f.write(data)
|
75 |
|
76 |
+
def translate_prompt_to_english(prompt):
|
|
|
77 |
if not re.search("[가-힣]", prompt):
|
78 |
return prompt
|
79 |
|
|
|
82 |
prompt = prompt.replace("#3", "IMAGE_TAG_THREE")
|
83 |
|
84 |
try:
|
85 |
+
api_key = get_next_api_key()
|
86 |
+
if not api_key:
|
|
|
87 |
logger.error("Gemini API 키가 설정되지 않았습니다.")
|
88 |
prompt = prompt.replace("IMAGE_TAG_ONE", "#1")
|
89 |
prompt = prompt.replace("IMAGE_TAG_TWO", "#2")
|
90 |
prompt = prompt.replace("IMAGE_TAG_THREE", "#3")
|
91 |
return prompt
|
92 |
|
93 |
+
client = genai.Client(api_key=api_key)
|
94 |
translation_prompt = f"""
|
95 |
Translate the following Korean text to English:
|
96 |
|
|
|
186 |
prompt += " 이미지를 생성해주세요. 이미지에 텍스트나 글자를 포함하지 마세요."
|
187 |
return prompt
|
188 |
|
189 |
+
def generate_with_images(prompt, images, variation_index=0):
|
|
|
190 |
try:
|
191 |
+
api_key = get_next_api_key()
|
192 |
+
if not api_key:
|
193 |
+
return None, "API 키가 설정되지 않았습니다. 환경 변수에 GEMINI_API_KEY_1, GEMINI_API_KEY_2, GEMINI_API_KEY_3, GEMINI_API_KEY_4, GEMINI_API_KEY_5 중 하나 이상을 설정해주세요."
|
|
|
194 |
|
195 |
+
client = genai.Client(api_key=api_key)
|
196 |
logger.info(f"Gemini API 요청 시작 - 프롬프트: {prompt}, 변형 인덱스: {variation_index}")
|
197 |
|
198 |
variation_suffixes = [
|
|
|
247 |
logger.exception("이미지 생성 중 오류 발생:")
|
248 |
return None, f"오류 발생: {str(e)}"
|
249 |
|
250 |
+
def process_images_with_prompt(image1, image2, image3, prompt, variation_index=0, max_retries=3):
|
|
|
251 |
retry_count = 0
|
252 |
last_error = None
|
253 |
|
|
|
261 |
if prompt and prompt.strip():
|
262 |
processed_prompt = preprocess_prompt(prompt, image1, image2, image3)
|
263 |
if re.search("[가-힣]", processed_prompt):
|
264 |
+
final_prompt = translate_prompt_to_english(processed_prompt)
|
265 |
else:
|
266 |
final_prompt = processed_prompt
|
267 |
else:
|
|
|
275 |
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."
|
276 |
logger.info("Default prompt generated for three images")
|
277 |
|
278 |
+
result_img, status = generate_with_images(final_prompt, valid_images, variation_index)
|
279 |
if result_img is not None:
|
280 |
return result_img, status, final_prompt
|
281 |
else:
|
|
|
291 |
|
292 |
return None, f"최대 재시도 횟수({max_retries}회) 초과 후 실패: {last_error}", prompt
|
293 |
|
294 |
+
def generate_multiple_images(image1, image2, image3, prompt, progress=gr.Progress()):
|
|
|
295 |
results = []
|
296 |
statuses = []
|
297 |
prompts = []
|
|
|
303 |
|
304 |
for i in range(num_images):
|
305 |
progress((i / num_images), desc=f"{i+1}/{num_images} 이미지 생성 중...")
|
306 |
+
result_img, status, final_prompt = process_images_with_prompt(image1, image2, image3, prompt, i, max_retries)
|
307 |
|
308 |
if result_img is not None:
|
309 |
results.append(result_img)
|
|
|
493 |
max-width: 100% !important;
|
494 |
}
|
495 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
496 |
/* 패널 스타일링 */
|
497 |
.gr-group {
|
498 |
background-color: var(--card-bg);
|
|
|
623 |
margin-bottom: 1.2rem;
|
624 |
}
|
625 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
626 |
/* 메인 컨텐츠 스크롤바 */
|
627 |
::-webkit-scrollbar {
|
628 |
width: 8px;
|
|
|
699 |
animation: fadeIn 0.5s ease-out;
|
700 |
}
|
701 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
702 |
/* Examples 섹션 스타일 */
|
703 |
.examples-section {
|
704 |
display: grid;
|
|
|
736 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
737 |
"""
|
738 |
|
739 |
+
# 제목과 사용 가이드 제거
|
740 |
+
header_html = ""
|
741 |
+
image_generator_guide_html = ""
|
742 |
+
image_editor_guide_html = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
743 |
|
744 |
# UI 구성
|
745 |
with gr.Blocks(css=custom_css, theme=gr.themes.Default(
|
|
|
748 |
font=[gr.themes.GoogleFont("Noto Sans KR"), "ui-sans-serif", "system-ui"]
|
749 |
)) as demo:
|
750 |
gr.HTML(fontawesome_link)
|
751 |
+
# 제목 제거
|
752 |
+
# gr.HTML(header_html)
|
753 |
|
754 |
with gr.Tabs(elem_classes="tabs") as tabs:
|
755 |
# 이미지 생성기 탭
|
756 |
with gr.Tab("✨ 이미지 생성기", elem_classes="tab-content"):
|
757 |
+
# 사용 가이드 섹션 제거
|
758 |
+
# gr.HTML(image_generator_guide_html)
|
759 |
with gr.Row(equal_height=True):
|
760 |
with gr.Column(scale=1):
|
761 |
+
# API 키 입력 섹션 제거
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
762 |
|
763 |
# ======== 이미지 업로드 및 설정 섹션 ========
|
764 |
with gr.Group():
|
|
|
849 |
|
850 |
# 이미지 편집기 탭 추가
|
851 |
with gr.Tab("🎨 이미지 편집기", elem_classes="tab-content"):
|
852 |
+
# 사용 가이드 섹션 제거
|
853 |
+
# gr.HTML(image_editor_guide_html)
|
854 |
|
855 |
with gr.Row():
|
856 |
# 왼쪽 열: 비율 1
|
|
|
964 |
outputs=prompt_input
|
965 |
)
|
966 |
|
967 |
+
# 단일 이미지 생성 버튼 이벤트 연결 - API 키 입력값 제거
|
968 |
+
def generate_single_image(image1, image2, image3, prompt):
|
969 |
+
return process_images_with_prompt(image1, image2, image3, prompt, 0, 3)
|
|
|
970 |
|
971 |
submit_single_btn.click(
|
972 |
+
fn=generate_single_image,
|
973 |
+
inputs=[image1_input, image2_input, image3_input, prompt_input],
|
974 |
outputs=[output_image1, output_text, prompt_display],
|
975 |
)
|
976 |
|
977 |
+
# 4장 이미지 생성 버튼 이벤트 연결 - API 키 입력값 제거
|
978 |
submit_btn.click(
|
979 |
+
fn=generate_multiple_images,
|
980 |
+
inputs=[image1_input, image2_input, image3_input, prompt_input],
|
981 |
outputs=[output_image1, output_image2, output_image3, output_image4, output_text, prompt_display],
|
982 |
)
|
983 |
|
|
|
1029 |
outputs=download_output
|
1030 |
)
|
1031 |
|
1032 |
+
# API 키 초기화 및 애플리케이션 실행
|
1033 |
+
initialize_api_keys() # API 키 초기화 함수 호출
|
1034 |
demo.queue()
|
1035 |
demo.launch(share=False, inbrowser=True, width="100%") # width 파라미터 추가
|