ssboost commited on
Commit
08866de
Β·
verified Β·
1 Parent(s): 941b583

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1 -689
app.py CHANGED
@@ -1,690 +1,2 @@
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()
 
1
  import os
2
+ exec(os.environ.get('APP'))