Spaces:
Running
Running
| 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 = """ | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" /> | |
| """ | |
| # λ€ν¬λͺ¨λ μ μ© 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('<div class="section-title"><i class="fas fa-edit"></i> μλ³Έ ν μ€νΈ</div>') | |
| input_text = gr.Textbox( | |
| label="", | |
| placeholder="μ¬κΈ°μ κ°μ νκ³ μΆμ ν μ€νΈλ₯Ό μ λ ₯νμΈμ...", | |
| lines=15 | |
| ) | |
| with gr.Column(elem_classes="card"): | |
| gr.HTML('<div class="section-title"><i class="fas fa-sliders"></i> λ³ν μ€μ </div>') | |
| 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('<div class="section-title"><i class="fas fa-wand-magic-sparkles"></i> λ³νλ ν μ€νΈ</div>') | |
| 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() |