G378DFE4AS / app.py
ssboost's picture
Upload 2 files
941b583 verified
raw
history blame
22.6 kB
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
DetectorFactory.seed = 0
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# API ν‚€ μ„€μ • (ν™˜κ²½ λ³€μˆ˜μ—μ„œ μžλ™μœΌλ‘œ κ°€μ Έμ˜΄)
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "여기에_κΈ°λ³Έ_API_ν‚€_μž…λ ₯")
genai.configure(api_key=GEMINI_API_KEY)
# ν•œκ΅­μ–΄ μžμ—°μŠ€λŸ½κ²Œ 쑰건 (ν•œ-영 λ²ˆμ—­μš©)
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}")
# 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)}")
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)
# 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)}")
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" />
"""
# μ•± 헀더 HTML
header_html = """
<div class="custom-header">
<h1>μ…€λŸ¬ μžλ™ λ²ˆμ—­κΈ°</h1>
<p style="font-size:18px; margin-top:10px;">
ν•œ-쀑, ν•œ-영 μžλ™ λ²ˆμ—­μœΌλ‘œ κΈ€λ‘œλ²Œ λΉ„μ¦ˆλ‹ˆμŠ€ μ†Œν†΅μ„ λ„μ™€λ“œλ¦½λ‹ˆλ‹€.
</p>
</div>
"""
# μ‚¬μš© κ°€μ΄λ“œ HTML
guide_html = """
<div class="guide-container">
<div class="guide-title"><i class="fas fa-book"></i> μ‚¬μš© κ°€μ΄λ“œ</div>
<div class="guide-item">
<div class="guide-number">1</div>
<div class="guide-text">
μž…λ ₯ν•œ μ–Έμ–΄λ₯Ό μžλ™μœΌλ‘œ κ°μžν•˜μ—¬ ν•œκ΅­μ–΄ ↔ 쀑ꡭ어, ν•œκ΅­μ–΄ ↔ μ€‘κ΅­μ–΄λ‘œ λ²ˆμ—­λ©λ‹ˆλ‹€.
</div>
</div>
<div class="guide-item">
<div class="guide-number">2</div>
<div class="guide-text">
ν•œ-쀑 λ²ˆμ—­μ—μ„œλŠ” 자주 μ‚¬μš©ν•˜λŠ” ν‘œν˜„μ„ μΉ΄ν…Œκ³ λ¦¬λ³„λ‘œ 선택할 수 μžˆμŠ΅λ‹ˆλ‹€.
</div>
</div>
</div>
"""
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)
gr.HTML(header_html)
gr.HTML(guide_html)
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()