import os import gradio as gr from dotenv import load_dotenv import logging import time import uuid import google.generativeai as genai import random # 로깅 설정 - INFO 레벨로 변경 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # 환경 변수 로드 load_dotenv() # 환경변수에서 API 키 설정 로드 def get_gemini_api_configs(): """환경변수에서 Gemini API 키 설정을 로드""" api_configs_str = os.getenv('GEMINI_API_CONFIGS', '') if not api_configs_str: logger.error("GEMINI_API_CONFIGS 환경변수가 설정되지 않았습니다.") return [] try: # 환경변수 값을 exec로 실행하여 설정 로드 local_vars = {} exec(api_configs_str, {}, local_vars) return local_vars.get('API_KEYS_LIST', []) except Exception as e: logger.error(f"환경변수 파싱 오류: {e}") return [] # API 키 관리를 위한 전역 변수 api_key_manager = { 'keys': [], 'current_index': -1, 'failed_keys': set(), # 실패한 키들을 추적 'is_initialized': False } # API 키 로드 함수 (개선된 버전) def load_api_keys(): """환경변수에서 API 키를 로드합니다.""" # 환경변수에서 API 키 목록 가져오기 api_keys_from_env = get_gemini_api_configs() # 빈 키나 플레이스홀더 제거 api_keys = [ key.strip() for key in api_keys_from_env if key and key.strip() and not key.startswith("YOUR_") and not key.startswith("your_") ] # 중복 제거 api_keys = list(dict.fromkeys(api_keys)) if not api_keys: logger.error("API 키가 설정되지 않았습니다. GEMINI_API_CONFIGS 환경변수에 실제 API 키를 추가하세요.") raise ValueError("API 키가 설정되지 않았습니다. GEMINI_API_CONFIGS 환경변수에 실제 API 키를 추가해주세요.") logger.info(f"총 {len(api_keys)}개의 API 키가 로드되었습니다.") return api_keys def initialize_api_keys(): """API 키 관리자를 초기화합니다.""" global api_key_manager if api_key_manager['is_initialized']: return try: api_key_manager['keys'] = load_api_keys() api_key_manager['is_initialized'] = True logger.info("API 키 관리자 초기화 완료") except Exception as e: logger.error(f"API 키 초기화 실패: {str(e)}") raise e def get_next_api_key(): """다음 사용할 API 키를 반환합니다. 실패한 키는 건너뜁니다.""" global api_key_manager # 초기화 확인 if not api_key_manager['is_initialized']: initialize_api_keys() available_keys = [ key for i, key in enumerate(api_key_manager['keys']) if i not in api_key_manager['failed_keys'] ] if not available_keys: # 모든 키가 실패했으면 실패 목록을 초기화하고 다시 시도 logger.warning("모든 API 키가 실패했습니다. 실패 목록을 초기화하고 다시 시도합니다.") api_key_manager['failed_keys'].clear() available_keys = api_key_manager['keys'] # 첫 번째 사용은 랜덤으로 선택 if api_key_manager['current_index'] == -1: available_indices = [ i for i, key in enumerate(api_key_manager['keys']) if i not in api_key_manager['failed_keys'] ] api_key_manager['current_index'] = random.choice(available_indices) logger.info(f"첫 번째 API 키 선택: 랜덤 인덱스 {api_key_manager['current_index'] + 1}") else: # 이후 사용은 순차적으로 다음 키 선택 (실패한 키는 건너뜀) original_index = api_key_manager['current_index'] for _ in range(len(api_key_manager['keys'])): api_key_manager['current_index'] = (api_key_manager['current_index'] + 1) % len(api_key_manager['keys']) if api_key_manager['current_index'] not in api_key_manager['failed_keys']: break if api_key_manager['current_index'] in api_key_manager['failed_keys']: # 모든 키를 시도했지만 사용할 수 있는 키가 없음 logger.warning("사용 가능한 API 키가 없습니다. 실패 목록을 초기화합니다.") api_key_manager['failed_keys'].clear() api_key_manager['current_index'] = 0 logger.info(f"다음 API 키 선택: 인덱스 {api_key_manager['current_index'] + 1}") return api_key_manager['keys'][api_key_manager['current_index']] def mark_api_key_failed(api_key): """API 키를 실패 목록에 추가합니다.""" global api_key_manager try: key_index = api_key_manager['keys'].index(api_key) api_key_manager['failed_keys'].add(key_index) logger.warning(f"API 키 인덱스 {key_index + 1}를 실패 목록에 추가했습니다.") except ValueError: logger.error("실패한 API 키를 목록에서 찾을 수 없습니다.") def test_api_key(api_key): """API 키가 유효한지 테스트합니다.""" try: genai.configure(api_key=api_key) model = genai.GenerativeModel(model_name="gemini-2.0-flash") # 간단한 테스트 요청 response = model.generate_content("Test", generation_config={ "max_output_tokens": 10, "temperature": 0.1, }) if response and response.text: return True return False except Exception as e: logger.error(f"API 키 테스트 실패: {str(e)}") return False def get_working_api_key(): """작동하는 API 키를 찾아 반환합니다.""" max_attempts = len(api_key_manager['keys']) if api_key_manager['is_initialized'] else 5 for attempt in range(max_attempts): try: api_key = get_next_api_key() if test_api_key(api_key): logger.info(f"작동하는 API 키를 찾았습니다. (시도 {attempt + 1}회)") return api_key else: mark_api_key_failed(api_key) logger.warning(f"API 키 테스트 실패. 다음 키로 시도합니다. (시도 {attempt + 1}회)") except Exception as e: logger.error(f"API 키 가져오기 실패: {str(e)}") raise Exception("사용 가능한 API 키를 찾을 수 없습니다.") # 모델별 프롬프트 정의 GEMINI_PROMPTS = { "맞춤법 검사기": """ # 목적: 한국어 문법 교정 및 개선 ## 지침 1. 주어진 텍스트의 문법을 전문적으로 검토하고 교정하세요. 2. 원본 텍스트의 의미와 의도를 철저히 유지하면서 문법적인 부분만 수정하세요. 3. 다음 요소들을 한국어 문법 규칙에 맞게 정확하게 수정하세요: - 맞춤법 오류 - 띄어쓰기 오류 - 문장 부호 사용 - 조사 사용의 정확성 - 어미 활용의 정확성 - 문장 성분의 호응 관계 4. 절대 원본 텍스트에 없는 내용을 추가하거나 의미를 변경하지 마세요. 5. 부가 설명이나 메타 정보(예: "수정된 텍스트:", "개선된 텍스트:" 등)는 출력하지 마세요. 6. 오직 교정된 텍스트만 출력하세요. """, "글 다듬기": """ # 목적: 문장 구조와 표현 개선 ## 지침 1. 문장 구조 최적화 - 불필요한 문구와 중복 표현을 제거하여 간결성 확보 - 주어-서술어 관계를 명확히 하여 의미 전달력 강화 - 과도하게 긴 문장은 적절히 분리하여 가독성 향상 - 문장 간의 논리적 연결성 확보 2. 단락 구성 최적화 - 각 단락이 명확한 핵심 주제를 가지도록 조정 - 단락 간 자연스러운 흐름을 위한 연결어 사용 - 단락 길이의 균형 유지 3. 어휘 및 표현 개선 - 불필요한 외래어는 적절한 한국어로 대체 - 추상적이거나 모호한 표현은 구체적이고 명확한 용어로 대체 - 중복되는 표현 제거로 문장 효율성 증대 - 관용적 표현과 적절한 비유를 활용하여 표현력 강화 4. 전달력 강화 - 핵심 메시지가 돋보이도록 문장 구조 재배치 - 논리적 흐름과 일관성 확보 - 독자의 이해도를 고려한 표현 선택 5. 일관성 유지 - 문체와 톤의 일관성 유지 - 전문 용어와 표현의 통일성 확보 - 시제의 일관된 사용 ## 출력 형식 - 원본 텍스트와 동일한 말투와 어조를 유지하되, 위 기준에 따라 개선된 텍스트만 출력 - 부가 설명이나 메타 정보는 포함하지 않음 """, "명언 인용하기": """ # 목적: 명언을 활용한 설득력 있는 텍스트 개선 ## 지침 1. 문맥 분석 - 텍스트의 핵심 주제와 의도 파악 - 텍스트의 분위기와 톤 분석 - 독자층과 목적 고려 2. 적절한 명언 선택 - 텍스트의 주제와 직접적으로 연관된 명언 선택 - 다양한 분야(철학, 문학, 역사, 과학 등)의 명언 고려 - 고전적인 명언뿐만 아니라 현대 유명인(연예인, 기업인, 운동선수, 정치인 등)의 인상적인 말도 적극 활용 - 한국 및 세계의 저명한 인물의 명언 활용 - 텍스트의 톤과 분위기에 어울리는 명언 선택 - 가능하면 시의적절하고 최신 트렌드를 반영한 현대적 명언 우선 고려 3. 명언 통합 - 텍스트 내 가장 효과적인 위치에 명언 배치 (시작, 중간, 또는 결론) - 명언을 자연스럽게 도입하여 기존 텍스트와 조화롭게 통합 - 필요시 명언의 출처(인물 이름) 포함 - 딱 한 번만 명언 인용하기 - 현대 유명인의 명언 사용 시 간단한 맥락 설명 고려(필요한 경우) 4. 전체 텍스트 조정 - 명언 도입 후 필요시 전후 문장 조정하여 자연스러운 흐름 유지 - 원본 텍스트의 핵심 메시지와 의도 보존 - 전체적인 일관성과 응집력 유지 - 명언이 텍스트에 신선함과 권위를 부여하는지 확인 ## 출력 형식 - 명언이 자연스럽게 통합된 개선된 텍스트만 출력 - 부가 설명이나 메타 정보 없이 순수하게 개선된 텍스트만 제공 """, "맞춤형 변환": """ # 목적: 사용자 정의 페르소나와 목적에 맞는 텍스트 변환 ## 지침 1. 페르소나 분석 및 적용 - 사용자가 제공한 페르소나의 특성 분석 (말투, 어휘 선택, 문장 구조 등) - 해당 페르소나가 실제로 작성했을 법한 스타일로 텍스트 재구성 - 페르소나의 고유한 표현 방식과 관점 반영 2. 목적 분석 및 적용 - 사용자가 명시한 목적에 맞게 텍스트의 전반적인 방향성 조정 - 목적에 맞는 구조, 논조, 강조점 설정 - 목적 달성에 필요한 요소 강화 및 불필요한 요소 제거 3. 텍스트 맞춤 변환 - 페르소나와 목적을 종합적으로 고려한 텍스트 변환 - 원본 텍스트의 핵심 정보와 주요 논점 유지 - 어휘, 문장 구조, 비유, 예시 등을 페르소나와 목적에 맞게 조정 - 전체적인 일관성과 진정성 유지 4. 품질 및 효과 최적화 - 명확성과 이해도 향상 - 설득력과 호소력 강화 - 독창성과 참신함 추구 - 목표 독자에게 효과적으로 전달될 수 있는 형태로 최적화 ## 출력 형식 - 페르소나와 목적에 맞게 변형된 텍스트만 출력 - 부가 설명이나 메타 정보 없이 순수하게 변환된 텍스트만 제공 - 사용자가 명시한 목적과 페르소나의 특성이 명확히 드러나는 결과물 제공 """, } def process_text(input_text, improvement_type, custom_purpose, persona, temperature, top_p): """Gemini 모델용 텍스트 처리 함수""" try: print("텍스트 처리 시작") request_id = str(uuid.uuid4())[:8] timestamp_micro = int(time.time() * 1000000) % 1000 if improvement_type == "맞춤형 변환": purpose = f"다음 페르소나를 가지고 텍스트를 다듬으세요: {persona}\n\n목적: {custom_purpose}" else: purpose = GEMINI_PROMPTS[improvement_type] # 작동하는 API 키 가져오기 selected_api_key = get_working_api_key() logger.info(f"API 키 선택 완료: {selected_api_key[:5]}...") # 선택된 API 키로 Gemini 구성 genai.configure(api_key=selected_api_key) model = genai.GenerativeModel( model_name="gemini-2.0-flash", generation_config={ "temperature": temperature, "top_p": top_p, "max_output_tokens": 2000, } ) # 시스템 지시사항을 첫 번째 사용자 메시지에 포함 prompt = f"REQ-{request_id}-{timestamp_micro}\n\n다음 텍스트를 목적에 맞게 다듬어주세요.\n\n텍스트: {input_text}\n\n목적: {purpose}" response = model.generate_content(prompt) print("텍스트 처리 완료") return response.text except Exception as e: print("텍스트 처리 실패") logger.error(f"처리 중 오류 발생: {str(e)}") # API 키 문제인 경우 해당 키를 실패 목록에 추가 if "api" in str(e).lower() or "key" in str(e).lower() or "quota" in str(e).lower(): try: current_key = api_key_manager['keys'][api_key_manager['current_index']] mark_api_key_failed(current_key) logger.info("API 키 문제로 인한 오류. 다음 키로 재시도하세요.") except: pass return f"오류 발생: {str(e)}" def create_interface(): # FontAwesome 아이콘 포함 fontawesome = """ """ # 다크모드 적용 CSS 스타일 css = """ /* ============================================ 다크모드 자동 변경 CSS ============================================ */ /* 1. CSS 변수 정의 (라이트모드 - 기본값) */ :root { /* 메인 컬러 */ --primary-color: #FB7F0D; --secondary-color: #ff9a8b; --accent-color: #FF6B6B; /* 배경 컬러 */ --background-color: #FFFFFF; --card-bg: #ffffff; --input-bg: #ffffff; /* 텍스트 컬러 */ --text-color: #334155; --text-secondary: #64748b; /* 보더 및 구분선 */ --border-color: #dddddd; --border-light: #e5e5e5; /* 테이블 컬러 */ --table-even-bg: #f3f3f3; --table-hover-bg: #f0f0f0; /* 그림자 */ --shadow: 0 8px 30px rgba(251, 127, 13, 0.08); --shadow-light: 0 2px 4px rgba(0, 0, 0, 0.1); /* 기타 */ --border-radius: 18px; /* 가이드 컨테이너 */ --guide-bg: #FFF6F0; --guide-border: rgba(255, 127, 0, 0.1); } /* 2. 다크모드 색상 변수 (자동 감지) */ @media (prefers-color-scheme: dark) { :root { /* 배경 컬러 */ --background-color: #1a1a1a; --card-bg: #2d2d2d; --input-bg: #2d2d2d; /* 텍스트 컬러 */ --text-color: #e5e5e5; --text-secondary: #a1a1aa; /* 보더 및 구분선 */ --border-color: #404040; --border-light: #525252; /* 테이블 컬러 */ --table-even-bg: #333333; --table-hover-bg: #404040; /* 그림자 */ --shadow: 0 8px 30px rgba(0, 0, 0, 0.3); --shadow-light: 0 2px 4px rgba(0, 0, 0, 0.2); /* 가이드 컨테이너 */ --guide-bg: #2a2a2a; --guide-border: rgba(255, 127, 0, 0.2); } } /* 3. 수동 다크모드 클래스 (Gradio 토글용) */ [data-theme="dark"], .dark, .gr-theme-dark { /* 배경 컬러 */ --background-color: #1a1a1a; --card-bg: #2d2d2d; --input-bg: #2d2d2d; /* 텍스트 컬러 */ --text-color: #e5e5e5; --text-secondary: #a1a1aa; /* 보더 및 구분선 */ --border-color: #404040; --border-light: #525252; /* 테이블 컬러 */ --table-even-bg: #333333; --table-hover-bg: #404040; /* 그림자 */ --shadow: 0 8px 30px rgba(0, 0, 0, 0.3); --shadow-light: 0 2px 4px rgba(0, 0, 0, 0.2); /* 가이드 컨테이너 */ --guide-bg: #2a2a2a; --guide-border: rgba(255, 127, 0, 0.2); } /* 4. 기본 요소 다크모드 적용 */ body { font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif; background-color: var(--background-color) !important; color: var(--text-color) !important; line-height: 1.6; transition: background-color 0.3s ease, color 0.3s ease; } footer { visibility: hidden; } /* 5. Gradio 컨테이너 강제 적용 */ .gradio-container, .gradio-container *, .gr-app, .gr-app *, .gr-interface { background-color: var(--background-color) !important; color: var(--text-color) !important; } .container { max-width: 1200px; margin: 0 auto; } .header { background: linear-gradient(135deg, #FB7F0D, #FF9A5B); padding: 2rem; border-radius: 15px; margin-bottom: 20px; box-shadow: var(--shadow); text-align: center; color: white; } .header h1 { margin: 0; font-size: 2.5rem; font-weight: 700; } .header p { margin: 10px 0 0; font-size: 1.2rem; opacity: 0.9; } /* 6. 카드 및 패널 스타일 */ .card, .gr-form, .gr-box, .gr-panel, .custom-frame, [class*="frame"], [class*="panel"] { background-color: var(--card-bg) !important; border-radius: var(--border-radius); padding: 20px; margin: 10px 0; box-shadow: var(--shadow); border: 1px solid var(--border-color) !important; color: var(--text-color) !important; } .button-primary { border-radius: 30px !important; background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important; color: white !important; font-size: 18px !important; padding: 10px 20px !important; border: none; box-shadow: 0 4px 8px rgba(251, 127, 13, 0.25); transition: transform 0.3s ease; text-align: center; font-weight: 600; } .button-primary:hover { transform: translateY(-2px); box-shadow: 0 6px 12px rgba(251, 127, 13, 0.3); } .section-title { display: flex; align-items: center; font-size: 20px; font-weight: 700; color: var(--text-color) !important; margin-bottom: 15px; padding-bottom: 8px; border-bottom: 2px solid var(--primary-color); } .section-title i { margin-right: 10px; color: var(--primary-color); } .guide-container { background-color: var(--guide-bg) !important; border-radius: var(--border-radius); padding: 1.5rem; margin-bottom: 1.5rem; border: 1px solid var(--guide-border) !important; color: var(--text-color) !important; } .guide-title { font-size: 1.3rem; font-weight: 700; color: var(--primary-color) !important; margin-bottom: 1rem; display: flex; align-items: center; } .guide-title i { margin-right: 0.8rem; font-size: 1.3rem; } .guide-item { display: flex; margin-bottom: 0.8rem; align-items: flex-start; } .guide-number { background-color: var(--primary-color); color: white; width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; margin-right: 10px; flex-shrink: 0; font-size: 14px; } .guide-text { flex: 1; line-height: 1.6; color: var(--text-color) !important; } .feature-tag { display: inline-block; background-color: rgba(255, 127, 0, 0.1); color: var(--primary-color); padding: 3px 10px; border-radius: 12px; font-size: 14px; font-weight: 600; margin-right: 8px; margin-bottom: 8px; } .input-label { font-weight: 600; margin-bottom: 8px; color: var(--text-color) !important; } /* 7. 입력 필드 스타일 */ input[type="text"], input[type="number"], input[type="email"], input[type="password"], textarea, select, .gr-input, .gr-text-input, .gr-textarea, .gr-dropdown { background-color: var(--input-bg) !important; color: var(--text-color) !important; border-color: var(--border-color) !important; border-radius: var(--border-radius) !important; border: 1px solid var(--border-color) !important; padding: 12px !important; transition: all 0.3s ease !important; } input[type="text"]:focus, input[type="number"]:focus, input[type="email"]:focus, input[type="password"]:focus, textarea:focus, select:focus, .gr-input:focus, .gr-text-input:focus, .gr-textarea:focus, .gr-dropdown:focus { border-color: var(--primary-color) !important; outline: none !important; box-shadow: 0 0 0 2px rgba(251, 127, 13, 0.2) !important; } /* 8. 라벨 및 텍스트 요소 */ label, .gr-label, .gr-checkbox label, .gr-radio label, p, span, div { color: var(--text-color) !important; } /* 9. 테이블 스타일 */ table { background-color: var(--card-bg) !important; color: var(--text-color) !important; border-color: var(--border-color) !important; } table th { background-color: var(--primary-color) !important; color: white !important; border-color: var(--border-color) !important; } table td { background-color: var(--card-bg) !important; color: var(--text-color) !important; border-color: var(--border-color) !important; } table tbody tr:nth-child(even) { background-color: var(--table-even-bg) !important; } table tbody tr:hover { background-color: var(--table-hover-bg) !important; } /* 10. 체크박스 및 라디오 버튼 */ input[type="checkbox"], input[type="radio"] { accent-color: var(--primary-color) !important; } /* 11. 스크롤바 스타일 */ ::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-track { background: var(--card-bg); border-radius: 10px; } ::-webkit-scrollbar-thumb { background: var(--primary-color); border-radius: 10px; } ::-webkit-scrollbar-thumb:hover { background: var(--secondary-color); } /* 12. 아코디언 및 드롭다운 */ details { background-color: var(--card-bg) !important; border-color: var(--border-color) !important; color: var(--text-color) !important; } details summary { background-color: var(--card-bg) !important; color: var(--text-color) !important; } /* 13. 툴팁 및 팝업 */ [data-tooltip]:hover::after, .tooltip, .popup { background-color: var(--card-bg) !important; color: var(--text-color) !important; border-color: var(--border-color) !important; box-shadow: var(--shadow-light) !important; } /* 14. 모달 및 오버레이 */ .modal, .overlay, [class*="modal"], [class*="overlay"] { background-color: var(--card-bg) !important; color: var(--text-color) !important; border-color: var(--border-color) !important; } /* 15. 추가 Gradio 컴포넌트들 */ .gr-block, .gr-group, .gr-row, .gr-column { background-color: var(--background-color) !important; color: var(--text-color) !important; } /* 16. 버튼은 기존 스타일 유지 (primary-color 사용) */ button:not([class*="custom"]):not([class*="primary"]):not([class*="secondary"]) { background-color: var(--card-bg) !important; color: var(--text-color) !important; border-color: var(--border-color) !important; } /* 17. 코드 블록 및 pre 태그 */ code, pre, .code-block { background-color: var(--table-even-bg) !important; color: var(--text-color) !important; border-color: var(--border-color) !important; } /* 18. 알림 및 메시지 */ .alert, .message, .notification, [class*="alert"], [class*="message"], [class*="notification"] { background-color: var(--card-bg) !important; color: var(--text-color) !important; border-color: var(--border-color) !important; } /* 19. 전환 애니메이션 */ * { transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease !important; } /* 20. 추가 Gradio 스타일 보완 */ .gr-sample-inputs { border-radius: var(--border-radius) !important; border: 1px solid var(--border-color) !important; padding: 12px !important; background-color: var(--input-bg) !important; color: var(--text-color) !important; } /* 21. 슬라이더 스타일 */ .gr-slider input[type="range"] { accent-color: var(--primary-color) !important; } /* 22. 라디오 버튼 그룹 */ .gr-radio-group { background-color: var(--card-bg) !important; color: var(--text-color) !important; border-color: var(--border-color) !important; } /* 23. 체크박스 그룹 */ .gr-checkbox-group { background-color: var(--card-bg) !important; color: var(--text-color) !important; border-color: var(--border-color) !important; } """ with gr.Blocks(css=css, theme=gr.themes.Soft( primary_hue=gr.themes.Color( c50="#FFF7ED", c100="#FFEDD5", c200="#FED7AA", c300="#FDBA74", c400="#FB923C", c500="#F97316", c600="#EA580C", c700="#C2410C", c800="#9A3412", c900="#7C2D12", c950="#431407", ), secondary_hue="zinc", neutral_hue="zinc", font=("Pretendard", "sans-serif") )) as demo: gr.HTML(fontawesome) with gr.Row(): with gr.Column(scale=1): # 왼쪽 입력 영역 with gr.Column(elem_classes="card"): gr.HTML('
원본 텍스트
') input_text = gr.Textbox( label="", placeholder="여기에 개선하고 싶은 텍스트를 입력하세요...", lines=15 ) with gr.Column(elem_classes="card"): gr.HTML('
변환 설정
') improvement_type = gr.Radio( choices=["맞춤법 검사기", "글 다듬기", "명언 인용하기", "맞춤형 변환"], label="변환 유형 선택", value="맞춤법 검사기" ) # 맞춤형 변환 옵션들 custom_purpose = gr.Textbox( label="변환 목적", placeholder="텍스트를 어떤 방향으로 변환하고 싶으신지 설명해주세요...", lines=2, visible=False ) persona = gr.Textbox( label="페르소나 설정", placeholder="어떤 역할이나 성격을 가진 글쓰기 스타일을 원하시나요? (예: 유머러스한 개그맨, 전문적인 분석가)", lines=2, visible=False ) with gr.Row(visible=False) as generation_settings: temperature = gr.Slider( minimum=0.0, maximum=1.0, value=0.7, step=0.1, label="창의성 수준", info="높을수록 더 창의적인 결과가 생성됩니다" ) top_p = gr.Slider( minimum=0.0, maximum=1.0, value=0.9, step=0.1, label="다양성 수준", info="높을수록 더 다양한 표현을 사용합니다" ) submit_btn = gr.Button( "✨ 텍스트 변환하기", elem_classes="button-primary" ) with gr.Column(scale=1): # 오른쪽 출력 영역 with gr.Column(elem_classes="card"): gr.HTML('
변환된 텍스트
') output_text = gr.Textbox( label="", lines=22, show_copy_button=True ) def update_custom_inputs_visibility(choice): is_custom = choice == "맞춤형 변환" return { custom_purpose: gr.update(visible=is_custom), persona: gr.update(visible=is_custom), generation_settings: gr.update(visible=is_custom) } improvement_type.change( fn=update_custom_inputs_visibility, inputs=[improvement_type], outputs=[custom_purpose, persona, generation_settings] ) submit_btn.click( fn=process_text, inputs=[input_text, improvement_type, custom_purpose, persona, temperature, top_p], outputs=output_text, api_name="improve_text" ) return demo if __name__ == "__main__": logger.info("애플리케이션 시작") demo = create_interface() demo.queue() demo.launch()