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() |