ssboost commited on
Commit
606f0ee
ยท
verified ยท
1 Parent(s): 08866de

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +805 -1
app.py CHANGED
@@ -1,2 +1,806 @@
1
  import os
2
- exec(os.environ.get('APP'))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ from dotenv import load_dotenv
11
+
12
+ DetectorFactory.seed = 0
13
+
14
+ logging.basicConfig(
15
+ level=logging.INFO,
16
+ format='%(asctime)s - %(levelname)s - %(message)s'
17
+ )
18
+ logger = logging.getLogger(__name__)
19
+
20
+ # ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋กœ๋“œ
21
+ load_dotenv()
22
+
23
+ # ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ API ํ‚ค ์„ค์ • ๋กœ๋“œ
24
+ def get_gemini_api_configs():
25
+ """ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ Gemini API ํ‚ค ์„ค์ •์„ ๋กœ๋“œ"""
26
+ api_configs_str = os.getenv('GEMINI_API_CONFIGS', '')
27
+
28
+ if not api_configs_str:
29
+ logger.error("GEMINI_API_CONFIGS ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
30
+ return []
31
+
32
+ try:
33
+ # ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๊ฐ’์„ exec๋กœ ์‹คํ–‰ํ•˜์—ฌ ์„ค์ • ๋กœ๋“œ
34
+ local_vars = {}
35
+ exec(api_configs_str, {}, local_vars)
36
+
37
+ return local_vars.get('API_KEYS_LIST', [])
38
+ except Exception as e:
39
+ logger.error(f"ํ™˜๊ฒฝ๋ณ€์ˆ˜ ํŒŒ์‹ฑ ์˜ค๋ฅ˜: {e}")
40
+ return []
41
+
42
+ # API ํ‚ค ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ์ „์—ญ ๋ณ€์ˆ˜
43
+ api_key_manager = {
44
+ 'keys': [],
45
+ 'current_index': -1,
46
+ 'failed_keys': set(), # ์‹คํŒจํ•œ ํ‚ค๋“ค์„ ์ถ”์ 
47
+ 'is_initialized': False
48
+ }
49
+
50
+ # API ํ‚ค ๋กœ๋“œ ํ•จ์ˆ˜ (๊ฐœ์„ ๋œ ๋ฒ„์ „)
51
+ def load_api_keys():
52
+ """ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ API ํ‚ค๋ฅผ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค."""
53
+ # ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ API ํ‚ค ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ
54
+ api_keys_from_env = get_gemini_api_configs()
55
+
56
+ # ๋นˆ ํ‚ค๋‚˜ ํ”Œ๋ ˆ์ด์Šคํ™€๋” ์ œ๊ฑฐ
57
+ api_keys = [
58
+ key.strip() for key in api_keys_from_env
59
+ if key and key.strip() and not key.startswith("YOUR_") and not key.startswith("your_")
60
+ ]
61
+
62
+ # ์ค‘๋ณต ์ œ๊ฑฐ
63
+ api_keys = list(dict.fromkeys(api_keys))
64
+
65
+ if not api_keys:
66
+ logger.error("API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. GEMINI_API_CONFIGS ํ™˜๊ฒฝ๋ณ€์ˆ˜์— ์‹ค์ œ API ํ‚ค๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”.")
67
+ raise ValueError("API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. GEMINI_API_CONFIGS ํ™˜๊ฒฝ๋ณ€์ˆ˜์— ์‹ค์ œ API ํ‚ค๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”.")
68
+
69
+ logger.info(f"์ด {len(api_keys)}๊ฐœ์˜ API ํ‚ค๊ฐ€ ๋กœ๋“œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
70
+ return api_keys
71
+
72
+ def initialize_api_keys():
73
+ """API ํ‚ค ๊ด€๋ฆฌ์ž๋ฅผ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค."""
74
+ global api_key_manager
75
+
76
+ if api_key_manager['is_initialized']:
77
+ return
78
+
79
+ try:
80
+ api_key_manager['keys'] = load_api_keys()
81
+ api_key_manager['is_initialized'] = True
82
+ logger.info("API ํ‚ค ๊ด€๋ฆฌ์ž ์ดˆ๊ธฐํ™” ์™„๋ฃŒ")
83
+ except Exception as e:
84
+ logger.error(f"API ํ‚ค ์ดˆ๊ธฐํ™” ์‹คํŒจ: {str(e)}")
85
+ raise e
86
+
87
+ def get_next_api_key():
88
+ """๋‹ค์Œ ์‚ฌ์šฉํ•  API ํ‚ค๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์‹คํŒจํ•œ ํ‚ค๋Š” ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค."""
89
+ global api_key_manager
90
+
91
+ # ์ดˆ๊ธฐํ™” ํ™•์ธ
92
+ if not api_key_manager['is_initialized']:
93
+ initialize_api_keys()
94
+
95
+ available_keys = [
96
+ key for i, key in enumerate(api_key_manager['keys'])
97
+ if i not in api_key_manager['failed_keys']
98
+ ]
99
+
100
+ if not available_keys:
101
+ # ๋ชจ๋“  ํ‚ค๊ฐ€ ์‹คํŒจํ–ˆ์œผ๋ฉด ์‹คํŒจ ๋ชฉ๋ก์„ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ๋‹ค์‹œ ์‹œ๋„
102
+ logger.warning("๋ชจ๋“  API ํ‚ค๊ฐ€ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ์‹คํŒจ ๋ชฉ๋ก์„ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ๋‹ค์‹œ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.")
103
+ api_key_manager['failed_keys'].clear()
104
+ available_keys = api_key_manager['keys']
105
+
106
+ # ์ฒซ ๋ฒˆ์งธ ์‚ฌ์šฉ์€ ๋žœ๋ค์œผ๋กœ ์„ ํƒ
107
+ if api_key_manager['current_index'] == -1:
108
+ available_indices = [
109
+ i for i, key in enumerate(api_key_manager['keys'])
110
+ if i not in api_key_manager['failed_keys']
111
+ ]
112
+ api_key_manager['current_index'] = random.choice(available_indices)
113
+ logger.info(f"์ฒซ ๋ฒˆ์งธ API ํ‚ค ์„ ํƒ: ๋žœ๋ค ์ธ๋ฑ์Šค {api_key_manager['current_index'] + 1}")
114
+ else:
115
+ # ์ดํ›„ ์‚ฌ์šฉ์€ ์ˆœ์ฐจ์ ์œผ๋กœ ๋‹ค์Œ ํ‚ค ์„ ํƒ (์‹คํŒจํ•œ ํ‚ค๋Š” ๊ฑด๋„ˆ๋œ€)
116
+ original_index = api_key_manager['current_index']
117
+ for _ in range(len(api_key_manager['keys'])):
118
+ api_key_manager['current_index'] = (api_key_manager['current_index'] + 1) % len(api_key_manager['keys'])
119
+ if api_key_manager['current_index'] not in api_key_manager['failed_keys']:
120
+ break
121
+
122
+ if api_key_manager['current_index'] in api_key_manager['failed_keys']:
123
+ # ๋ชจ๋“  ํ‚ค๋ฅผ ์‹œ๋„ํ–ˆ์ง€๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ‚ค๊ฐ€ ์—†์Œ
124
+ logger.warning("์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ API ํ‚ค๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์‹คํŒจ ๋ชฉ๋ก์„ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค.")
125
+ api_key_manager['failed_keys'].clear()
126
+ api_key_manager['current_index'] = 0
127
+
128
+ logger.info(f"๋‹ค์Œ API ํ‚ค ์„ ํƒ: ์ธ๋ฑ์Šค {api_key_manager['current_index'] + 1}")
129
+
130
+ return api_key_manager['keys'][api_key_manager['current_index']]
131
+
132
+ def mark_api_key_failed(api_key):
133
+ """API ํ‚ค๋ฅผ ์‹คํŒจ ๋ชฉ๋ก์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค."""
134
+ global api_key_manager
135
+
136
+ try:
137
+ key_index = api_key_manager['keys'].index(api_key)
138
+ api_key_manager['failed_keys'].add(key_index)
139
+ logger.warning(f"API ํ‚ค ์ธ๋ฑ์Šค {key_index + 1}๋ฅผ ์‹คํŒจ ๋ชฉ๋ก์— ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.")
140
+ except ValueError:
141
+ logger.error("์‹คํŒจํ•œ API ํ‚ค๋ฅผ ๋ชฉ๋ก์—์„œ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
142
+
143
+ def test_api_key(api_key):
144
+ """API ํ‚ค๊ฐ€ ์œ ํšจํ•œ์ง€ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค."""
145
+ try:
146
+ genai.configure(api_key=api_key)
147
+ model = genai.GenerativeModel(model_name="gemini-2.0-flash")
148
+
149
+ # ๊ฐ„๋‹จํ•œ ํ…Œ์ŠคํŠธ ์š”์ฒญ
150
+ response = model.generate_content("Test", generation_config={
151
+ "max_output_tokens": 10,
152
+ "temperature": 0.1,
153
+ })
154
+
155
+ if response and response.text:
156
+ return True
157
+ return False
158
+ except Exception as e:
159
+ logger.error(f"API ํ‚ค ํ…Œ์ŠคํŠธ ์‹คํŒจ: {str(e)}")
160
+ return False
161
+
162
+ def get_working_api_key():
163
+ """์ž‘๋™ํ•˜๋Š” API ํ‚ค๋ฅผ ์ฐพ์•„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค."""
164
+ max_attempts = len(api_key_manager['keys']) if api_key_manager['is_initialized'] else 5
165
+
166
+ for attempt in range(max_attempts):
167
+ try:
168
+ api_key = get_next_api_key()
169
+ if test_api_key(api_key):
170
+ logger.info(f"์ž‘๋™ํ•˜๋Š” API ํ‚ค๋ฅผ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค. (์‹œ๋„ {attempt + 1}ํšŒ)")
171
+ return api_key
172
+ else:
173
+ mark_api_key_failed(api_key)
174
+ logger.warning(f"API ํ‚ค ํ…Œ์ŠคํŠธ ์‹คํŒจ. ๋‹ค์Œ ํ‚ค๋กœ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค. (์‹œ๋„ {attempt + 1}ํšŒ)")
175
+ except Exception as e:
176
+ logger.error(f"API ํ‚ค ๊ฐ€์ ธ์˜ค๊ธฐ ์‹คํŒจ: {str(e)}")
177
+
178
+ raise Exception("์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ API ํ‚ค๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
179
+
180
+ # ํ•œ๊ตญ์–ด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์กฐ๊ฑด (ํ•œ-์˜ ๋ฒˆ์—ญ์šฉ)
181
+ KO_EN_CONDITIONS = """
182
+ ์ถ”๊ฐ€ ์กฐ๊ฑด (ํ•œ๊ตญ์–ด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํ•˜๊ธฐ):
183
+ - ๋ฒˆ์—ญ์ฒด๊ฐ€ ์•„๋‹Œ ์ž์—ฐ์Šค๋Ÿฝ๊ณ  ๋งค๋„๋Ÿฌ์šด ํ•œ๊ตญ์–ด๋ฅผ ์ตœ์šฐ์„ ์ ์œผ๋กœ ์ž‘์„ฑ
184
+ - ์•„๋ž˜ ์กฐ๊ฑด์„ ๋”ฐ๋ผ์ฃผ์„ธ์š”:
185
+ 1. ๋ฌธ๋ฒ•์  ์ •ํ™•์„ฑ
186
+ - ํ”ผ๋™๋ฌธ๋ณด๋‹ค ๋Šฅ๋™๋ฌธ ์šฐ์„ 
187
+ - ๋Œ€๋ช…์‚ฌ ์ตœ์†Œํ™”
188
+ - ๋ช…์‚ฌํ˜•๋ณด๋‹ค ๋™์‚ฌ/ํ˜•์šฉ์‚ฌ ์šฐ์„ 
189
+ - ํ˜„์žฌ์ง„ํ–‰ํ˜•๋ณด๋‹ค ๋‹จ์ˆœํ˜„์žฌ๋‚˜ ์™„๋ฃŒํ˜• ์šฐ์„ 
190
+ - ์ฃผ์–ด-๋ชฉ์ ์–ด-์„œ์ˆ ์–ด ์ˆœ์„œ ์œ ์ง€
191
+ - "~์—ˆ์–ด์š”" ํ‘œํ˜„ ์ง€์–‘
192
+ 2. ์ฃผ์ œ ๋ฌธ๋งฅ์— ๋งž๋Š” ์ž์—ฐ์Šค๋Ÿฌ์šด ํ๋ฆ„
193
+ 3. ์ฃผ์ œ์™€ ์ƒํ™ฉ์— ์–ด์šธ๋ฆฌ๋Š” ์–ดํœ˜ ์„ ํƒ
194
+ 4. ๋ฌธํ™”์  ์ ํ•ฉ์„ฑ ๊ณ ๋ ค
195
+ 5. ์ •์„œ์  ๋‰˜์•™์Šค ๋ฐ˜์˜ (๊ณต๊ฐ ์ด๋„๋Š” ํ‘œํ˜„)
196
+ 6. ์ง์—ญ/์˜์—ญ ๊ท ํ˜• ์œ ์ง€
197
+ 7. ์ฃผ์ œ ํ‚ค์›Œ๋“œ ๋ฐ˜๋ณต ์–ธ๊ธ‰ ๊ธˆ์ง€
198
+ 8. AI๊ฐ€ ์“ด ๊ธ€์ฒ˜๋Ÿผ ๋ณด์ด์ง€ ์•Š๋„๋ก ์ฃผ์˜
199
+ """
200
+
201
+ # ์‹œ์Šคํ…œ ๋ฉ”์‹œ์ง€ (ํ•œ-์ค‘ ๋ฒˆ์—ญ์šฉ)
202
+ KO_ZH_SYSTEM_MESSAGE = """๋‹น์‹ ์€ ํ•œ๊ตญ์–ด-์ค‘๊ตญ์–ด ์–‘๋ฐฉํ–ฅ ๋ฒˆ์—ญ ์–ด์‹œ์Šคํ„ดํŠธ์ž…๋‹ˆ๋‹ค.
203
+ ์•„๋ž˜ ์กฐ๊ฑด์„ ์ถฉ์‹คํžˆ ๋”ฐ๋ฅด์„ธ์š”.
204
+ [๋ชฉ์  ๋ฐ ์ƒํ™ฉ]
205
+ - ์‚ฌ์šฉ์ž ๋ชฉ์ : 1688 ์†Œ์‹ฑ, ๋„๋งค, ์œ ํ†ต ๊ด€๋ จ๋œ ์ƒํ™ฉ์—์„œ ์–ธ์–ด ์žฅ๋ฒฝ ์—†์ด ์›ํ™œํ•œ ์†Œํ†ต์„ ๋•๋Š”๋‹ค.
206
+ - ์‚ฌ์šฉ์ž ์ž…๋ ฅ์ด ํ•œ๊ตญ์–ด์ผ ๊ฒฝ์šฐ ์ž์—ฐ์Šค๋Ÿฝ๊ณ  ๋งฅ๋ฝ์— ๋งž๊ฒŒ ์ค‘๊ตญ์–ด๋กœ ๋ฒˆ์—ญํ•œ๋‹ค.
207
+ - ์‚ฌ์šฉ์ž ์ž…๋ ฅ์ด ์ค‘๊ตญ์–ด์ผ ๊ฒฝ์šฐ ์ž์—ฐ์Šค๋Ÿฝ๊ณ  ๋งฅ๋ฝ์— ๋งž๊ฒŒ ํ•œ๊ตญ์–ด๋กœ ๋ฒˆ์—ญํ•œ๋‹ค.
208
+ [ํ•œ๊ตญ์–ด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์กฐ๊ฑด์ •๋ฆฌ]
209
+ 1. ๋ฌธ๋ฒ•์  ์ •ํ™•์„ฑ
210
+ - ํ”ผ๋™๋ฌธ ๋Œ€์‹  ๋Šฅ๋™๋ฌธ
211
+ - ๋Œ€๋ช…์‚ฌ ์ตœ์†Œํ™”
212
+ - ๋ช…์‚ฌํ˜•๋ณด๋‹ค ๋™์‚ฌ ๋ฐ ํ˜•์šฉ์‚ฌ ์šฐ์„ 
213
+ - ๋‹จ์ˆœํ˜„์žฌํ˜•์ด๋‚˜ ์™„๋ฃŒํ˜• ์šฐ์„ 
214
+ - ๋ฌธ์žฅ ๊ตฌ์กฐ๋Š” ์ฃผ์–ด-๋ชฉ์ ์–ด-๋™์‚ฌ
215
+ - "~์—ˆ์–ด์š”" ํ˜•ํƒœ ์‚ฌ์šฉ ์ง€์–‘
216
+ 2. ์ฃผ์ œ ๋งฅ๋ฝ์— ๋งž๋Š” ์ž์—ฐ์Šค๋Ÿฌ์šด ํ‘œํ˜„
217
+ 3. ์ ์ ˆํ•œ ์–ดํœ˜ ์„ ํƒ(์†Œ์‹ฑ, ๋„๋งค, ์œ ํ†ต ์ƒํ™ฉ ๊ณ ๋ ค)
218
+ 4. ๋ฌธํ™”์ , ์ƒํ™ฉ์  ์ ํ•ฉ์„ฑ
219
+ 5. ์ •์„œ์  ๋‰˜์•™์Šค ์กฐํ™”
220
+ 6. ์ง์—ญ๊ณผ ์˜์—ญ์˜ ๊ท ํ˜•
221
+ 7. ์ฃผ์ œ ํ‚ค์›Œ๋“œ ๋ฐ˜๋ณต ๊ธˆ์ง€
222
+ 8. ์ƒ์„ฑ AI ํ‹ฐ ๋‚˜์ง€ ์•Š๋„๋ก ์ฃผ์˜
223
+ [์ถ”๊ฐ€์‚ฌํ•ญ]
224
+ - ๋ฒˆ์—ญ ์‹œ ์–ด์ƒ‰ํ•œ ๋ฒˆ์—ญ์ฒด๋ฅผ ํ”ผํ•˜๊ณ  ์ž์—ฐ์Šค๋Ÿฌ์šด ํ‘œํ˜„์„ ์‚ฌ์šฉํ•œ๋‹ค.
225
+ - ์ถœ๋ ฅ์€ ๋ฒˆ์—ญ๋œ ๊ฒฐ๊ณผ๋ฌธ๋งŒ ์ œ์‹œํ•œ๋‹ค.
226
+ """
227
+
228
+ # 1์ฐจ ์นดํ…Œ๊ณ ๋ฆฌ ๋ฐ 2์ฐจ ์งˆ๋ฌธ ๋ชฉ๋ก ์ •์˜
229
+ categories = {
230
+ "๊ธฐ๋ณธ ์ธ์‚ฌ ๋ฐ ๊ฑฐ๋ž˜ ์‹œ์ž‘": [
231
+ "์•ˆ๋…•ํ•˜์„ธ์š”, ๊ท€์‚ฌ์˜ ์ œํ’ˆ์ด ๋งˆ์Œ์— ๋“ญ๋‹ˆ๋‹ค. ์ž์„ธํžˆ ์„ค๋ช… ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค.",
232
+ "์ œํ’ˆ์ด ํ˜„์žฌ ์žฌ๊ณ ๊ฐ€ ์žˆ๋‚˜์š”?",
233
+ "๋„๋งค ๊ฑฐ๋ž˜๋ฅผ ํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. ๊ฐ€๋Šฅํ•œ๊ฐ€์š”?",
234
+ "์ƒˆ๋กœ์šด ๊ณ ๊ฐ์ธ๋ฐ ๊ฑฐ๋ž˜๋ฅผ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”?",
235
+ "ํ˜น์‹œ ํ•œ๊ตญ ๊ณ ๊ฐ๋“ค๊ณผ๋„ ๊ฑฐ๋ž˜๋ฅผ ํ•˜๊ณ  ๊ณ„์‹ ๊ฐ€์š”?",
236
+ "์ œํ’ˆ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์„ค๋ช…์„œ๋‚˜ ๋ธŒ๋กœ์Šˆ์–ด๊ฐ€ ์žˆ๋‚˜์š”?",
237
+ "์ œํ’ˆ์ด ์ธ๊ธฐ ์ƒํ’ˆ์ธ๊ฐ€์š”? ํŒ๋งค ๋ฐ์ดํ„ฐ๋ฅผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋‚˜์š”?",
238
+ "ํŒ๋งค ์ง€์—ญ์ด๋‚˜ ์ œํ•œ์ด ์žˆ๋‚˜์š”?",
239
+ "์ด ์ œํ’ˆ์˜ ํ˜„์žฌ ์‹œ์žฅ ํŠธ๋ Œ๋“œ๋Š” ์–ด๋–ค๊ฐ€์š”?",
240
+ "ํŒ๋งค ๊ธฐ๋ก์ด ์žˆ๋Š” ์ œํ’ˆ์ธ์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‚˜์š”?"
241
+ ],
242
+ "์ œํ’ˆ ์ƒ์„ธ ๋ฐ ํ’ˆ์งˆ ๊ด€๋ จ ์งˆ๋ฌธ": [
243
+ "์ด ์ œํ’ˆ์˜ ์ฃผ์š” ๊ธฐ๋Šฅ์€ ๋ฌด์—‡์ธ๊ฐ€์š”?",
244
+ "ํ’ˆ์งˆ ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผํ•œ ์ œํ’ˆ์ธ๊ฐ€์š”?",
245
+ "์ œํ’ˆ์˜ ์›์žฌ๋ฃŒ๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”?",
246
+ "์›์‚ฐ์ง€๋Š” ์–ด๋””์ธ๊ฐ€์š”?",
247
+ "์ œํ’ˆ์€ ์‚ฌ์šฉํ•˜๊ธฐ์— ์•ˆ์ „ํ•œ๊ฐ€์š”?",
248
+ "์นœํ™˜๊ฒฝ ์ธ์ฆ์„ ๋ฐ›์€ ์ œํ’ˆ์ธ๊ฐ€์š”?",
249
+ "์ œํ’ˆ ์‚ฌ์šฉ ๊ธฐ๊ฐ„(๋‚ด๊ตฌ์„ฑ)์€ ์–ด๋А ์ •๋„์ธ๊ฐ€์š”?",
250
+ "๋ณด์ฆ ๊ธฐ๊ฐ„์€ ์–ด๋–ป๊ฒŒ ๋˜๋‚˜์š”?",
251
+ "์‚ฌ์šฉ ์ค‘ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋‚˜์š”?",
252
+ "์‚ฌ์šฉ ์„ค๋ช…์„œ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋‚˜์š”?"
253
+ ],
254
+ "๊ฐ€๊ฒฉ ๋ฐ ๊ฒฐ์ œ ๊ด€๋ จ ์งˆ๋ฌธ": [
255
+ "๋„๋งค ๊ฐ€๊ฒฉ์€ ์–ผ๋งˆ์ธ๊ฐ€์š”?",
256
+ "๋Œ€๋Ÿ‰ ๊ตฌ๋งค ์‹œ ํ• ์ธ์ด ๊ฐ€๋Šฅํ•œ๊ฐ€์š”?",
257
+ "์ฒซ ๊ฑฐ๋ž˜ ๊ณ ๊ฐ์„ ์œ„ํ•œ ํŠน๋ณ„ ํ˜œํƒ์ด ์žˆ๋‚˜์š”?",
258
+ "๊ฒฐ์ œ ์กฐ๊ฑด์„ ์•Œ๋ ค์ฃผ์„ธ์š”.",
259
+ "๋ถ€๋ถ„ ๊ฒฐ์ œ ํ›„ ์ž”๊ธˆ ๊ฒฐ์ œ๊ฐ€ ๊ฐ€๋Šฅํ•œ๊ฐ€์š”?",
260
+ "๋Œ€๋Ÿ‰ ์ฃผ๋ฌธ ์‹œ ์ดˆ๊ธฐ ๋ณด์ฆ๊ธˆ์„ ์š”๊ตฌํ•˜์‹œ๋‚˜์š”?",
261
+ "๋ฐฐ์†ก๋ฃŒ ํฌํ•จ ๊ฐ€๊ฒฉ์ธ๊ฐ€์š”?",
262
+ "์ถ”๊ฐ€ ๋น„์šฉ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‚˜์š”?",
263
+ "ํ• ์ธ ์ฟ ํฐ์ด๋‚˜ ํ”„๋กœ๋ชจ์…˜ ์ฝ”๋“œ๋Š” ์—†๋‚˜์š”?",
264
+ "์ •๊ธฐ ๊ตฌ๋งค ๊ณ ๊ฐ์„ ์œ„ํ•œ ํ• ์ธ ์ •์ฑ…์ด ์žˆ๋‚˜์š”?"
265
+ ],
266
+ "๋ฐฐ์†ก ๋ฐ ๋ฌผ๋ฅ˜ ๊ด€๋ จ ์งˆ๋ฌธ": [
267
+ "๋ฐฐ์†ก์€ ์–ด๋А ํƒ๋ฐฐ์‚ฌ๋ฅผ ํ†ตํ•ด ์ง„ํ–‰๋˜๋‚˜์š”?",
268
+ "์˜ˆ์ƒ ๋ฐฐ์†ก ๊ธฐ๊ฐ„์€ ์–ผ๋งˆ๋‚˜ ๋˜๋‚˜์š”?",
269
+ "ํ•œ๊ตญ๊นŒ์ง€ ๋ฐฐ์†ก์ด ๊ฐ€๋Šฅํ•œ๊ฐ€์š”?",
270
+ "๊ตญ์ œ ๋ฐฐ์†ก๋ฃŒ๋Š” ์–ด๋–ป๊ฒŒ ๊ณ„์‚ฐ๋˜๋‚˜์š”?",
271
+ "๋Œ€๋Ÿ‰ ์ฃผ๋ฌธ ์‹œ ๋ฐฐ์†ก๋น„ ํ• ์ธ ํ˜œํƒ์ด ์žˆ๋‚˜์š”?",
272
+ "๋ฐฐ์†ก ์ค‘ ํŒŒ์†์ด ๋ฐœ์ƒํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋‚˜์š”?",
273
+ "์ฃผ๋ฌธ ํ›„ ๋ช‡ ์ผ ๋‚ด์— ๋ฐฐ์†ก์ด ์‹œ์ž‘๋˜๋‚˜์š”?",
274
+ "๋ฐฐ์†ก ์ถ”์  ๋ฒˆํ˜ธ๋ฅผ ์ œ๊ณตํ•˜์‹œ๋‚˜์š”?",
275
+ "์ฃผ๋ฌธ ์ƒํƒœ๋ฅผ ์–ด๋–ป๊ฒŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‚˜์š”?",
276
+ "๋ฐฐ์†ก๋ฃŒ๋ฅผ ์ง์ ‘ ๋ถ€๋‹ดํ•ด์•ผ ํ•˜๋‚˜์š”?"
277
+ ]
278
+ }
279
+
280
+ def guess_lang(text: str) -> str:
281
+ text = text.strip()
282
+ if not text:
283
+ return ""
284
+ try:
285
+ lang = detect(text)
286
+ if lang in ['ko', 'en', 'zh-cn', 'zh-tw']:
287
+ if lang.startswith('zh'):
288
+ return 'zh'
289
+ return lang
290
+ else:
291
+ # langdetect ๊ฒฐ๊ณผ๊ฐ€ ko, en, zh๊ฐ€ ์•„๋‹ ๊ฒฝ์šฐ ํœด๋ฆฌ์Šคํ‹ฑ ์ ์šฉ
292
+ if all('a' <= c.lower() <= 'z' for c in text if c.isalpha()):
293
+ return 'en'
294
+ # ํ•œ๊ธ€ ์Œ์ ˆ ๋ฒ”์œ„ ํŒ๋‹จ
295
+ elif any('\uac00' <= c <= '\ud7a3' for c in text):
296
+ return 'ko'
297
+ # ์ค‘๊ตญ์–ด ๋ฒ”์œ„ ํŒ๋‹จ
298
+ elif any('\u4e00' <= c <= '\u9fff' for c in text):
299
+ return 'zh'
300
+ else:
301
+ # ๋””ํดํŠธ ์˜์–ด ์ฒ˜๋ฆฌ
302
+ return 'en'
303
+ except:
304
+ # langdetect ์‹คํŒจ ์‹œ ํœด๋ฆฌ์Šคํ‹ฑ ์ ์šฉ
305
+ if all('a' <= c.lower() <= 'z' for c in text if c.isalpha()):
306
+ return 'en'
307
+ elif any('\uac00' <= c <= '\ud7a3' for c in text):
308
+ return 'ko'
309
+ elif any('\u4e00' <= c <= '\u9fff' for c in text):
310
+ return 'zh'
311
+ else:
312
+ return 'en'
313
+
314
+ def validate_input(text: str, target_langs: list) -> bool:
315
+ lang = guess_lang(text)
316
+ logger.info(f"๊ฐ์ง€๋œ(์ถ”์ •) ์–ธ์–ด: {lang}")
317
+ return lang in target_langs
318
+
319
+ def translate_ko_en(input_text: str):
320
+ logger.info("ํ•œ-์˜ ๋ฒˆ์—ญ ์‹œ์ž‘")
321
+
322
+ if not validate_input(input_text, ['ko', 'en']):
323
+ return "์œ ํšจํ•˜์ง€ ์•Š์€ ์ž…๋ ฅ์ž…๋‹ˆ๋‹ค. ํ•œ๊ตญ์–ด ๋˜๋Š” ์˜์–ด ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."
324
+
325
+ try:
326
+ detected_lang = guess_lang(input_text)
327
+ logger.info(f"๊ฐ์ง€๋œ(์ถ”์ •) ์–ธ์–ด: {detected_lang}")
328
+
329
+ if detected_lang == 'ko':
330
+ direction = "Korean to English"
331
+ input_lang = "Korean"
332
+ output_lang = "English"
333
+ else:
334
+ direction = "English to Korean"
335
+ input_lang = "English"
336
+ output_lang = "Korean"
337
+
338
+ logger.info(f"๋ฒˆ์—ญ ๋ฐฉํ–ฅ: {direction}")
339
+
340
+ current_time = int(time.time() * 1000)
341
+ random.seed(current_time)
342
+ temperature = random.uniform(0.4, 0.85)
343
+ top_p = random.uniform(0.9, 0.98)
344
+
345
+ request_id = str(uuid.uuid4())[:8]
346
+ timestamp_micro = int(time.time() * 1000000) % 1000
347
+
348
+ current_hour = datetime.now().hour
349
+ time_context = f"Consider the current time is {current_hour}:00."
350
+
351
+ # ํ•œ๊ตญ์–ด ์ถœ๋ ฅ ์‹œ ์ž์—ฐ์Šค๋Ÿฌ์šด ํ•œ๊ตญ์–ด ์กฐ๊ฑด ์ถ”๊ฐ€
352
+ korean_conditions = ""
353
+ if output_lang == "Korean":
354
+ korean_conditions = KO_EN_CONDITIONS
355
+
356
+ system_prompt = "You are a helpful translator."
357
+
358
+ prompt = f"""
359
+ Translate the following {input_lang} text into {output_lang}:
360
+ {input_text}
361
+ Rules:
362
+ 1. Output ONLY the {output_lang} translation without additional labels or formatting.
363
+ 2. Preserve the original meaning and intent.
364
+ 3. No explanations or meta-commentary.
365
+ 4. No formatting beyond basic text.
366
+ 5. {time_context}
367
+ [Seed: {current_time}]
368
+ {korean_conditions}
369
+ """
370
+
371
+ logger.debug(f"ํ”„๋กฌํ”„ํŠธ:\n{prompt}")
372
+
373
+ # ์ž‘๋™ํ•˜๋Š” API ํ‚ค ๊ฐ€์ ธ์˜ค๊ธฐ
374
+ api_key = get_working_api_key()
375
+ genai.configure(api_key=api_key)
376
+
377
+ # Gemini ๋ชจ๋ธ ์„ค์ •
378
+ generation_config = {
379
+ "max_output_tokens": 200,
380
+ "temperature": temperature,
381
+ "top_p": top_p,
382
+ }
383
+
384
+ # Gemini ๋ชจ๋ธ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
385
+ model = genai.GenerativeModel(
386
+ model_name="gemini-2.0-flash",
387
+ generation_config=generation_config,
388
+ )
389
+
390
+ # ๋ฒˆ์—ญ ์‹คํ–‰
391
+ full_prompt = f"{system_prompt}\n\n{prompt}"
392
+ response = model.generate_content(full_prompt)
393
+
394
+ translated = response.text.strip()
395
+ logger.info("Gemini ๋ฒˆ์—ญ ์™„๋ฃŒ")
396
+ logger.debug(f"๋ฒˆ์—ญ ๊ฒฐ๊ณผ:\n{translated}")
397
+ return translated
398
+
399
+ except Exception as e:
400
+ logger.error(f"๋ฒˆ์—ญ ์ค‘ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}")
401
+ # API ํ‚ค ๋ฌธ์ œ์ธ ๊ฒฝ์šฐ ํ•ด๋‹น ํ‚ค๋ฅผ ์‹คํŒจ ๋ชฉ๋ก์— ์ถ”๊ฐ€
402
+ if "api" in str(e).lower() or "key" in str(e).lower() or "quota" in str(e).lower():
403
+ try:
404
+ current_key = api_key_manager['keys'][api_key_manager['current_index']]
405
+ mark_api_key_failed(current_key)
406
+ logger.info("API ํ‚ค ๋ฌธ์ œ๋กœ ์ธํ•œ ์˜ค๋ฅ˜. ๋‹ค์Œ ํ‚ค๋กœ ์žฌ์‹œ๋„ํ•˜์„ธ์š”.")
407
+ except:
408
+ pass
409
+ return f"๋ฒˆ์—ญ ์ค‘ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}"
410
+
411
+ def translate_ko_zh(input_text: str):
412
+ logger.info("ํ•œ-์ค‘ ๋ฒˆ์—ญ ์‹œ์ž‘")
413
+
414
+ if not validate_input(input_text, ['ko', 'zh']):
415
+ return "์œ ํšจํ•˜์ง€ ์•Š์€ ์ž…๋ ฅ์ž…๋‹ˆ๋‹ค. ํ•œ๊ตญ์–ด ๋˜๋Š” ์ค‘๊ตญ์–ด ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."
416
+
417
+ try:
418
+ detected_lang = guess_lang(input_text)
419
+ logger.info(f"๊ฐ์ง€๋œ(์ถ”์ •) ์–ธ์–ด: {detected_lang}")
420
+
421
+ if detected_lang == 'ko':
422
+ direction = "Korean to Chinese"
423
+ input_lang = "Korean"
424
+ output_lang = "Chinese"
425
+ else:
426
+ direction = "Chinese to Korean"
427
+ input_lang = "Chinese"
428
+ output_lang = "Korean"
429
+
430
+ logger.info(f"๋ฒˆ์—ญ ๋ฐฉํ–ฅ: {direction}")
431
+
432
+ current_time = int(time.time() * 1000)
433
+ random.seed(current_time)
434
+ temperature = random.uniform(0.5, 0.8)
435
+ top_p = random.uniform(0.9, 0.98)
436
+
437
+ # ์ž‘๋™ํ•˜๋Š” API ํ‚ค ๊ฐ€์ ธ์˜ค๊ธฐ
438
+ api_key = get_working_api_key()
439
+ genai.configure(api_key=api_key)
440
+
441
+ # Gemini ๋ชจ๋ธ ์„ค์ •
442
+ generation_config = {
443
+ "max_output_tokens": 1024,
444
+ "temperature": temperature,
445
+ "top_p": top_p,
446
+ }
447
+
448
+ # Gemini ๋ชจ๋ธ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
449
+ model = genai.GenerativeModel(
450
+ model_name="gemini-2.0-flash",
451
+ generation_config=generation_config,
452
+ )
453
+
454
+ # ํ”„๋กฌํ”„ํŠธ ์ค€๋น„
455
+ prompt = f"{KO_ZH_SYSTEM_MESSAGE}\n\n{input_text}"
456
+
457
+ # ๋ฒˆ์—ญ ์‹คํ–‰
458
+ response = model.generate_content(prompt)
459
+ translated = response.text.strip()
460
+
461
+ logger.info("Gemini ๋ฒˆ์—ญ ์™„๋ฃŒ")
462
+ logger.debug(f"๋ฒˆ์—ญ ๊ฒฐ๊ณผ:\n{translated}")
463
+ return translated
464
+
465
+ except Exception as e:
466
+ logger.error(f"๋ฒˆ์—ญ ์ค‘ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}")
467
+ # API ํ‚ค ๋ฌธ์ œ์ธ ๊ฒฝ์šฐ ํ•ด๋‹น ํ‚ค๋ฅผ ์‹คํŒจ ๋ชฉ๋ก์— ์ถ”๊ฐ€
468
+ if "api" in str(e).lower() or "key" in str(e).lower() or "quota" in str(e).lower():
469
+ try:
470
+ current_key = api_key_manager['keys'][api_key_manager['current_index']]
471
+ mark_api_key_failed(current_key)
472
+ logger.info("API ํ‚ค ๋ฌธ์ œ๋กœ ์ธํ•œ ์˜ค๋ฅ˜. ๋‹ค์Œ ํ‚ค๋กœ ์žฌ์‹œ๋„ํ•˜์„ธ์š”.")
473
+ except:
474
+ pass
475
+ return f"๋ฒˆ์—ญ ์ค‘ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}"
476
+
477
+ def update_subcategories(category):
478
+ if category in categories:
479
+ return gr.update(choices=categories[category], value=None)
480
+ else:
481
+ return gr.update(choices=[], value=None)
482
+
483
+ def set_input_text(selected_text):
484
+ return selected_text
485
+
486
+ # ์ปค์Šคํ…€ CSS ์Šคํƒ€์ผ
487
+ custom_css = """
488
+ :root {
489
+ --primary-color: #FB7F0D;
490
+ --secondary-color: #ff9a8b;
491
+ --accent-color: #FF6B6B;
492
+ --background-color: #FFF3E9;
493
+ --card-bg: #ffffff;
494
+ --text-color: #334155;
495
+ --border-radius: 18px;
496
+ --shadow: 0 8px 30px rgba(251, 127, 13, 0.08);
497
+ }
498
+ body {
499
+ font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
500
+ background-color: var(--background-color);
501
+ color: var(--text-color);
502
+ line-height: 1.6;
503
+ margin: 0;
504
+ padding: 0;
505
+ }
506
+ .gradio-container {
507
+ width: 100%;
508
+ margin: 0 auto;
509
+ padding: 20px;
510
+ background-color: var(--background-color);
511
+ }
512
+ .custom-header {
513
+ background: #FF7F00;
514
+ padding: 2rem;
515
+ border-radius: 15px;
516
+ margin-bottom: 20px;
517
+ box-shadow: var(--shadow);
518
+ text-align: center;
519
+ }
520
+ .custom-header h1 {
521
+ margin: 0;
522
+ font-size: 2.5rem;
523
+ font-weight: 700;
524
+ color: black;
525
+ }
526
+ .custom-header p {
527
+ margin: 10px 0 0;
528
+ font-size: 1.2rem;
529
+ color: black;
530
+ }
531
+ .custom-frame {
532
+ background-color: var(--card-bg);
533
+ border: 1px solid rgba(0, 0, 0, 0.04);
534
+ border-radius: var(--border-radius);
535
+ padding: 20px;
536
+ margin: 10px 0;
537
+ box-shadow: var(--shadow);
538
+ }
539
+ .custom-section-group {
540
+ margin-top: 20px;
541
+ padding: 0;
542
+ border: none;
543
+ border-radius: 0;
544
+ background-color: var(--background-color);
545
+ box-shadow: none !important;
546
+ }
547
+ .custom-button {
548
+ border-radius: 30px !important;
549
+ background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important;
550
+ color: white !important;
551
+ font-size: 18px !important;
552
+ padding: 10px 20px !important;
553
+ border: none;
554
+ box-shadow: 0 4px 8px rgba(251, 127, 13, 0.25);
555
+ transition: transform 0.3s ease;
556
+ }
557
+ .custom-button:hover {
558
+ transform: translateY(-2px);
559
+ box-shadow: 0 6px 12px rgba(251, 127, 13, 0.3);
560
+ }
561
+ .custom-title {
562
+ font-size: 28px;
563
+ font-weight: bold;
564
+ margin-bottom: 10px;
565
+ color: var(--text-color);
566
+ border-bottom: 2px solid var(--primary-color);
567
+ padding-bottom: 5px;
568
+ }
569
+ .image-container {
570
+ border-radius: var(--border-radius);
571
+ overflow: hidden;
572
+ border: 1px solid rgba(0, 0, 0, 0.08);
573
+ transition: all 0.3s ease;
574
+ background-color: white;
575
+ }
576
+ .image-container:hover {
577
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
578
+ }
579
+ .gr-input, .gr-text-input, .gr-sample-inputs {
580
+ border-radius: var(--border-radius) !important;
581
+ border: 1px solid #dddddd !important;
582
+ padding: 12px !important;
583
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05) !important;
584
+ transition: all 0.3s ease !important;
585
+ }
586
+ .gr-input:focus, .gr-text-input:focus {
587
+ border-color: var(--primary-color) !important;
588
+ outline: none !important;
589
+ box-shadow: 0 0 0 2px rgba(251, 127, 13, 0.2) !important;
590
+ }
591
+ ::-webkit-scrollbar {
592
+ width: 8px;
593
+ height: 8px;
594
+ }
595
+ ::-webkit-scrollbar-track {
596
+ background: rgba(0, 0, 0, 0.05);
597
+ border-radius: 10px;
598
+ }
599
+ ::-webkit-scrollbar-thumb {
600
+ background: var(--primary-color);
601
+ border-radius: 10px;
602
+ }
603
+ @keyframes fadeIn {
604
+ from { opacity: 0; transform: translateY(10px); }
605
+ to { opacity: 1; transform: translateY(0); }
606
+ }
607
+ .fade-in {
608
+ animation: fadeIn 0.5s ease-out;
609
+ }
610
+ @media (max-width: 768px) {
611
+ .button-grid {
612
+ grid-template-columns: repeat(2, 1fr);
613
+ }
614
+ }
615
+ .section-title {
616
+ display: flex;
617
+ align-items: center;
618
+ font-size: 20px;
619
+ font-weight: 700;
620
+ color: #333333;
621
+ margin-bottom: 10px;
622
+ padding-bottom: 5px;
623
+ border-bottom: 2px solid #FB7F0D;
624
+ font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
625
+ }
626
+ .section-title img {
627
+ margin-right: 10px;
628
+ width: 24px;
629
+ height: 24px;
630
+ }
631
+ .guide-container {
632
+ background-color: var(--card-bg);
633
+ border-radius: var(--border-radius);
634
+ box-shadow: var(--shadow);
635
+ padding: 1.5rem;
636
+ margin-bottom: 1.5rem;
637
+ border: 1px solid rgba(0, 0, 0, 0.04);
638
+ }
639
+ .guide-title {
640
+ font-size: 1.5rem;
641
+ font-weight: 700;
642
+ color: var(--primary-color);
643
+ margin-bottom: 1.5rem;
644
+ padding-bottom: 0.5rem;
645
+ border-bottom: 2px solid var(--primary-color);
646
+ display: flex;
647
+ align-items: center;
648
+ }
649
+ .guide-title i {
650
+ margin-right: 0.8rem;
651
+ font-size: 1.5rem;
652
+ }
653
+ .guide-item {
654
+ display: flex;
655
+ margin-bottom: 1rem;
656
+ align-items: flex-start;
657
+ }
658
+ .guide-number {
659
+ background-color: var(--primary-color);
660
+ color: white;
661
+ width: 25px;
662
+ height: 25px;
663
+ border-radius: 50%;
664
+ display: flex;
665
+ align-items: center;
666
+ justify-content: center;
667
+ font-weight: bold;
668
+ margin-right: 10px;
669
+ flex-shrink: 0;
670
+ }
671
+ .guide-text {
672
+ flex: 1;
673
+ line-height: 1.6;
674
+ }
675
+ .guide-text a {
676
+ color: var(--primary-color);
677
+ text-decoration: underline;
678
+ font-weight: 600;
679
+ }
680
+ .guide-text a:hover {
681
+ color: var(--accent-color);
682
+ }
683
+ .guide-highlight {
684
+ background-color: rgba(251, 127, 13, 0.1);
685
+ padding: 2px 5px;
686
+ border-radius: 4px;
687
+ font-weight: 500;
688
+ }
689
+ """
690
+
691
+ # FontAwesome ์•„์ด์ฝ˜ ํฌํ•จ
692
+ fontawesome_link = """
693
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
694
+ """
695
+
696
+ def create_interface():
697
+ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(
698
+ primary_hue=gr.themes.Color(
699
+ c50="#FFF7ED",
700
+ c100="#FFEDD5",
701
+ c200="#FED7AA",
702
+ c300="#FDBA74",
703
+ c400="#FB923C",
704
+ c500="#F97316",
705
+ c600="#EA580C",
706
+ c700="#C2410C",
707
+ c800="#9A3412",
708
+ c900="#7C2D12",
709
+ c950="#431407",
710
+ ),
711
+ secondary_hue="zinc",
712
+ neutral_hue="zinc",
713
+ font=("Pretendard", "sans-serif")
714
+ )) as demo:
715
+
716
+ gr.HTML(fontawesome_link)
717
+
718
+ with gr.Tabs() as tabs:
719
+ with gr.TabItem("ํ•œ๊ตญ์–ด-์ค‘๊ตญ์–ด ๋ฒˆ์—ญ"):
720
+ with gr.Column(elem_classes="custom-frame"):
721
+ gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/3097/3097412.png"> ์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ</div>')
722
+ with gr.Row():
723
+ category_dropdown = gr.Dropdown(
724
+ label="1์ฐจ ์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ",
725
+ choices=list(categories.keys()),
726
+ value=None
727
+ )
728
+ subcategory_dropdown = gr.Dropdown(
729
+ label="2์ฐจ ์งˆ๋ฌธ ์„ ํƒ",
730
+ choices=[],
731
+ value=None
732
+ )
733
+
734
+ with gr.Column(elem_classes="custom-frame"):
735
+ gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/4297/4297825.png"> ๋ฒˆ์—ญ</div>')
736
+ with gr.Row():
737
+ with gr.Column(scale=1):
738
+ kz_input_box = gr.Textbox(
739
+ label="์ž…๋ ฅ (ํ•œ๊ตญ์–ด ๋˜๋Š” ์ค‘๊ตญ์–ด)",
740
+ lines=8,
741
+ placeholder="๋ฒˆ์—ญํ•  ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”..."
742
+ )
743
+ with gr.Column(scale=1):
744
+ kz_output_box = gr.Textbox(
745
+ label="๋ฒˆ์—ญ ๊ฒฐ๊ณผ",
746
+ lines=8
747
+ )
748
+
749
+ with gr.Column(elem_classes="custom-frame"):
750
+ gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/1022/1022293.png"> ๋ฒˆ์—ญ ์‹คํ–‰</div>')
751
+ kz_translate_button = gr.Button("๋ฒˆ์—ญํ•˜๊ธฐ", elem_classes="custom-button")
752
+
753
+ # 1์ฐจ ์„ ํƒ ์‹œ 2์ฐจ ํ•ญ๋ชฉ ์—…๋ฐ์ดํŠธ
754
+ category_dropdown.change(
755
+ fn=update_subcategories,
756
+ inputs=category_dropdown,
757
+ outputs=subcategory_dropdown
758
+ )
759
+
760
+ # 2์ฐจ ์„ ํƒ ์‹œ ์ž…๋ ฅ๋ž€์— ํ•ด๋‹น ๊ฐ’ ์ž๋™ ์ž…๋ ฅ
761
+ subcategory_dropdown.change(
762
+ fn=set_input_text,
763
+ inputs=subcategory_dropdown,
764
+ outputs=kz_input_box
765
+ )
766
+
767
+ # ๋ฒˆ์—ญ ํ•จ์ˆ˜
768
+ kz_translate_button.click(
769
+ fn=translate_ko_zh,
770
+ inputs=kz_input_box,
771
+ outputs=kz_output_box
772
+ )
773
+
774
+ with gr.TabItem("ํ•œ๊ตญ์–ด-์˜์–ด ๋ฒˆ์—ญ"):
775
+ with gr.Column(elem_classes="custom-frame"):
776
+ gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/4297/4297825.png"> ๋ฒˆ์—ญ</div>')
777
+ with gr.Row():
778
+ with gr.Column(scale=1):
779
+ ke_input_box = gr.Textbox(
780
+ label="์ž…๋ ฅ ํ…์ŠคํŠธ (ํ•œ๊ตญ์–ด ๋˜๋Š” ์˜์–ด)",
781
+ placeholder="๋ฒˆ์—ญํ•  ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”...",
782
+ lines=8
783
+ )
784
+ with gr.Column(scale=1):
785
+ ke_output_box = gr.Textbox(
786
+ label="๋ฒˆ์—ญ ๊ฒฐ๊ณผ",
787
+ lines=8,
788
+ interactive=False
789
+ )
790
+
791
+ with gr.Column(elem_classes="custom-frame"):
792
+ gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/1022/1022293.png"> ๋ฒˆ์—ญ ์‹คํ–‰</div>')
793
+ ke_translate_button = gr.Button("๋ฒˆ์—ญํ•˜๊ธฐ", elem_classes="custom-button")
794
+
795
+ # ๋ฒˆ์—ญ ํ•จ์ˆ˜
796
+ ke_translate_button.click(
797
+ fn=translate_ko_en,
798
+ inputs=ke_input_box,
799
+ outputs=ke_output_box
800
+ )
801
+
802
+ return demo
803
+
804
+ if __name__ == "__main__":
805
+ demo = create_interface()
806
+ demo.launch()