G378DFE4AS / app.py
ssboost's picture
Update app.py
606f0ee verified
import os
import gradio as gr
import time
import logging
import random
import uuid
from datetime import datetime
from langdetect import detect, DetectorFactory
import google.generativeai as genai
from dotenv import load_dotenv
DetectorFactory.seed = 0
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
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 ν‚€λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.")
# ν•œκ΅­μ–΄ μžμ—°μŠ€λŸ½κ²Œ 쑰건 (ν•œ-영 λ²ˆμ—­μš©)
KO_EN_CONDITIONS = """
μΆ”κ°€ 쑰건 (ν•œκ΅­μ–΄ μžμ—°μŠ€λŸ½κ²Œ ν•˜κΈ°):
- λ²ˆμ—­μ²΄κ°€ μ•„λ‹Œ μžμ—°μŠ€λŸ½κ³  λ§€λ„λŸ¬μš΄ ν•œκ΅­μ–΄λ₯Ό μ΅œμš°μ„ μ μœΌλ‘œ μž‘μ„±
- μ•„λž˜ 쑰건을 λ”°λΌμ£Όμ„Έμš”:
1. 문법적 μ •ν™•μ„±
- 피동문보닀 λŠ₯동문 μš°μ„ 
- λŒ€λͺ…사 μ΅œμ†Œν™”
- λͺ…μ‚¬ν˜•λ³΄λ‹€ 동사/ν˜•μš©μ‚¬ μš°μ„ 
- ν˜„μž¬μ§„ν–‰ν˜•λ³΄λ‹€ λ‹¨μˆœν˜„μž¬λ‚˜ μ™„λ£Œν˜• μš°μ„ 
- μ£Όμ–΄-λͺ©μ μ–΄-μ„œμˆ μ–΄ μˆœμ„œ μœ μ§€
- "~μ—ˆμ–΄μš”" ν‘œν˜„ μ§€μ–‘
2. 주제 λ¬Έλ§₯에 λ§žλŠ” μžμ—°μŠ€λŸ¬μš΄ 흐름
3. μ£Όμ œμ™€ 상황에 μ–΄μšΈλ¦¬λŠ” μ–΄νœ˜ 선택
4. 문화적 적합성 κ³ λ €
5. μ •μ„œμ  λ‰˜μ•™μŠ€ 반영 (곡감 μ΄λ„λŠ” ν‘œν˜„)
6. 직역/μ˜μ—­ κ· ν˜• μœ μ§€
7. 주제 ν‚€μ›Œλ“œ 반볡 μ–ΈκΈ‰ κΈˆμ§€
8. AIκ°€ μ“΄ κΈ€μ²˜λŸΌ 보이지 μ•Šλ„λ‘ 주의
"""
# μ‹œμŠ€ν…œ λ©”μ‹œμ§€ (ν•œ-쀑 λ²ˆμ—­μš©)
KO_ZH_SYSTEM_MESSAGE = """당신은 ν•œκ΅­μ–΄-쀑ꡭ어 μ–‘λ°©ν–₯ λ²ˆμ—­ μ–΄μ‹œμŠ€ν„΄νŠΈμž…λ‹ˆλ‹€.
μ•„λž˜ 쑰건을 μΆ©μ‹€νžˆ λ”°λ₯΄μ„Έμš”.
[λͺ©μ  및 상황]
- μ‚¬μš©μž λͺ©μ : 1688 μ†Œμ‹±, 도맀, μœ ν†΅ κ΄€λ ¨λœ μƒν™©μ—μ„œ μ–Έμ–΄ μž₯λ²½ 없이 μ›ν™œν•œ μ†Œν†΅μ„ λ•λŠ”λ‹€.
- μ‚¬μš©μž μž…λ ₯이 ν•œκ΅­μ–΄μΌ 경우 μžμ—°μŠ€λŸ½κ³  λ§₯락에 맞게 μ€‘κ΅­μ–΄λ‘œ λ²ˆμ—­ν•œλ‹€.
- μ‚¬μš©μž μž…λ ₯이 쀑ꡭ어일 경우 μžμ—°μŠ€λŸ½κ³  λ§₯락에 맞게 ν•œκ΅­μ–΄λ‘œ λ²ˆμ—­ν•œλ‹€.
[ν•œκ΅­μ–΄ μžμ—°μŠ€λŸ½κ²Œ 쑰건정리]
1. 문법적 μ •ν™•μ„±
- 피동문 λŒ€μ‹  λŠ₯동문
- λŒ€λͺ…사 μ΅œμ†Œν™”
- λͺ…μ‚¬ν˜•λ³΄λ‹€ 동사 및 ν˜•μš©μ‚¬ μš°μ„ 
- λ‹¨μˆœν˜„μž¬ν˜•μ΄λ‚˜ μ™„λ£Œν˜• μš°μ„ 
- λ¬Έμž₯ κ΅¬μ‘°λŠ” μ£Όμ–΄-λͺ©μ μ–΄-동사
- "~μ—ˆμ–΄μš”" ν˜•νƒœ μ‚¬μš© μ§€μ–‘
2. 주제 λ§₯락에 λ§žλŠ” μžμ—°μŠ€λŸ¬μš΄ ν‘œν˜„
3. μ μ ˆν•œ μ–΄νœ˜ 선택(μ†Œμ‹±, 도맀, μœ ν†΅ 상황 κ³ λ €)
4. 문화적, 상황적 적합성
5. μ •μ„œμ  λ‰˜μ•™μŠ€ μ‘°ν™”
6. 직역과 μ˜μ—­μ˜ κ· ν˜•
7. 주제 ν‚€μ›Œλ“œ 반볡 κΈˆμ§€
8. 생성 AI ν‹° λ‚˜μ§€ μ•Šλ„λ‘ 주의
[좔가사항]
- λ²ˆμ—­ μ‹œ μ–΄μƒ‰ν•œ λ²ˆμ—­μ²΄λ₯Ό ν”Όν•˜κ³  μžμ—°μŠ€λŸ¬μš΄ ν‘œν˜„μ„ μ‚¬μš©ν•œλ‹€.
- 좜λ ₯은 λ²ˆμ—­λœ 결과문만 μ œμ‹œν•œλ‹€.
"""
# 1μ°¨ μΉ΄ν…Œκ³ λ¦¬ 및 2μ°¨ 질문 λͺ©λ‘ μ •μ˜
categories = {
"κΈ°λ³Έ 인사 및 거래 μ‹œμž‘": [
"μ•ˆλ…•ν•˜μ„Έμš”, κ·€μ‚¬μ˜ μ œν’ˆμ΄ λ§ˆμŒμ— λ“­λ‹ˆλ‹€. μžμ„Ένžˆ μ„€λͺ… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.",
"μ œν’ˆμ΄ ν˜„μž¬ μž¬κ³ κ°€ μžˆλ‚˜μš”?",
"도맀 거래λ₯Ό ν•˜κ³  μ‹ΆμŠ΅λ‹ˆλ‹€. κ°€λŠ₯ν•œκ°€μš”?",
"μƒˆλ‘œμš΄ 고객인데 거래λ₯Ό μ‹œμž‘ν•  수 μžˆμ„κΉŒμš”?",
"ν˜Ήμ‹œ ν•œκ΅­ 고객듀과도 거래λ₯Ό ν•˜κ³  κ³„μ‹ κ°€μš”?",
"μ œν’ˆμ— λŒ€ν•œ μžμ„Έν•œ μ„€λͺ…μ„œλ‚˜ λΈŒλ‘œμŠˆμ–΄κ°€ μžˆλ‚˜μš”?",
"μ œν’ˆμ΄ 인기 μƒν’ˆμΈκ°€μš”? 판맀 데이터λ₯Ό κ³΅μœ ν•  수 μžˆλ‚˜μš”?",
"판맀 μ§€μ—­μ΄λ‚˜ μ œν•œμ΄ μžˆλ‚˜μš”?",
"이 μ œν’ˆμ˜ ν˜„μž¬ μ‹œμž₯ νŠΈλ Œλ“œλŠ” μ–΄λ–€κ°€μš”?",
"판맀 기둝이 μžˆλŠ” μ œν’ˆμΈμ§€ 확인할 수 μžˆλ‚˜μš”?"
],
"μ œν’ˆ 상세 및 ν’ˆμ§ˆ κ΄€λ ¨ 질문": [
"이 μ œν’ˆμ˜ μ£Όμš” κΈ°λŠ₯은 λ¬΄μ—‡μΈκ°€μš”?",
"ν’ˆμ§ˆ ν…ŒμŠ€νŠΈλ₯Ό ν†΅κ³Όν•œ μ œν’ˆμΈκ°€μš”?",
"μ œν’ˆμ˜ μ›μž¬λ£ŒλŠ” λ¬΄μ—‡μΈκ°€μš”?",
"μ›μ‚°μ§€λŠ” μ–΄λ””μΈκ°€μš”?",
"μ œν’ˆμ€ μ‚¬μš©ν•˜κΈ°μ— μ•ˆμ „ν•œκ°€μš”?",
"μΉœν™˜κ²½ 인증을 받은 μ œν’ˆμΈκ°€μš”?",
"μ œν’ˆ μ‚¬μš© κΈ°κ°„(내ꡬ성)은 μ–΄λŠ μ •λ„μΈκ°€μš”?",
"보증 기간은 μ–΄λ–»κ²Œ λ˜λ‚˜μš”?",
"μ‚¬μš© 쀑 λ¬Έμ œκ°€ λ°œμƒν•˜λ©΄ μ–΄λ–»κ²Œ μ²˜λ¦¬ν•΄μ•Ό ν•˜λ‚˜μš”?",
"μ‚¬μš© μ„€λͺ…μ„œκ°€ ν¬ν•¨λ˜μ–΄ μžˆλ‚˜μš”?"
],
"가격 및 결제 κ΄€λ ¨ 질문": [
"도맀 가격은 μ–Όλ§ˆμΈκ°€μš”?",
"λŒ€λŸ‰ ꡬ맀 μ‹œ 할인이 κ°€λŠ₯ν•œκ°€μš”?",
"첫 거래 고객을 μœ„ν•œ νŠΉλ³„ ν˜œνƒμ΄ μžˆλ‚˜μš”?",
"결제 쑰건을 μ•Œλ €μ£Όμ„Έμš”.",
"λΆ€λΆ„ 결제 ν›„ μž”κΈˆ κ²°μ œκ°€ κ°€λŠ₯ν•œκ°€μš”?",
"λŒ€λŸ‰ μ£Όλ¬Έ μ‹œ 초기 λ³΄μ¦κΈˆμ„ μš”κ΅¬ν•˜μ‹œλ‚˜μš”?",
"λ°°μ†‘λ£Œ 포함 κ°€κ²©μΈκ°€μš”?",
"μΆ”κ°€ λΉ„μš©μ΄ λ°œμƒν•  수 μžˆλ‚˜μš”?",
"할인 μΏ ν°μ΄λ‚˜ ν”„λ‘œλͺ¨μ…˜ μ½”λ“œλŠ” μ—†λ‚˜μš”?",
"μ •κΈ° ꡬ맀 고객을 μœ„ν•œ 할인 정책이 μžˆλ‚˜μš”?"
],
"배솑 및 λ¬Όλ₯˜ κ΄€λ ¨ 질문": [
"배솑은 μ–΄λŠ 택배사λ₯Ό 톡해 μ§„ν–‰λ˜λ‚˜μš”?",
"μ˜ˆμƒ 배솑 기간은 μ–Όλ§ˆλ‚˜ λ˜λ‚˜μš”?",
"ν•œκ΅­κΉŒμ§€ 배솑이 κ°€λŠ₯ν•œκ°€μš”?",
"ꡭ제 λ°°μ†‘λ£ŒλŠ” μ–΄λ–»κ²Œ κ³„μ‚°λ˜λ‚˜μš”?",
"λŒ€λŸ‰ μ£Όλ¬Έ μ‹œ 배솑비 할인 ν˜œνƒμ΄ μžˆλ‚˜μš”?",
"배솑 쀑 νŒŒμ†μ΄ λ°œμƒν•˜λ©΄ μ–΄λ–»κ²Œ μ²˜λ¦¬ν•˜λ‚˜μš”?",
"μ£Όλ¬Έ ν›„ λͺ‡ 일 내에 배솑이 μ‹œμž‘λ˜λ‚˜μš”?",
"배솑 좔적 번호λ₯Ό μ œκ³΅ν•˜μ‹œλ‚˜μš”?",
"μ£Όλ¬Έ μƒνƒœλ₯Ό μ–΄λ–»κ²Œ 확인할 수 μžˆλ‚˜μš”?",
"λ°°μ†‘λ£Œλ₯Ό 직접 λΆ€λ‹΄ν•΄μ•Ό ν•˜λ‚˜μš”?"
]
}
def guess_lang(text: str) -> str:
text = text.strip()
if not text:
return ""
try:
lang = detect(text)
if lang in ['ko', 'en', 'zh-cn', 'zh-tw']:
if lang.startswith('zh'):
return 'zh'
return lang
else:
# langdetect κ²°κ³Όκ°€ ko, en, zhκ°€ 아닐 경우 νœ΄λ¦¬μŠ€ν‹± 적용
if all('a' <= c.lower() <= 'z' for c in text if c.isalpha()):
return 'en'
# ν•œκΈ€ 음절 λ²”μœ„ νŒλ‹¨
elif any('\uac00' <= c <= '\ud7a3' for c in text):
return 'ko'
# 쀑ꡭ어 λ²”μœ„ νŒλ‹¨
elif any('\u4e00' <= c <= '\u9fff' for c in text):
return 'zh'
else:
# λ””ν΄νŠΈ μ˜μ–΄ 처리
return 'en'
except:
# langdetect μ‹€νŒ¨ μ‹œ νœ΄λ¦¬μŠ€ν‹± 적용
if all('a' <= c.lower() <= 'z' for c in text if c.isalpha()):
return 'en'
elif any('\uac00' <= c <= '\ud7a3' for c in text):
return 'ko'
elif any('\u4e00' <= c <= '\u9fff' for c in text):
return 'zh'
else:
return 'en'
def validate_input(text: str, target_langs: list) -> bool:
lang = guess_lang(text)
logger.info(f"κ°μ§€λœ(μΆ”μ •) μ–Έμ–΄: {lang}")
return lang in target_langs
def translate_ko_en(input_text: str):
logger.info("ν•œ-영 λ²ˆμ—­ μ‹œμž‘")
if not validate_input(input_text, ['ko', 'en']):
return "μœ νš¨ν•˜μ§€ μ•Šμ€ μž…λ ₯μž…λ‹ˆλ‹€. ν•œκ΅­μ–΄ λ˜λŠ” μ˜μ–΄ ν…μŠ€νŠΈλ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."
try:
detected_lang = guess_lang(input_text)
logger.info(f"κ°μ§€λœ(μΆ”μ •) μ–Έμ–΄: {detected_lang}")
if detected_lang == 'ko':
direction = "Korean to English"
input_lang = "Korean"
output_lang = "English"
else:
direction = "English to Korean"
input_lang = "English"
output_lang = "Korean"
logger.info(f"λ²ˆμ—­ λ°©ν–₯: {direction}")
current_time = int(time.time() * 1000)
random.seed(current_time)
temperature = random.uniform(0.4, 0.85)
top_p = random.uniform(0.9, 0.98)
request_id = str(uuid.uuid4())[:8]
timestamp_micro = int(time.time() * 1000000) % 1000
current_hour = datetime.now().hour
time_context = f"Consider the current time is {current_hour}:00."
# ν•œκ΅­μ–΄ 좜λ ₯ μ‹œ μžμ—°μŠ€λŸ¬μš΄ ν•œκ΅­μ–΄ 쑰건 μΆ”κ°€
korean_conditions = ""
if output_lang == "Korean":
korean_conditions = KO_EN_CONDITIONS
system_prompt = "You are a helpful translator."
prompt = f"""
Translate the following {input_lang} text into {output_lang}:
{input_text}
Rules:
1. Output ONLY the {output_lang} translation without additional labels or formatting.
2. Preserve the original meaning and intent.
3. No explanations or meta-commentary.
4. No formatting beyond basic text.
5. {time_context}
[Seed: {current_time}]
{korean_conditions}
"""
logger.debug(f"ν”„λ‘¬ν”„νŠΈ:\n{prompt}")
# μž‘λ™ν•˜λŠ” API ν‚€ κ°€μ Έμ˜€κΈ°
api_key = get_working_api_key()
genai.configure(api_key=api_key)
# Gemini λͺ¨λΈ μ„€μ •
generation_config = {
"max_output_tokens": 200,
"temperature": temperature,
"top_p": top_p,
}
# Gemini λͺ¨λΈ 뢈러였기
model = genai.GenerativeModel(
model_name="gemini-2.0-flash",
generation_config=generation_config,
)
# λ²ˆμ—­ μ‹€ν–‰
full_prompt = f"{system_prompt}\n\n{prompt}"
response = model.generate_content(full_prompt)
translated = response.text.strip()
logger.info("Gemini λ²ˆμ—­ μ™„λ£Œ")
logger.debug(f"λ²ˆμ—­ κ²°κ³Ό:\n{translated}")
return translated
except Exception as e:
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 translate_ko_zh(input_text: str):
logger.info("ν•œ-쀑 λ²ˆμ—­ μ‹œμž‘")
if not validate_input(input_text, ['ko', 'zh']):
return "μœ νš¨ν•˜μ§€ μ•Šμ€ μž…λ ₯μž…λ‹ˆλ‹€. ν•œκ΅­μ–΄ λ˜λŠ” 쀑ꡭ어 ν…μŠ€νŠΈλ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."
try:
detected_lang = guess_lang(input_text)
logger.info(f"κ°μ§€λœ(μΆ”μ •) μ–Έμ–΄: {detected_lang}")
if detected_lang == 'ko':
direction = "Korean to Chinese"
input_lang = "Korean"
output_lang = "Chinese"
else:
direction = "Chinese to Korean"
input_lang = "Chinese"
output_lang = "Korean"
logger.info(f"λ²ˆμ—­ λ°©ν–₯: {direction}")
current_time = int(time.time() * 1000)
random.seed(current_time)
temperature = random.uniform(0.5, 0.8)
top_p = random.uniform(0.9, 0.98)
# μž‘λ™ν•˜λŠ” API ν‚€ κ°€μ Έμ˜€κΈ°
api_key = get_working_api_key()
genai.configure(api_key=api_key)
# Gemini λͺ¨λΈ μ„€μ •
generation_config = {
"max_output_tokens": 1024,
"temperature": temperature,
"top_p": top_p,
}
# Gemini λͺ¨λΈ 뢈러였기
model = genai.GenerativeModel(
model_name="gemini-2.0-flash",
generation_config=generation_config,
)
# ν”„λ‘¬ν”„νŠΈ μ€€λΉ„
prompt = f"{KO_ZH_SYSTEM_MESSAGE}\n\n{input_text}"
# λ²ˆμ—­ μ‹€ν–‰
response = model.generate_content(prompt)
translated = response.text.strip()
logger.info("Gemini λ²ˆμ—­ μ™„λ£Œ")
logger.debug(f"λ²ˆμ—­ κ²°κ³Ό:\n{translated}")
return translated
except Exception as e:
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 update_subcategories(category):
if category in categories:
return gr.update(choices=categories[category], value=None)
else:
return gr.update(choices=[], value=None)
def set_input_text(selected_text):
return selected_text
# μ»€μŠ€ν…€ CSS μŠ€νƒ€μΌ
custom_css = """
:root {
--primary-color: #FB7F0D;
--secondary-color: #ff9a8b;
--accent-color: #FF6B6B;
--background-color: #FFF3E9;
--card-bg: #ffffff;
--text-color: #334155;
--border-radius: 18px;
--shadow: 0 8px 30px rgba(251, 127, 13, 0.08);
}
body {
font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
background-color: var(--background-color);
color: var(--text-color);
line-height: 1.6;
margin: 0;
padding: 0;
}
.gradio-container {
width: 100%;
margin: 0 auto;
padding: 20px;
background-color: var(--background-color);
}
.custom-header {
background: #FF7F00;
padding: 2rem;
border-radius: 15px;
margin-bottom: 20px;
box-shadow: var(--shadow);
text-align: center;
}
.custom-header h1 {
margin: 0;
font-size: 2.5rem;
font-weight: 700;
color: black;
}
.custom-header p {
margin: 10px 0 0;
font-size: 1.2rem;
color: black;
}
.custom-frame {
background-color: var(--card-bg);
border: 1px solid rgba(0, 0, 0, 0.04);
border-radius: var(--border-radius);
padding: 20px;
margin: 10px 0;
box-shadow: var(--shadow);
}
.custom-section-group {
margin-top: 20px;
padding: 0;
border: none;
border-radius: 0;
background-color: var(--background-color);
box-shadow: none !important;
}
.custom-button {
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;
}
.custom-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(251, 127, 13, 0.3);
}
.custom-title {
font-size: 28px;
font-weight: bold;
margin-bottom: 10px;
color: var(--text-color);
border-bottom: 2px solid var(--primary-color);
padding-bottom: 5px;
}
.image-container {
border-radius: var(--border-radius);
overflow: hidden;
border: 1px solid rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
background-color: white;
}
.image-container:hover {
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}
.gr-input, .gr-text-input, .gr-sample-inputs {
border-radius: var(--border-radius) !important;
border: 1px solid #dddddd !important;
padding: 12px !important;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05) !important;
transition: all 0.3s ease !important;
}
.gr-input:focus, .gr-text-input:focus {
border-color: var(--primary-color) !important;
outline: none !important;
box-shadow: 0 0 0 2px rgba(251, 127, 13, 0.2) !important;
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.05);
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: var(--primary-color);
border-radius: 10px;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.fade-in {
animation: fadeIn 0.5s ease-out;
}
@media (max-width: 768px) {
.button-grid {
grid-template-columns: repeat(2, 1fr);
}
}
.section-title {
display: flex;
align-items: center;
font-size: 20px;
font-weight: 700;
color: #333333;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 2px solid #FB7F0D;
font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
}
.section-title img {
margin-right: 10px;
width: 24px;
height: 24px;
}
.guide-container {
background-color: var(--card-bg);
border-radius: var(--border-radius);
box-shadow: var(--shadow);
padding: 1.5rem;
margin-bottom: 1.5rem;
border: 1px solid rgba(0, 0, 0, 0.04);
}
.guide-title {
font-size: 1.5rem;
font-weight: 700;
color: var(--primary-color);
margin-bottom: 1.5rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid var(--primary-color);
display: flex;
align-items: center;
}
.guide-title i {
margin-right: 0.8rem;
font-size: 1.5rem;
}
.guide-item {
display: flex;
margin-bottom: 1rem;
align-items: flex-start;
}
.guide-number {
background-color: var(--primary-color);
color: white;
width: 25px;
height: 25px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
margin-right: 10px;
flex-shrink: 0;
}
.guide-text {
flex: 1;
line-height: 1.6;
}
.guide-text a {
color: var(--primary-color);
text-decoration: underline;
font-weight: 600;
}
.guide-text a:hover {
color: var(--accent-color);
}
.guide-highlight {
background-color: rgba(251, 127, 13, 0.1);
padding: 2px 5px;
border-radius: 4px;
font-weight: 500;
}
"""
# FontAwesome μ•„μ΄μ½˜ 포함
fontawesome_link = """
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
"""
def create_interface():
with gr.Blocks(css=custom_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_link)
with gr.Tabs() as tabs:
with gr.TabItem("ν•œκ΅­μ–΄-쀑ꡭ어 λ²ˆμ—­"):
with gr.Column(elem_classes="custom-frame"):
gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/3097/3097412.png"> μΉ΄ν…Œκ³ λ¦¬ 선택</div>')
with gr.Row():
category_dropdown = gr.Dropdown(
label="1μ°¨ μΉ΄ν…Œκ³ λ¦¬ 선택",
choices=list(categories.keys()),
value=None
)
subcategory_dropdown = gr.Dropdown(
label="2μ°¨ 질문 선택",
choices=[],
value=None
)
with gr.Column(elem_classes="custom-frame"):
gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/4297/4297825.png"> λ²ˆμ—­</div>')
with gr.Row():
with gr.Column(scale=1):
kz_input_box = gr.Textbox(
label="μž…λ ₯ (ν•œκ΅­μ–΄ λ˜λŠ” 쀑ꡭ어)",
lines=8,
placeholder="λ²ˆμ—­ν•  ν…μŠ€νŠΈλ₯Ό μž…λ ₯ν•˜μ„Έμš”..."
)
with gr.Column(scale=1):
kz_output_box = gr.Textbox(
label="λ²ˆμ—­ κ²°κ³Ό",
lines=8
)
with gr.Column(elem_classes="custom-frame"):
gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/1022/1022293.png"> λ²ˆμ—­ μ‹€ν–‰</div>')
kz_translate_button = gr.Button("λ²ˆμ—­ν•˜κΈ°", elem_classes="custom-button")
# 1μ°¨ 선택 μ‹œ 2μ°¨ ν•­λͺ© μ—…λ°μ΄νŠΈ
category_dropdown.change(
fn=update_subcategories,
inputs=category_dropdown,
outputs=subcategory_dropdown
)
# 2μ°¨ 선택 μ‹œ μž…λ ₯λž€μ— ν•΄λ‹Ή κ°’ μžλ™ μž…λ ₯
subcategory_dropdown.change(
fn=set_input_text,
inputs=subcategory_dropdown,
outputs=kz_input_box
)
# λ²ˆμ—­ ν•¨μˆ˜
kz_translate_button.click(
fn=translate_ko_zh,
inputs=kz_input_box,
outputs=kz_output_box
)
with gr.TabItem("ν•œκ΅­μ–΄-μ˜μ–΄ λ²ˆμ—­"):
with gr.Column(elem_classes="custom-frame"):
gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/4297/4297825.png"> λ²ˆμ—­</div>')
with gr.Row():
with gr.Column(scale=1):
ke_input_box = gr.Textbox(
label="μž…λ ₯ ν…μŠ€νŠΈ (ν•œκ΅­μ–΄ λ˜λŠ” μ˜μ–΄)",
placeholder="λ²ˆμ—­ν•  ν…μŠ€νŠΈλ₯Ό μž…λ ₯ν•˜μ„Έμš”...",
lines=8
)
with gr.Column(scale=1):
ke_output_box = gr.Textbox(
label="λ²ˆμ—­ κ²°κ³Ό",
lines=8,
interactive=False
)
with gr.Column(elem_classes="custom-frame"):
gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/1022/1022293.png"> λ²ˆμ—­ μ‹€ν–‰</div>')
ke_translate_button = gr.Button("λ²ˆμ—­ν•˜κΈ°", elem_classes="custom-button")
# λ²ˆμ—­ ν•¨μˆ˜
ke_translate_button.click(
fn=translate_ko_en,
inputs=ke_input_box,
outputs=ke_output_box
)
return demo
if __name__ == "__main__":
demo = create_interface()
demo.launch()