ssboost commited on
Commit
941b583
Β·
verified Β·
1 Parent(s): 8ab21c3

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +690 -0
  2. requirements.txt +3 -0
app.py ADDED
@@ -0,0 +1,690 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ import time
4
+ import logging
5
+ import random
6
+ import uuid
7
+ from datetime import datetime
8
+ from langdetect import detect, DetectorFactory
9
+ import google.generativeai as genai
10
+
11
+ DetectorFactory.seed = 0
12
+
13
+ logging.basicConfig(
14
+ level=logging.INFO,
15
+ format='%(asctime)s - %(levelname)s - %(message)s'
16
+ )
17
+ logger = logging.getLogger(__name__)
18
+
19
+ # API ν‚€ μ„€μ • (ν™˜κ²½ λ³€μˆ˜μ—μ„œ μžλ™μœΌλ‘œ κ°€μ Έμ˜΄)
20
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "여기에_κΈ°λ³Έ_API_ν‚€_μž…λ ₯")
21
+ genai.configure(api_key=GEMINI_API_KEY)
22
+
23
+ # ν•œκ΅­μ–΄ μžμ—°μŠ€λŸ½κ²Œ 쑰건 (ν•œ-영 λ²ˆμ—­μš©)
24
+ KO_EN_CONDITIONS = """
25
+ μΆ”κ°€ 쑰건 (ν•œκ΅­μ–΄ μžμ—°μŠ€λŸ½κ²Œ ν•˜κΈ°):
26
+ - λ²ˆμ—­μ²΄κ°€ μ•„λ‹Œ μžμ—°μŠ€λŸ½κ³  λ§€λ„λŸ¬μš΄ ν•œκ΅­μ–΄λ₯Ό μ΅œμš°μ„ μ μœΌλ‘œ μž‘μ„±
27
+ - μ•„λž˜ 쑰건을 λ”°λΌμ£Όμ„Έμš”:
28
+ 1. 문법적 μ •ν™•μ„±
29
+ - 피동문보닀 λŠ₯동문 μš°μ„ 
30
+ - λŒ€λͺ…사 μ΅œμ†Œν™”
31
+ - λͺ…μ‚¬ν˜•λ³΄λ‹€ 동사/ν˜•μš©μ‚¬ μš°μ„ 
32
+ - ν˜„μž¬μ§„ν–‰ν˜•λ³΄λ‹€ λ‹¨μˆœν˜„μž¬λ‚˜ μ™„λ£Œν˜• μš°μ„ 
33
+ - μ£Όμ–΄-λͺ©μ μ–΄-μ„œμˆ μ–΄ μˆœμ„œ μœ μ§€
34
+ - "~μ—ˆμ–΄μš”" ν‘œν˜„ μ§€μ–‘
35
+ 2. 주제 λ¬Έλ§₯에 λ§žλŠ” μžμ—°μŠ€λŸ¬μš΄ 흐름
36
+ 3. μ£Όμ œμ™€ 상황에 μ–΄μšΈλ¦¬λŠ” μ–΄νœ˜ 선택
37
+ 4. 문화적 적합성 κ³ λ €
38
+ 5. μ •μ„œμ  λ‰˜μ•™μŠ€ 반영 (곡감 μ΄λ„λŠ” ν‘œν˜„)
39
+ 6. 직역/μ˜μ—­ κ· ν˜• μœ μ§€
40
+ 7. 주제 ν‚€μ›Œλ“œ 반볡 μ–ΈκΈ‰ κΈˆμ§€
41
+ 8. AIκ°€ μ“΄ κΈ€μ²˜λŸΌ 보이지 μ•Šλ„λ‘ 주의
42
+ """
43
+
44
+ # μ‹œμŠ€ν…œ λ©”μ‹œμ§€ (ν•œ-쀑 λ²ˆμ—­μš©)
45
+ KO_ZH_SYSTEM_MESSAGE = """당신은 ν•œκ΅­μ–΄-쀑ꡭ어 μ–‘λ°©ν–₯ λ²ˆμ—­ μ–΄μ‹œμŠ€ν„΄νŠΈμž…λ‹ˆλ‹€.
46
+ μ•„λž˜ 쑰건을 μΆ©μ‹€νžˆ λ”°λ₯΄μ„Έμš”.
47
+ [λͺ©μ  및 상황]
48
+ - μ‚¬μš©μž λͺ©μ : 1688 μ†Œμ‹±, 도맀, μœ ν†΅ κ΄€λ ¨λœ μƒν™©μ—μ„œ μ–Έμ–΄ μž₯λ²½ 없이 μ›ν™œν•œ μ†Œν†΅μ„ λ•λŠ”λ‹€.
49
+ - μ‚¬μš©μž μž…λ ₯이 ν•œκ΅­μ–΄μΌ 경우 μžμ—°μŠ€λŸ½κ³  λ§₯락에 맞게 μ€‘κ΅­μ–΄λ‘œ λ²ˆμ—­ν•œλ‹€.
50
+ - μ‚¬μš©μž μž…λ ₯이 쀑ꡭ어일 경우 μžμ—°μŠ€λŸ½κ³  λ§₯락에 맞게 ν•œκ΅­μ–΄λ‘œ λ²ˆμ—­ν•œλ‹€.
51
+ [ν•œκ΅­μ–΄ μžμ—°μŠ€λŸ½κ²Œ 쑰건정리]
52
+ 1. 문법적 μ •ν™•μ„±
53
+ - 피동문 λŒ€μ‹  λŠ₯동문
54
+ - λŒ€λͺ…사 μ΅œμ†Œν™”
55
+ - λͺ…μ‚¬ν˜•λ³΄λ‹€ 동사 및 ν˜•μš©μ‚¬ μš°μ„ 
56
+ - λ‹¨μˆœν˜„μž¬ν˜•μ΄λ‚˜ μ™„λ£Œν˜• μš°μ„ 
57
+ - λ¬Έμž₯ κ΅¬μ‘°λŠ” μ£Όμ–΄-λͺ©μ μ–΄-동사
58
+ - "~μ—ˆμ–΄μš”" ν˜•νƒœ μ‚¬μš© μ§€μ–‘
59
+ 2. 주제 λ§₯락에 λ§žλŠ” μžμ—°μŠ€λŸ¬μš΄ ν‘œν˜„
60
+ 3. μ μ ˆν•œ μ–΄νœ˜ 선택(μ†Œμ‹±, 도맀, μœ ν†΅ 상황 κ³ λ €)
61
+ 4. 문화적, 상황적 적합성
62
+ 5. μ •μ„œμ  λ‰˜μ•™μŠ€ μ‘°ν™”
63
+ 6. 직역과 μ˜μ—­μ˜ κ· ν˜•
64
+ 7. 주제 ν‚€μ›Œλ“œ 반볡 κΈˆμ§€
65
+ 8. 생성 AI ν‹° λ‚˜μ§€ μ•Šλ„λ‘ 주의
66
+ [좔가사항]
67
+ - λ²ˆμ—­ μ‹œ μ–΄μƒ‰ν•œ λ²ˆμ—­μ²΄λ₯Ό ν”Όν•˜κ³  μžμ—°μŠ€λŸ¬μš΄ ν‘œν˜„μ„ μ‚¬μš©ν•œλ‹€.
68
+ - 좜λ ₯은 λ²ˆμ—­λœ 결과문만 μ œμ‹œν•œλ‹€.
69
+ """
70
+
71
+ # 1μ°¨ μΉ΄ν…Œκ³ λ¦¬ 및 2μ°¨ 질문 λͺ©λ‘ μ •μ˜
72
+ categories = {
73
+ "κΈ°λ³Έ 인사 및 거래 μ‹œμž‘": [
74
+ "μ•ˆλ…•ν•˜μ„Έμš”, κ·€μ‚¬μ˜ μ œν’ˆμ΄ λ§ˆμŒμ— λ“­λ‹ˆλ‹€. μžμ„Ένžˆ μ„€λͺ… λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.",
75
+ "μ œν’ˆμ΄ ν˜„μž¬ μž¬κ³ κ°€ μžˆλ‚˜μš”?",
76
+ "도맀 거래λ₯Ό ν•˜κ³  μ‹ΆμŠ΅λ‹ˆλ‹€. κ°€λŠ₯ν•œκ°€μš”?",
77
+ "μƒˆλ‘œμš΄ 고객인데 거래λ₯Ό μ‹œμž‘ν•  수 μžˆμ„κΉŒμš”?",
78
+ "ν˜Ήμ‹œ ν•œκ΅­ 고객듀과도 거래λ₯Ό ν•˜κ³  κ³„μ‹ κ°€μš”?",
79
+ "μ œν’ˆμ— λŒ€ν•œ μžμ„Έν•œ μ„€λͺ…μ„œλ‚˜ λΈŒλ‘œμŠˆμ–΄κ°€ μžˆλ‚˜μš”?",
80
+ "μ œν’ˆμ΄ 인기 μƒν’ˆμΈκ°€μš”? 판맀 데이터λ₯Ό κ³΅μœ ν•  수 μžˆλ‚˜μš”?",
81
+ "판맀 μ§€μ—­μ΄λ‚˜ μ œν•œμ΄ μžˆλ‚˜μš”?",
82
+ "이 μ œν’ˆμ˜ ν˜„μž¬ μ‹œμž₯ νŠΈλ Œλ“œλŠ” μ–΄λ–€κ°€μš”?",
83
+ "판맀 기둝이 μžˆλŠ” μ œν’ˆμΈμ§€ 확인할 수 μžˆλ‚˜μš”?"
84
+ ],
85
+ "μ œν’ˆ 상세 및 ν’ˆμ§ˆ κ΄€λ ¨ 질문": [
86
+ "이 μ œν’ˆμ˜ μ£Όμš” κΈ°λŠ₯은 λ¬΄μ—‡μΈκ°€μš”?",
87
+ "ν’ˆμ§ˆ ν…ŒμŠ€νŠΈλ₯Ό ν†΅κ³Όν•œ μ œν’ˆμΈκ°€μš”?",
88
+ "μ œν’ˆμ˜ μ›μž¬λ£ŒλŠ” λ¬΄μ—‡μΈκ°€μš”?",
89
+ "μ›μ‚°μ§€λŠ” μ–΄λ””μΈκ°€μš”?",
90
+ "μ œν’ˆμ€ μ‚¬μš©ν•˜κΈ°μ— μ•ˆμ „ν•œκ°€μš”?",
91
+ "μΉœν™˜κ²½ 인증을 받은 μ œν’ˆμΈκ°€μš”?",
92
+ "μ œν’ˆ μ‚¬μš© κΈ°κ°„(내ꡬ성)은 μ–΄λŠ μ •λ„μΈκ°€μš”?",
93
+ "보증 기간은 μ–΄λ–»κ²Œ λ˜λ‚˜μš”?",
94
+ "μ‚¬μš© 쀑 λ¬Έμ œκ°€ λ°œμƒν•˜λ©΄ μ–΄λ–»κ²Œ μ²˜λ¦¬ν•΄μ•Ό ν•˜λ‚˜μš”?",
95
+ "μ‚¬μš© μ„€λͺ…μ„œκ°€ ν¬ν•¨λ˜μ–΄ μžˆλ‚˜μš”?"
96
+ ],
97
+ "가격 및 결제 κ΄€λ ¨ 질문": [
98
+ "도맀 가격은 μ–Όλ§ˆμΈκ°€μš”?",
99
+ "λŒ€λŸ‰ ꡬ맀 μ‹œ 할인이 κ°€λŠ₯ν•œκ°€μš”?",
100
+ "첫 거래 고객을 μœ„ν•œ νŠΉλ³„ ν˜œνƒμ΄ μžˆλ‚˜μš”?",
101
+ "결제 쑰건을 μ•Œλ €μ£Όμ„Έμš”.",
102
+ "λΆ€λΆ„ 결제 ν›„ μž”κΈˆ κ²°μ œκ°€ κ°€λŠ₯ν•œκ°€μš”?",
103
+ "λŒ€λŸ‰ μ£Όλ¬Έ μ‹œ 초기 λ³΄μ¦κΈˆμ„ μš”κ΅¬ν•˜μ‹œλ‚˜μš”?",
104
+ "λ°°μ†‘λ£Œ 포함 κ°€κ²©μΈκ°€μš”?",
105
+ "μΆ”κ°€ λΉ„μš©μ΄ λ°œμƒν•  수 μžˆλ‚˜μš”?",
106
+ "할인 μΏ ν°μ΄λ‚˜ ν”„λ‘œλͺ¨μ…˜ μ½”λ“œλŠ” μ—†λ‚˜μš”?",
107
+ "μ •κΈ° ꡬ맀 고객을 μœ„ν•œ 할인 정책이 μžˆλ‚˜μš”?"
108
+ ],
109
+ "배솑 및 λ¬Όλ₯˜ κ΄€λ ¨ 질문": [
110
+ "배솑은 μ–΄λŠ 택배사λ₯Ό 톡해 μ§„ν–‰λ˜λ‚˜μš”?",
111
+ "μ˜ˆμƒ 배솑 기간은 μ–Όλ§ˆλ‚˜ λ˜λ‚˜μš”?",
112
+ "ν•œκ΅­κΉŒμ§€ ��솑이 κ°€λŠ₯ν•œκ°€μš”?",
113
+ "ꡭ제 λ°°μ†‘λ£ŒλŠ” μ–΄λ–»κ²Œ κ³„μ‚°λ˜λ‚˜μš”?",
114
+ "λŒ€λŸ‰ μ£Όλ¬Έ μ‹œ 배솑비 할인 ν˜œνƒμ΄ μžˆλ‚˜μš”?",
115
+ "배솑 쀑 νŒŒμ†μ΄ λ°œμƒν•˜λ©΄ μ–΄λ–»κ²Œ μ²˜λ¦¬ν•˜λ‚˜μš”?",
116
+ "μ£Όλ¬Έ ν›„ λͺ‡ 일 내에 배솑이 μ‹œμž‘λ˜λ‚˜μš”?",
117
+ "배솑 좔적 번호λ₯Ό μ œκ³΅ν•˜μ‹œλ‚˜μš”?",
118
+ "μ£Όλ¬Έ μƒνƒœλ₯Ό μ–΄λ–»κ²Œ 확인할 수 μžˆλ‚˜μš”?",
119
+ "λ°°μ†‘λ£Œλ₯Ό 직접 λΆ€λ‹΄ν•΄μ•Ό ν•˜λ‚˜μš”?"
120
+ ]
121
+ }
122
+
123
+ def guess_lang(text: str) -> str:
124
+ text = text.strip()
125
+ if not text:
126
+ return ""
127
+ try:
128
+ lang = detect(text)
129
+ if lang in ['ko', 'en', 'zh-cn', 'zh-tw']:
130
+ if lang.startswith('zh'):
131
+ return 'zh'
132
+ return lang
133
+ else:
134
+ # langdetect κ²°κ³Όκ°€ ko, en, zhκ°€ 아닐 경우 νœ΄λ¦¬μŠ€ν‹± 적용
135
+ if all('a' <= c.lower() <= 'z' for c in text if c.isalpha()):
136
+ return 'en'
137
+ # ν•œκΈ€ 음절 λ²”μœ„ νŒλ‹¨
138
+ elif any('\uac00' <= c <= '\ud7a3' for c in text):
139
+ return 'ko'
140
+ # 쀑ꡭ어 λ²”μœ„ νŒλ‹¨
141
+ elif any('\u4e00' <= c <= '\u9fff' for c in text):
142
+ return 'zh'
143
+ else:
144
+ # λ””ν΄νŠΈ μ˜μ–΄ 처리
145
+ return 'en'
146
+ except:
147
+ # langdetect μ‹€νŒ¨ μ‹œ νœ΄λ¦¬μŠ€ν‹± 적용
148
+ if all('a' <= c.lower() <= 'z' for c in text if c.isalpha()):
149
+ return 'en'
150
+ elif any('\uac00' <= c <= '\ud7a3' for c in text):
151
+ return 'ko'
152
+ elif any('\u4e00' <= c <= '\u9fff' for c in text):
153
+ return 'zh'
154
+ else:
155
+ return 'en'
156
+
157
+ def validate_input(text: str, target_langs: list) -> bool:
158
+ lang = guess_lang(text)
159
+ logger.info(f"κ°μ§€λœ(μΆ”μ •) μ–Έμ–΄: {lang}")
160
+ return lang in target_langs
161
+
162
+ def translate_ko_en(input_text: str):
163
+ logger.info("ν•œ-영 λ²ˆμ—­ μ‹œμž‘")
164
+
165
+ if not validate_input(input_text, ['ko', 'en']):
166
+ return "μœ νš¨ν•˜μ§€ μ•Šμ€ μž…λ ₯μž…λ‹ˆλ‹€. ν•œκ΅­μ–΄ λ˜λŠ” μ˜μ–΄ ν…μŠ€νŠΈλ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."
167
+
168
+ try:
169
+ detected_lang = guess_lang(input_text)
170
+ logger.info(f"κ°μ§€λœ(μΆ”μ •) μ–Έμ–΄: {detected_lang}")
171
+
172
+ if detected_lang == 'ko':
173
+ direction = "Korean to English"
174
+ input_lang = "Korean"
175
+ output_lang = "English"
176
+ else:
177
+ direction = "English to Korean"
178
+ input_lang = "English"
179
+ output_lang = "Korean"
180
+
181
+ logger.info(f"λ²ˆμ—­ λ°©ν–₯: {direction}")
182
+
183
+ current_time = int(time.time() * 1000)
184
+ random.seed(current_time)
185
+ temperature = random.uniform(0.4, 0.85)
186
+ top_p = random.uniform(0.9, 0.98)
187
+
188
+ request_id = str(uuid.uuid4())[:8]
189
+ timestamp_micro = int(time.time() * 1000000) % 1000
190
+
191
+ current_hour = datetime.now().hour
192
+ time_context = f"Consider the current time is {current_hour}:00."
193
+
194
+ # ν•œκ΅­μ–΄ 좜λ ₯ μ‹œ μžμ—°μŠ€λŸ¬μš΄ ν•œκ΅­μ–΄ 쑰건 μΆ”κ°€
195
+ korean_conditions = ""
196
+ if output_lang == "Korean":
197
+ korean_conditions = KO_EN_CONDITIONS
198
+
199
+ system_prompt = "You are a helpful translator."
200
+
201
+ prompt = f"""
202
+ Translate the following {input_lang} text into {output_lang}:
203
+
204
+ {input_text}
205
+
206
+ Rules:
207
+ 1. Output ONLY the {output_lang} translation without additional labels or formatting.
208
+ 2. Preserve the original meaning and intent.
209
+ 3. No explanations or meta-commentary.
210
+ 4. No formatting beyond basic text.
211
+ 5. {time_context}
212
+ [Seed: {current_time}]
213
+
214
+ {korean_conditions}
215
+ """
216
+
217
+ logger.debug(f"ν”„λ‘¬ν”„νŠΈ:\n{prompt}")
218
+
219
+ # Gemini λͺ¨λΈ μ„€μ •
220
+ generation_config = {
221
+ "max_output_tokens": 200,
222
+ "temperature": temperature,
223
+ "top_p": top_p,
224
+ }
225
+
226
+ # Gemini λͺ¨λΈ 뢈러였기
227
+ model = genai.GenerativeModel(
228
+ model_name="gemini-2.0-flash",
229
+ generation_config=generation_config,
230
+ )
231
+
232
+ # λ²ˆμ—­ μ‹€ν–‰
233
+ full_prompt = f"{system_prompt}\n\n{prompt}"
234
+ response = model.generate_content(full_prompt)
235
+
236
+ translated = response.text.strip()
237
+ logger.info("Gemini λ²ˆμ—­ μ™„λ£Œ")
238
+ logger.debug(f"λ²ˆμ—­ κ²°κ³Ό:\n{translated}")
239
+ return translated
240
+
241
+ except Exception as e:
242
+ logger.error(f"λ²ˆμ—­ 쀑 μ˜ˆμƒμΉ˜ λͺ»ν•œ 였λ₯˜ λ°œμƒ: {str(e)}")
243
+ return f"λ²ˆμ—­ 쀑 μ˜ˆμƒμΉ˜ λͺ»ν•œ 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: {str(e)}"
244
+
245
+ def translate_ko_zh(input_text: str):
246
+ logger.info("ν•œ-쀑 λ²ˆμ—­ μ‹œμž‘")
247
+
248
+ if not validate_input(input_text, ['ko', 'zh']):
249
+ return "μœ νš¨ν•˜μ§€ μ•Šμ€ μž…λ ₯μž…λ‹ˆλ‹€. ν•œκ΅­μ–΄ λ˜λŠ” 쀑ꡭ어 ν…μŠ€νŠΈλ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."
250
+
251
+ try:
252
+ detected_lang = guess_lang(input_text)
253
+ logger.info(f"κ°μ§€λœ(μΆ”μ •) μ–Έμ–΄: {detected_lang}")
254
+
255
+ if detected_lang == 'ko':
256
+ direction = "Korean to Chinese"
257
+ input_lang = "Korean"
258
+ output_lang = "Chinese"
259
+ else:
260
+ direction = "Chinese to Korean"
261
+ input_lang = "Chinese"
262
+ output_lang = "Korean"
263
+
264
+ logger.info(f"λ²ˆμ—­ λ°©ν–₯: {direction}")
265
+
266
+ current_time = int(time.time() * 1000)
267
+ random.seed(current_time)
268
+ temperature = random.uniform(0.5, 0.8)
269
+ top_p = random.uniform(0.9, 0.98)
270
+
271
+ # Gemini λͺ¨λΈ μ„€μ •
272
+ generation_config = {
273
+ "max_output_tokens": 1024,
274
+ "temperature": temperature,
275
+ "top_p": top_p,
276
+ }
277
+
278
+ # Gemini λͺ¨λΈ 뢈러였기
279
+ model = genai.GenerativeModel(
280
+ model_name="gemini-2.0-flash",
281
+ generation_config=generation_config,
282
+ )
283
+
284
+ # ν”„λ‘¬ν”„νŠΈ μ€€λΉ„
285
+ prompt = f"{KO_ZH_SYSTEM_MESSAGE}\n\n{input_text}"
286
+
287
+ # λ²ˆμ—­ μ‹€ν–‰
288
+ response = model.generate_content(prompt)
289
+ translated = response.text.strip()
290
+
291
+ logger.info("Gemini λ²ˆμ—­ μ™„λ£Œ")
292
+ logger.debug(f"λ²ˆμ—­ κ²°κ³Ό:\n{translated}")
293
+ return translated
294
+
295
+ except Exception as e:
296
+ logger.error(f"λ²ˆμ—­ 쀑 μ˜ˆμƒμΉ˜ λͺ»ν•œ 였λ₯˜ λ°œμƒ: {str(e)}")
297
+ return f"λ²ˆμ—­ 쀑 μ˜ˆμƒμΉ˜ λͺ»ν•œ 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: {str(e)}"
298
+
299
+ def update_subcategories(category):
300
+ if category in categories:
301
+ return gr.update(choices=categories[category], value=None)
302
+ else:
303
+ return gr.update(choices=[], value=None)
304
+
305
+ def set_input_text(selected_text):
306
+ return selected_text
307
+
308
+ # μ»€μŠ€ν…€ CSS μŠ€νƒ€μΌ
309
+ custom_css = """
310
+ :root {
311
+ --primary-color: #FB7F0D;
312
+ --secondary-color: #ff9a8b;
313
+ --accent-color: #FF6B6B;
314
+ --background-color: #FFF3E9;
315
+ --card-bg: #ffffff;
316
+ --text-color: #334155;
317
+ --border-radius: 18px;
318
+ --shadow: 0 8px 30px rgba(251, 127, 13, 0.08);
319
+ }
320
+
321
+ body {
322
+ font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
323
+ background-color: var(--background-color);
324
+ color: var(--text-color);
325
+ line-height: 1.6;
326
+ margin: 0;
327
+ padding: 0;
328
+ }
329
+
330
+ .gradio-container {
331
+ width: 100%;
332
+ margin: 0 auto;
333
+ padding: 20px;
334
+ background-color: var(--background-color);
335
+ }
336
+
337
+ .custom-header {
338
+ background: #FF7F00;
339
+ padding: 2rem;
340
+ border-radius: 15px;
341
+ margin-bottom: 20px;
342
+ box-shadow: var(--shadow);
343
+ text-align: center;
344
+ }
345
+
346
+ .custom-header h1 {
347
+ margin: 0;
348
+ font-size: 2.5rem;
349
+ font-weight: 700;
350
+ color: black;
351
+ }
352
+
353
+ .custom-header p {
354
+ margin: 10px 0 0;
355
+ font-size: 1.2rem;
356
+ color: black;
357
+ }
358
+
359
+ .custom-frame {
360
+ background-color: var(--card-bg);
361
+ border: 1px solid rgba(0, 0, 0, 0.04);
362
+ border-radius: var(--border-radius);
363
+ padding: 20px;
364
+ margin: 10px 0;
365
+ box-shadow: var(--shadow);
366
+ }
367
+
368
+ .custom-section-group {
369
+ margin-top: 20px;
370
+ padding: 0;
371
+ border: none;
372
+ border-radius: 0;
373
+ background-color: var(--background-color);
374
+ box-shadow: none !important;
375
+ }
376
+
377
+ .custom-button {
378
+ border-radius: 30px !important;
379
+ background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important;
380
+ color: white !important;
381
+ font-size: 18px !important;
382
+ padding: 10px 20px !important;
383
+ border: none;
384
+ box-shadow: 0 4px 8px rgba(251, 127, 13, 0.25);
385
+ transition: transform 0.3s ease;
386
+ }
387
+
388
+ .custom-button:hover {
389
+ transform: translateY(-2px);
390
+ box-shadow: 0 6px 12px rgba(251, 127, 13, 0.3);
391
+ }
392
+
393
+ .custom-title {
394
+ font-size: 28px;
395
+ font-weight: bold;
396
+ margin-bottom: 10px;
397
+ color: var(--text-color);
398
+ border-bottom: 2px solid var(--primary-color);
399
+ padding-bottom: 5px;
400
+ }
401
+
402
+ .image-container {
403
+ border-radius: var(--border-radius);
404
+ overflow: hidden;
405
+ border: 1px solid rgba(0, 0, 0, 0.08);
406
+ transition: all 0.3s ease;
407
+ background-color: white;
408
+ }
409
+
410
+ .image-container:hover {
411
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
412
+ }
413
+
414
+ .gr-input, .gr-text-input, .gr-sample-inputs {
415
+ border-radius: var(--border-radius) !important;
416
+ border: 1px solid #dddddd !important;
417
+ padding: 12px !important;
418
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05) !important;
419
+ transition: all 0.3s ease !important;
420
+ }
421
+
422
+ .gr-input:focus, .gr-text-input:focus {
423
+ border-color: var(--primary-color) !important;
424
+ outline: none !important;
425
+ box-shadow: 0 0 0 2px rgba(251, 127, 13, 0.2) !important;
426
+ }
427
+
428
+ ::-webkit-scrollbar {
429
+ width: 8px;
430
+ height: 8px;
431
+ }
432
+
433
+ ::-webkit-scrollbar-track {
434
+ background: rgba(0, 0, 0, 0.05);
435
+ border-radius: 10px;
436
+ }
437
+
438
+ ::-webkit-scrollbar-thumb {
439
+ background: var(--primary-color);
440
+ border-radius: 10px;
441
+ }
442
+
443
+ @keyframes fadeIn {
444
+ from { opacity: 0; transform: translateY(10px); }
445
+ to { opacity: 1; transform: translateY(0); }
446
+ }
447
+
448
+ .fade-in {
449
+ animation: fadeIn 0.5s ease-out;
450
+ }
451
+
452
+ @media (max-width: 768px) {
453
+ .button-grid {
454
+ grid-template-columns: repeat(2, 1fr);
455
+ }
456
+ }
457
+
458
+ .section-title {
459
+ display: flex;
460
+ align-items: center;
461
+ font-size: 20px;
462
+ font-weight: 700;
463
+ color: #333333;
464
+ margin-bottom: 10px;
465
+ padding-bottom: 5px;
466
+ border-bottom: 2px solid #FB7F0D;
467
+ font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
468
+ }
469
+
470
+ .section-title img {
471
+ margin-right: 10px;
472
+ width: 24px;
473
+ height: 24px;
474
+ }
475
+
476
+ .guide-container {
477
+ background-color: var(--card-bg);
478
+ border-radius: var(--border-radius);
479
+ box-shadow: var(--shadow);
480
+ padding: 1.5rem;
481
+ margin-bottom: 1.5rem;
482
+ border: 1px solid rgba(0, 0, 0, 0.04);
483
+ }
484
+
485
+ .guide-title {
486
+ font-size: 1.5rem;
487
+ font-weight: 700;
488
+ color: var(--primary-color);
489
+ margin-bottom: 1.5rem;
490
+ padding-bottom: 0.5rem;
491
+ border-bottom: 2px solid var(--primary-color);
492
+ display: flex;
493
+ align-items: center;
494
+ }
495
+
496
+ .guide-title i {
497
+ margin-right: 0.8rem;
498
+ font-size: 1.5rem;
499
+ }
500
+
501
+ .guide-item {
502
+ display: flex;
503
+ margin-bottom: 1rem;
504
+ align-items: flex-start;
505
+ }
506
+
507
+ .guide-number {
508
+ background-color: var(--primary-color);
509
+ color: white;
510
+ width: 25px;
511
+ height: 25px;
512
+ border-radius: 50%;
513
+ display: flex;
514
+ align-items: center;
515
+ justify-content: center;
516
+ font-weight: bold;
517
+ margin-right: 10px;
518
+ flex-shrink: 0;
519
+ }
520
+
521
+ .guide-text {
522
+ flex: 1;
523
+ line-height: 1.6;
524
+ }
525
+
526
+ .guide-text a {
527
+ color: var(--primary-color);
528
+ text-decoration: underline;
529
+ font-weight: 600;
530
+ }
531
+
532
+ .guide-text a:hover {
533
+ color: var(--accent-color);
534
+ }
535
+
536
+ .guide-highlight {
537
+ background-color: rgba(251, 127, 13, 0.1);
538
+ padding: 2px 5px;
539
+ border-radius: 4px;
540
+ font-weight: 500;
541
+ }
542
+ """
543
+
544
+ # FontAwesome μ•„μ΄μ½˜ 포함
545
+ fontawesome_link = """
546
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
547
+ """
548
+
549
+ # μ•± 헀더 HTML
550
+ header_html = """
551
+ <div class="custom-header">
552
+ <h1>μ…€λŸ¬ μžλ™ λ²ˆμ—­κΈ°</h1>
553
+ <p style="font-size:18px; margin-top:10px;">
554
+ ν•œ-쀑, ν•œ-영 μžλ™ λ²ˆμ—­μœΌλ‘œ κΈ€λ‘œλ²Œ λΉ„μ¦ˆλ‹ˆμŠ€ μ†Œν†΅μ„ λ„μ™€λ“œλ¦½λ‹ˆλ‹€.
555
+ </p>
556
+ </div>
557
+ """
558
+
559
+ # μ‚¬μš© κ°€μ΄λ“œ HTML
560
+ guide_html = """
561
+ <div class="guide-container">
562
+ <div class="guide-title"><i class="fas fa-book"></i> μ‚¬μš© κ°€μ΄λ“œ</div>
563
+ <div class="guide-item">
564
+ <div class="guide-number">1</div>
565
+ <div class="guide-text">
566
+ μž…λ ₯ν•œ μ–Έμ–΄λ₯Ό μžλ™μœΌλ‘œ κ°μžν•˜μ—¬ ν•œκ΅­μ–΄ ↔ 쀑ꡭ어, ν•œκ΅­μ–΄ ↔ μ€‘κ΅­μ–΄λ‘œ λ²ˆμ—­λ©λ‹ˆλ‹€.
567
+ </div>
568
+ </div>
569
+ <div class="guide-item">
570
+ <div class="guide-number">2</div>
571
+ <div class="guide-text">
572
+ ν•œ-쀑 λ²ˆμ—­μ—μ„œλŠ” 자주 μ‚¬μš©ν•˜λŠ” ν‘œν˜„μ„ μΉ΄ν…Œκ³ λ¦¬λ³„λ‘œ 선택할 수 μžˆμŠ΅λ‹ˆλ‹€.
573
+ </div>
574
+ </div>
575
+ </div>
576
+ """
577
+
578
+ def create_interface():
579
+ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(
580
+ primary_hue=gr.themes.Color(
581
+ c50="#FFF7ED",
582
+ c100="#FFEDD5",
583
+ c200="#FED7AA",
584
+ c300="#FDBA74",
585
+ c400="#FB923C",
586
+ c500="#F97316",
587
+ c600="#EA580C",
588
+ c700="#C2410C",
589
+ c800="#9A3412",
590
+ c900="#7C2D12",
591
+ c950="#431407",
592
+ ),
593
+ secondary_hue="zinc",
594
+ neutral_hue="zinc",
595
+ font=("Pretendard", "sans-serif")
596
+ )) as demo:
597
+
598
+ gr.HTML(fontawesome_link)
599
+ gr.HTML(header_html)
600
+ gr.HTML(guide_html)
601
+
602
+ with gr.Tabs() as tabs:
603
+ with gr.TabItem("ν•œκ΅­μ–΄-쀑ꡭ어 λ²ˆμ—­"):
604
+ with gr.Column(elem_classes="custom-frame"):
605
+ gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/3097/3097412.png"> μΉ΄ν…Œκ³ λ¦¬ 선택</div>')
606
+ with gr.Row():
607
+ category_dropdown = gr.Dropdown(
608
+ label="1μ°¨ μΉ΄ν…Œκ³ λ¦¬ 선택",
609
+ choices=list(categories.keys()),
610
+ value=None
611
+ )
612
+ subcategory_dropdown = gr.Dropdown(
613
+ label="2μ°¨ 질문 선택",
614
+ choices=[],
615
+ value=None
616
+ )
617
+
618
+ with gr.Column(elem_classes="custom-frame"):
619
+ gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/4297/4297825.png"> λ²ˆμ—­</div>')
620
+ with gr.Row():
621
+ with gr.Column(scale=1):
622
+ kz_input_box = gr.Textbox(
623
+ label="μž…λ ₯ (ν•œκ΅­μ–΄ λ˜λŠ” 쀑ꡭ어)",
624
+ lines=8,
625
+ placeholder="λ²ˆμ—­ν•  ν…μŠ€νŠΈλ₯Ό μž…λ ₯ν•˜μ„Έμš”..."
626
+ )
627
+ with gr.Column(scale=1):
628
+ kz_output_box = gr.Textbox(
629
+ label="λ²ˆμ—­ κ²°κ³Ό",
630
+ lines=8
631
+ )
632
+
633
+ with gr.Column(elem_classes="custom-frame"):
634
+ gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/1022/1022293.png"> λ²ˆμ—­ μ‹€ν–‰</div>')
635
+ kz_translate_button = gr.Button("λ²ˆμ—­ν•˜κΈ°", elem_classes="custom-button")
636
+
637
+ # 1μ°¨ 선택 μ‹œ 2μ°¨ ν•­λͺ© μ—…λ°μ΄νŠΈ
638
+ category_dropdown.change(
639
+ fn=update_subcategories,
640
+ inputs=category_dropdown,
641
+ outputs=subcategory_dropdown
642
+ )
643
+
644
+ # 2μ°¨ 선택 μ‹œ μž…λ ₯λž€μ— ν•΄λ‹Ή κ°’ μžλ™ μž…λ ₯
645
+ subcategory_dropdown.change(
646
+ fn=set_input_text,
647
+ inputs=subcategory_dropdown,
648
+ outputs=kz_input_box
649
+ )
650
+
651
+ # λ²ˆμ—­ ν•¨μˆ˜
652
+ kz_translate_button.click(
653
+ fn=translate_ko_zh,
654
+ inputs=kz_input_box,
655
+ outputs=kz_output_box
656
+ )
657
+
658
+ with gr.TabItem("ν•œκ΅­μ–΄-μ˜μ–΄ λ²ˆμ—­"):
659
+ with gr.Column(elem_classes="custom-frame"):
660
+ gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/4297/4297825.png"> λ²ˆμ—­</div>')
661
+ with gr.Row():
662
+ with gr.Column(scale=1):
663
+ ke_input_box = gr.Textbox(
664
+ label="μž…λ ₯ ν…μŠ€νŠΈ (ν•œκ΅­μ–΄ λ˜λŠ” μ˜μ–΄)",
665
+ placeholder="λ²ˆμ—­ν•  ν…μŠ€νŠΈλ₯Ό μž…λ ₯ν•˜μ„Έμš”...",
666
+ lines=8
667
+ )
668
+ with gr.Column(scale=1):
669
+ ke_output_box = gr.Textbox(
670
+ label="λ²ˆμ—­ κ²°κ³Ό",
671
+ lines=8,
672
+ interactive=False
673
+ )
674
+
675
+ with gr.Column(elem_classes="custom-frame"):
676
+ gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/1022/1022293.png"> λ²ˆμ—­ μ‹€ν–‰</div>')
677
+ ke_translate_button = gr.Button("λ²ˆμ—­ν•˜κΈ°", elem_classes="custom-button")
678
+
679
+ # λ²ˆμ—­ ν•¨μˆ˜
680
+ ke_translate_button.click(
681
+ fn=translate_ko_en,
682
+ inputs=ke_input_box,
683
+ outputs=ke_output_box
684
+ )
685
+
686
+ return demo
687
+
688
+ if __name__ == "__main__":
689
+ demo = create_interface()
690
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio
2
+ langdetect
3
+ google-generativeai