ssboost commited on
Commit
cd74a76
ยท
verified ยท
1 Parent(s): 9e318a1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +190 -257
app.py CHANGED
@@ -10,127 +10,115 @@ import uuid
10
  import shutil
11
  import glob
12
  from datetime import datetime
13
- from gradio_client import Client
 
14
  from dotenv import load_dotenv
15
 
16
  # ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋กœ๋“œ
17
  load_dotenv()
18
 
19
- # ๋กœ๊น… ์„ค์ • (API ์—”๋“œํฌ์ธํŠธ ์ •๋ณด๋Š” ์ œ์™ธ)
20
- class SecureFilter(logging.Filter):
21
- """API ์—”๋“œํฌ์ธํŠธ ์ •๋ณด๋ฅผ ๋กœ๊ทธ์—์„œ ์ œ๊ฑฐํ•˜๋Š” ํ•„ํ„ฐ"""
22
- def filter(self, record):
23
- # API ์—”๋“œํฌ์ธํŠธ ๊ด€๋ จ ๋กœ๊ทธ ํ•„ํ„ฐ๋ง
24
- message = record.getMessage()
25
- if any(keyword in message.lower() for keyword in ['hf.space', 'happydoggg', 'gradio_api', 'http request']):
26
- return False
27
- return True
28
-
29
- # ๋ณด์•ˆ ํ•„ํ„ฐ ์ ์šฉ
30
- secure_filter = SecureFilter()
31
-
32
- logging.basicConfig(
33
- level=logging.INFO,
34
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
35
- handlers=[
36
- logging.StreamHandler(),
37
- logging.FileHandler('control_tower_app.log', mode='a')
38
- ]
39
- )
40
-
41
  logger = logging.getLogger(__name__)
42
- logger.addFilter(secure_filter)
43
 
44
- # HTTP ์š”์ฒญ ๋กœ๊ทธ๋„ ์ˆจ๊น€
45
- logging.getLogger("httpx").setLevel(logging.WARNING)
46
- logging.getLogger("gradio_client").setLevel(logging.WARNING)
47
 
48
- # API ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™”
49
  def get_api_client():
50
- """ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ API ์—”๋“œํฌ์ธํŠธ๋ฅผ ๊ฐ€์ ธ์™€ ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ"""
51
  endpoint = os.getenv('API_ENDPOINT')
52
  if not endpoint:
53
- logger.error("API_ENDPOINT ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
54
  raise ValueError("API_ENDPOINT ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.")
55
- return Client(endpoint)
56
-
57
- # ์„ธ์…˜๋ณ„ ์ž„์‹œ ํŒŒ์ผ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ๋”•์…”๋„ˆ๋ฆฌ
58
- session_temp_files = {}
59
- session_data = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
  def cleanup_huggingface_temp_folders():
62
  """ํ—ˆ๊น…ํŽ˜์ด์Šค ์ž„์‹œ ํด๋” ์ดˆ๊ธฐ ์ •๋ฆฌ"""
63
  try:
64
- # ์ผ๋ฐ˜์ ์ธ ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ๋“ค
65
- temp_dirs = [
66
- tempfile.gettempdir(),
67
- "/tmp",
68
- "/var/tmp",
69
- os.path.join(os.getcwd(), "temp"),
70
- os.path.join(os.getcwd(), "tmp"),
71
- "/gradio_cached_examples",
72
- "/flagged"
73
- ]
74
-
75
  cleanup_count = 0
76
 
77
  for temp_dir in temp_dirs:
78
  if os.path.exists(temp_dir):
79
  try:
80
- # ๊ธฐ์กด ์„ธ์…˜ ํŒŒ์ผ๋“ค ์ •๋ฆฌ
81
  session_files = glob.glob(os.path.join(temp_dir, "session_*.xlsx"))
82
  session_files.extend(glob.glob(os.path.join(temp_dir, "session_*.csv")))
83
- session_files.extend(glob.glob(os.path.join(temp_dir, "*keyword*.xlsx")))
84
- session_files.extend(glob.glob(os.path.join(temp_dir, "*keyword*.csv")))
85
- session_files.extend(glob.glob(os.path.join(temp_dir, "tmp*.xlsx")))
86
- session_files.extend(glob.glob(os.path.join(temp_dir, "tmp*.csv")))
87
 
88
  for file_path in session_files:
89
  try:
90
- # ํŒŒ์ผ์ด 1์‹œ๊ฐ„ ์ด์ƒ ์˜ค๋ž˜๋œ ๊ฒฝ์šฐ๋งŒ ์‚ญ์ œ
91
  if os.path.getmtime(file_path) < time.time() - 3600:
92
  os.remove(file_path)
93
  cleanup_count += 1
94
- logger.info(f"์ดˆ๊ธฐ ์ •๋ฆฌ: ์˜ค๋ž˜๋œ ์ž„์‹œ ํŒŒ์ผ ์‚ญ์ œ - {file_path}")
95
- except Exception as e:
96
- logger.warning(f"ํŒŒ์ผ ์‚ญ์ œ ์‹คํŒจ (๋ฌด์‹œ๋จ): {file_path} - {e}")
97
-
98
- except Exception as e:
99
- logger.warning(f"์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์ •๋ฆฌ ์‹คํŒจ (๋ฌด์‹œ๋จ): {temp_dir} - {e}")
100
-
101
- logger.info(f"โœ… ํ—ˆ๊น…ํŽ˜์ด์Šค ์ž„์‹œ ํด๋” ์ดˆ๊ธฐ ์ •๋ฆฌ ์™„๋ฃŒ - {cleanup_count}๊ฐœ ํŒŒ์ผ ์‚ญ์ œ")
102
 
103
- # Gradio ์บ์‹œ ํด๋”๋„ ์ •๋ฆฌ
104
- try:
105
- gradio_temp_dir = os.path.join(os.getcwd(), "gradio_cached_examples")
106
- if os.path.exists(gradio_temp_dir):
107
- shutil.rmtree(gradio_temp_dir, ignore_errors=True)
108
- logger.info("Gradio ์บ์‹œ ํด๋” ์ •๋ฆฌ ์™„๋ฃŒ")
109
- except Exception as e:
110
- logger.warning(f"Gradio ์บ์‹œ ํด๋” ์ •๋ฆฌ ์‹คํŒจ (๋ฌด์‹œ๋จ): {e}")
111
-
112
  except Exception as e:
113
- logger.error(f"์ดˆ๊ธฐ ์ž„์‹œ ํด๋” ์ •๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ (๊ณ„์† ์ง„ํ–‰): {e}")
114
 
115
  def setup_clean_temp_environment():
116
  """๊นจ๋—ํ•œ ์ž„์‹œ ํ™˜๊ฒฝ ์„ค์ •"""
117
  try:
118
- # 1. ๊ธฐ์กด ์ž„์‹œ ํŒŒ์ผ๋“ค ์ •๋ฆฌ
119
  cleanup_huggingface_temp_folders()
120
 
121
- # 2. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์šฉ ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ
122
  app_temp_dir = os.path.join(tempfile.gettempdir(), "control_tower_app")
123
  if os.path.exists(app_temp_dir):
124
  shutil.rmtree(app_temp_dir, ignore_errors=True)
125
  os.makedirs(app_temp_dir, exist_ok=True)
126
 
127
- # 3. ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ • (์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์ง€์ •)
128
  os.environ['CONTROL_TOWER_TEMP'] = app_temp_dir
129
 
130
  logger.info(f"โœ… ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์šฉ ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์„ค์ •: {app_temp_dir}")
131
-
132
  return app_temp_dir
133
-
134
  except Exception as e:
135
  logger.error(f"์ž„์‹œ ํ™˜๊ฒฝ ์„ค์ • ์‹คํŒจ: {e}")
136
  return tempfile.gettempdir()
@@ -140,13 +128,12 @@ def get_app_temp_dir():
140
  return os.environ.get('CONTROL_TOWER_TEMP', tempfile.gettempdir())
141
 
142
  def get_session_id():
143
- """์„ธ์…˜ ID ์ƒ์„ฑ - ์›๋ณธ API์™€ ๋™์ผ"""
144
  try:
145
  client = get_api_client()
146
  result = client.predict(api_name="/get_session_id")
147
- return result
148
- except Exception as e:
149
- logger.error(f"์„ธ์…˜ ID ์ƒ์„ฑ API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
150
  return str(uuid.uuid4())
151
 
152
  def cleanup_session_files(session_id, delay=300):
@@ -179,11 +166,10 @@ def cleanup_old_sessions():
179
  sessions_to_remove = []
180
 
181
  for session_id, data in session_data.items():
182
- if current_time - data.get('last_activity', 0) > 3600: # 1์‹œ๊ฐ„ ์ดˆ๊ณผ
183
  sessions_to_remove.append(session_id)
184
 
185
  for session_id in sessions_to_remove:
186
- # ํŒŒ์ผ ์ •๋ฆฌ
187
  if session_id in session_temp_files:
188
  for file_path in session_temp_files[session_id]:
189
  try:
@@ -194,7 +180,6 @@ def cleanup_old_sessions():
194
  logger.error(f"์˜ค๋ž˜๋œ ์„ธ์…˜ ํŒŒ์ผ ์‚ญ์ œ ์˜ค๋ฅ˜: {e}")
195
  del session_temp_files[session_id]
196
 
197
- # ์„ธ์…˜ ๋ฐ์ดํ„ฐ ์ •๋ฆฌ
198
  if session_id in session_data:
199
  del session_data[session_id]
200
  logger.info(f"์˜ค๋ž˜๋œ ์„ธ์…˜ ๋ฐ์ดํ„ฐ ์‚ญ์ œ: {session_id[:8]}...")
@@ -206,26 +191,22 @@ def update_session_activity(session_id):
206
  session_data[session_id]['last_activity'] = time.time()
207
 
208
  def create_session_temp_file(session_id, suffix='.xlsx'):
209
- """์„ธ์…˜๋ณ„ ์ž„์‹œ ํŒŒ์ผ ์ƒ์„ฑ (์ „์šฉ ๋””๋ ‰ํ† ๋ฆฌ ์‚ฌ์šฉ)"""
210
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
211
  random_suffix = str(random.randint(1000, 9999))
212
 
213
- # ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์šฉ ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์‚ฌ์šฉ
214
  temp_dir = get_app_temp_dir()
215
  filename = f"session_{session_id[:8]}_{timestamp}_{random_suffix}{suffix}"
216
  temp_file_path = os.path.join(temp_dir, filename)
217
 
218
- # ๋นˆ ํŒŒ์ผ ์ƒ์„ฑ
219
  with open(temp_file_path, 'w') as f:
220
  pass
221
 
222
  register_session_file(session_id, temp_file_path)
223
  return temp_file_path
224
 
225
- # ========== ์›๋ณธ API์™€ ์ •ํ™•ํžˆ ๋™์ผํ•œ ํ•จ์ˆ˜๋“ค ==========
226
-
227
  def search_with_loading(keyword, korean_only, apply_main_keyword, exclude_zero_volume):
228
- """์›๋ณธ API: /search_with_loading (4๊ฐœ ๋งค๊ฐœ๋ณ€์ˆ˜, 1๊ฐœ ๋ฐ˜ํ™˜๊ฐ’)"""
229
  try:
230
  client = get_api_client()
231
  result = client.predict(
@@ -235,13 +216,13 @@ def search_with_loading(keyword, korean_only, apply_main_keyword, exclude_zero_v
235
  exclude_zero_volume=exclude_zero_volume,
236
  api_name="/search_with_loading"
237
  )
238
- return result
239
  except Exception as e:
240
  logger.error(f"search_with_loading API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
241
- return gr.update(visible=True)
242
 
243
  def process_search_results(keyword, korean_only, apply_main_keyword, exclude_zero_volume):
244
- """์›๋ณธ API: /process_search_results (4๊ฐœ ๋งค๊ฐœ๋ณ€์ˆ˜, 6๊ฐœ ๋ฐ˜ํ™˜๊ฐ’)"""
245
  try:
246
  client = get_api_client()
247
  result = client.predict(
@@ -252,42 +233,37 @@ def process_search_results(keyword, korean_only, apply_main_keyword, exclude_zer
252
  api_name="/process_search_results"
253
  )
254
 
255
- # ์›๋ณธ API ๋ฌธ์„œ์— ๋”ฐ๋ฅด๋ฉด 6๊ฐœ ๋ฐ˜ํ™˜๊ฐ’์ด์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” 5๊ฐœ์ผ ์ˆ˜ ์žˆ์Œ
256
- # ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌ
257
- if len(result) == 6:
258
- table_html, cat_choices, vol_choices, selected_cat, download_file, progress_html = result
259
- elif len(result) == 5:
260
- table_html, cat_choices, vol_choices, selected_cat, download_file = result
261
- progress_html = ""
 
 
 
 
 
 
 
 
 
262
  else:
263
- logger.error(f"์˜ˆ์ƒ๊ณผ ๋‹ค๋ฅธ ๋ฐ˜ํ™˜๊ฐ’ ๊ฐœ์ˆ˜: {len(result)}")
264
  return (
265
- "<p>์„œ๋น„์Šค ์‘๋‹ต ํ˜•์‹์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.</p>",
266
- ["์ „์ฒด ๋ณด๊ธฐ"], ["์ „์ฒด"], "์ „์ฒด ๋ณด๊ธฐ", None, ""
267
  )
268
 
269
- # ๋‹ค์šด๋กœ๋“œ ํŒŒ์ผ์ด ์žˆ๋Š” ๊ฒฝ์šฐ ๋กœ์ปฌ๋กœ ๋ณต์‚ฌ
270
- local_download_file = None
271
- if download_file:
272
- session_id = get_session_id()
273
- local_download_file = create_session_temp_file(session_id, '.xlsx')
274
- try:
275
- shutil.copy2(download_file, local_download_file)
276
- except Exception as e:
277
- logger.error(f"ํŒŒ์ผ ๋ณต์‚ฌ ์˜ค๋ฅ˜: {e}")
278
- local_download_file = None
279
-
280
- return table_html, cat_choices, vol_choices, selected_cat, local_download_file, progress_html
281
-
282
  except Exception as e:
283
- logger.error(f"API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
284
  return (
285
  "<p>์„œ๋น„์Šค ์—ฐ๊ฒฐ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.</p>",
286
- ["์ „์ฒด ๋ณด๊ธฐ"], ["์ „์ฒด"], "์ „์ฒด ๋ณด๊ธฐ", None, ""
287
  )
288
 
289
  def filter_and_sort_table(selected_cat, keyword_sort, total_volume_sort, usage_count_sort, selected_volume_range, exclude_zero_volume):
290
- """์›๋ณธ API: /filter_and_sort_table (6๊ฐœ ๋งค๊ฐœ๋ณ€์ˆ˜, 1๊ฐœ ๋ฐ˜ํ™˜๊ฐ’)"""
291
  try:
292
  client = get_api_client()
293
  result = client.predict(
@@ -299,98 +275,26 @@ def filter_and_sort_table(selected_cat, keyword_sort, total_volume_sort, usage_c
299
  exclude_zero_volume=exclude_zero_volume,
300
  api_name="/filter_and_sort_table"
301
  )
302
- return result
303
  except Exception as e:
304
  logger.error(f"filter_and_sort_table API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
305
  return "<p>ํ•„ํ„ฐ๋ง ์„œ๋น„์Šค ์—ฐ๊ฒฐ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.</p>"
306
 
307
- def filter_and_sort_table_1(selected_cat, keyword_sort, total_volume_sort, usage_count_sort, selected_volume_range, exclude_zero_volume):
308
- """์›๋ณธ API: /filter_and_sort_table_1"""
309
- try:
310
- client = get_api_client()
311
- result = client.predict(
312
- selected_cat=selected_cat,
313
- keyword_sort=keyword_sort,
314
- total_volume_sort=total_volume_sort,
315
- usage_count_sort=usage_count_sort,
316
- selected_volume_range=selected_volume_range,
317
- exclude_zero_volume=exclude_zero_volume,
318
- api_name="/filter_and_sort_table_1"
319
- )
320
- return result
321
- except Exception as e:
322
- logger.error(f"filter_and_sort_table_1 API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
323
- return "<p>ํ•„ํ„ฐ๋ง ์„œ๋น„์Šค ์—ฐ๊ฒฐ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.</p>"
324
-
325
- def filter_and_sort_table_2(selected_cat, keyword_sort, total_volume_sort, usage_count_sort, selected_volume_range, exclude_zero_volume):
326
- """์›๋ณธ API: /filter_and_sort_table_2"""
327
- try:
328
- client = get_api_client()
329
- result = client.predict(
330
- selected_cat=selected_cat,
331
- keyword_sort=keyword_sort,
332
- total_volume_sort=total_volume_sort,
333
- usage_count_sort=usage_count_sort,
334
- selected_volume_range=selected_volume_range,
335
- exclude_zero_volume=exclude_zero_volume,
336
- api_name="/filter_and_sort_table_2"
337
- )
338
- return result
339
- except Exception as e:
340
- logger.error(f"filter_and_sort_table_2 API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
341
- return "<p>ํ•„ํ„ฐ๋ง ์„œ๋น„์Šค ์—ฐ๊ฒฐ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.</p>"
342
-
343
- def filter_and_sort_table_3(selected_cat, keyword_sort, total_volume_sort, usage_count_sort, selected_volume_range, exclude_zero_volume):
344
- """์›๋ณธ API: /filter_and_sort_table_3"""
345
- try:
346
- client = get_api_client()
347
- result = client.predict(
348
- selected_cat=selected_cat,
349
- keyword_sort=keyword_sort,
350
- total_volume_sort=total_volume_sort,
351
- usage_count_sort=usage_count_sort,
352
- selected_volume_range=selected_volume_range,
353
- exclude_zero_volume=exclude_zero_volume,
354
- api_name="/filter_and_sort_table_3"
355
- )
356
- return result
357
- except Exception as e:
358
- logger.error(f"filter_and_sort_table_3 API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
359
- return "<p>ํ•„ํ„ฐ๋ง ์„œ๋น„์Šค ์—ฐ๊ฒฐ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.</p>"
360
-
361
- def filter_and_sort_table_4(selected_cat, keyword_sort, total_volume_sort, usage_count_sort, selected_volume_range, exclude_zero_volume):
362
- """์›๋ณธ API: /filter_and_sort_table_4"""
363
- try:
364
- client = get_api_client()
365
- result = client.predict(
366
- selected_cat=selected_cat,
367
- keyword_sort=keyword_sort,
368
- total_volume_sort=total_volume_sort,
369
- usage_count_sort=usage_count_sort,
370
- selected_volume_range=selected_volume_range,
371
- exclude_zero_volume=exclude_zero_volume,
372
- api_name="/filter_and_sort_table_4"
373
- )
374
- return result
375
- except Exception as e:
376
- logger.error(f"filter_and_sort_table_4 API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
377
- return "<p>ํ•„ํ„ฐ๋ง ์„œ๋น„์Šค ์—ฐ๊ฒฐ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.</p>"
378
-
379
  def update_category_selection(selected_cat):
380
- """์›๋ณธ API: /update_category_selection (1๊ฐœ ๋งค๊ฐœ๋ณ€์ˆ˜, 1๊ฐœ ๋ฐ˜ํ™˜๊ฐ’)"""
381
  try:
382
  client = get_api_client()
383
  result = client.predict(
384
  selected_cat=selected_cat,
385
  api_name="/update_category_selection"
386
  )
387
- return gr.update(value=result)
388
  except Exception as e:
389
  logger.error(f"update_category_selection API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
390
  return gr.update(value=selected_cat)
391
 
392
  def analyze_with_loading(analysis_keywords, selected_category):
393
- """์›๋ณธ API: /analyze_with_loading (2๊ฐœ ๋งค๊ฐœ๋ณ€์ˆ˜, 1๊ฐœ ๋ฐ˜ํ™˜๊ฐ’)"""
394
  try:
395
  client = get_api_client()
396
  result = client.predict(
@@ -398,13 +302,13 @@ def analyze_with_loading(analysis_keywords, selected_category):
398
  selected_category=selected_category,
399
  api_name="/analyze_with_loading"
400
  )
401
- return result
402
  except Exception as e:
403
  logger.error(f"analyze_with_loading API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
404
- return gr.update(visible=True)
405
 
406
  def process_analyze_results(analysis_keywords, selected_category):
407
- """์›๋ณธ API: /process_analyze_results (2๊ฐœ ๋งค๊ฐœ๋ณ€์ˆ˜, 2๊ฐœ ๋ฐ˜ํ™˜๏ฟฝ๏ฟฝ)"""
408
  try:
409
  client = get_api_client()
410
  result = client.predict(
@@ -413,58 +317,50 @@ def process_analyze_results(analysis_keywords, selected_category):
413
  api_name="/process_analyze_results"
414
  )
415
 
416
- analysis_result, download_file = result
417
-
418
- # ๋‹ค์šด๋กœ๋“œ ํŒŒ์ผ์ด ์žˆ๋Š” ๊ฒฝ์šฐ ๋กœ์ปฌ๋กœ ๋ณต์‚ฌ
419
- local_download_file = None
420
- if download_file:
421
- session_id = get_session_id()
422
- local_download_file = create_session_temp_file(session_id, '.xlsx')
423
- try:
424
- shutil.copy2(download_file, local_download_file)
425
- except Exception as e:
426
- logger.error(f"๋ถ„์„ ๊ฒฐ๊ณผ ํŒŒ์ผ ๋ณต์‚ฌ ์˜ค๋ฅ˜: {e}")
427
- local_download_file = None
428
-
429
- return analysis_result, local_download_file
 
 
 
430
 
431
  except Exception as e:
432
  logger.error(f"process_analyze_results API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
433
  return "๋ถ„์„ ์„œ๋น„์Šค ์—ฐ๊ฒฐ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.", None
434
 
435
  def reset_interface():
436
- """์›๋ณธ API: /reset_interface (0๊ฐœ ๋งค๊ฐœ๋ณ€์ˆ˜, 16๊ฐœ ๋ฐ˜ํ™˜๊ฐ’)"""
437
  try:
438
  client = get_api_client()
439
  result = client.predict(api_name="/reset_interface")
440
- return result
441
  except Exception as e:
442
  logger.error(f"reset_interface API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
443
- # ๊ธฐ๋ณธ ๋ฆฌ์…‹ ๊ฐ’ ๋ฐ˜ํ™˜ (16๊ฐœ)
444
- return (
445
- "", # ๊ฒ€์ƒ‰ ํ‚ค์›Œ๋“œ
446
- True, # ํ•œ๊ธ€๋งŒ ์ถ”์ถœ
447
- False, # ๊ฒ€์ƒ‰๋Ÿ‰ 0 ํ‚ค์›Œ๋“œ ์ œ์™ธ
448
- "๋ฉ”์ธํ‚ค์›Œ๋“œ ์ ์šฉ", # ์กฐํ•ฉ ๋ฐฉ์‹
449
- "", # HTML ํ…Œ์ด๋ธ”
450
- ["์ „์ฒด ๋ณด๊ธฐ"], # ์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ choices
451
- "์ „์ฒด ๋ณด๊ธฐ", # ์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ value
452
- ["์ „์ฒด"], # ๊ฒ€์ƒ‰๋Ÿ‰ ๊ตฌ๊ฐ„ ํ•„ํ„ฐ choices
453
- "์ „์ฒด", # ๊ฒ€์ƒ‰๋Ÿ‰ ๊ตฌ๊ฐ„ ํ•„ํ„ฐ value
454
- "์ •๋ ฌ ์—†์Œ", # ์ด๊ฒ€์ƒ‰๋Ÿ‰ ์ •๋ ฌ
455
- "์ •๋ ฌ ์—†์Œ", # ํ‚ค์›Œ๋“œ ์‚ฌ์šฉํšŸ์ˆ˜ ์ •๋ ฌ
456
- ["์ „์ฒด ๋ณด๊ธฐ"], # ๋ถ„์„ํ•  ์นดํ…Œ๊ณ ๋ฆฌ choices
457
- "์ „์ฒด ๋ณด๊ธฐ", # ๋ถ„์„ํ•  ์นดํ…Œ๊ณ ๋ฆฌ value
458
- "", # ํ‚ค์›Œ๋“œ ์ž…๋ ฅ
459
- "", # ๋ถ„์„ ๊ฒฐ๊ณผ
460
- None # ๋‹ค์šด๋กœ๋“œ ํŒŒ์ผ
461
- )
462
 
463
- # ========== UI ์ฒ˜๋ฆฌ ๋ž˜ํผ ํ•จ์ˆ˜๋“ค ==========
 
 
 
 
 
 
464
 
 
465
  def wrapper_search_with_loading(keyword, korean_only, apply_main_keyword, exclude_zero_volume):
466
  """๊ฒ€์ƒ‰ ๋กœ๋”ฉ UI ์ฒ˜๋ฆฌ"""
467
- result = search_with_loading(keyword, korean_only, apply_main_keyword, exclude_zero_volume)
468
  return (
469
  gr.update(visible=True), # progress_section
470
  gr.update(visible=False) # empty_table_html
@@ -474,18 +370,7 @@ def wrapper_process_search_results(keyword, korean_only, apply_main_keyword, exc
474
  """๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ UI"""
475
  result = process_search_results(keyword, korean_only, apply_main_keyword, exclude_zero_volume)
476
 
477
- # ์•ˆ์ „ํ•˜๊ฒŒ ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ
478
- if len(result) >= 5:
479
- table_html, cat_choices, vol_choices, selected_cat, download_file = result[:5]
480
- progress_html = result[5] if len(result) > 5 else ""
481
- else:
482
- logger.error(f"๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: ์˜ˆ์ƒ๋ณด๋‹ค ์ ์€ ๋ฐ˜ํ™˜๊ฐ’ {len(result)}")
483
- table_html = "<p>๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.</p>"
484
- cat_choices = ["์ „์ฒด ๋ณด๊ธฐ"]
485
- vol_choices = ["์ „์ฒด"]
486
- selected_cat = "์ „์ฒด ๋ณด๊ธฐ"
487
- download_file = None
488
- progress_html = ""
489
 
490
  # UI ํ‘œ์‹œ ์—ฌ๋ถ€ ๊ฒฐ์ •
491
  if table_html and "๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค" not in table_html and "๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๏ฟฝ๏ฟฝ๏ฟฝ๋‹ˆ๋‹ค" not in table_html:
@@ -499,7 +384,7 @@ def wrapper_process_search_results(keyword, korean_only, apply_main_keyword, exc
499
  empty_placeholder_vis = True
500
  execution_section_visibility = False
501
 
502
- # ๊ฐ€์ƒ์˜ state_df (์›๋ณธ๊ณผ ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•ด)
503
  state_df = pd.DataFrame()
504
 
505
  return (
@@ -519,7 +404,7 @@ def wrapper_process_search_results(keyword, korean_only, apply_main_keyword, exc
519
 
520
  def wrapper_analyze_with_loading(analysis_keywords, selected_category, state_df):
521
  """๋ถ„์„ ๋กœ๋”ฉ UI ์ฒ˜๋ฆฌ"""
522
- result = analyze_with_loading(analysis_keywords, selected_category)
523
  return gr.update(visible=True) # progress_section
524
 
525
  def wrapper_process_analyze_results(analysis_keywords, selected_category, state_df):
@@ -547,19 +432,14 @@ def cleanup_on_startup():
547
  """์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์‹œ ์ „์ฒด ์ •๋ฆฌ"""
548
  logger.info("๐Ÿงน ์ปจํŠธ๋กค ํƒ€์›Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ - ์ดˆ๊ธฐ ์ •๋ฆฌ ์ž‘์—… ์‹œ์ž‘...")
549
 
550
- # 1. ํ—ˆ๊น…ํŽ˜์ด์Šค ์ž„์‹œ ํด๋” ์ •๋ฆฌ
551
  cleanup_huggingface_temp_folders()
552
-
553
- # 2. ๊นจ๋—ํ•œ ์ž„์‹œ ํ™˜๊ฒฝ ์„ค์ •
554
  app_temp_dir = setup_clean_temp_environment()
555
 
556
- # 3. ์ „์—ญ ๋ณ€์ˆ˜ ์ดˆ๊ธฐํ™”
557
  global session_temp_files, session_data
558
  session_temp_files.clear()
559
  session_data.clear()
560
 
561
  logger.info(f"โœ… ์ดˆ๊ธฐ ์ •๋ฆฌ ์ž‘์—… ์™„๋ฃŒ - ์•ฑ ์ „์šฉ ๋””๋ ‰ํ† ๋ฆฌ: {app_temp_dir}")
562
-
563
  return app_temp_dir
564
 
565
  # Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ
@@ -588,10 +468,6 @@ def create_app():
588
  font-size: 16px !important;
589
  font-weight: bold !important;
590
  width: 100% !important;
591
- text-align: center !important;
592
- display: flex !important;
593
- align-items: center !important;
594
- justify-content: center !important;
595
  }
596
  """
597
 
@@ -873,4 +749,61 @@ def create_app():
873
  ]
874
  )
875
 
876
- return demo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  import shutil
11
  import glob
12
  from datetime import datetime
13
+ import requests
14
+ import json
15
  from dotenv import load_dotenv
16
 
17
  # ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋กœ๋“œ
18
  load_dotenv()
19
 
20
+ # ๋กœ๊น… ์„ค์ • (API ์ •๋ณด ์™„์ „ ์ฐจ๋‹จ)
21
+ logging.basicConfig(level=logging.WARNING)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  logger = logging.getLogger(__name__)
 
23
 
24
+ # ์„ธ์…˜๋ณ„ ์ž„์‹œ ํŒŒ์ผ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ๋”•์…”๋„ˆ๋ฆฌ
25
+ session_temp_files = {}
26
+ session_data = {}
27
 
 
28
  def get_api_client():
29
+ """ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ API ์—”๋“œํฌ์ธํŠธ๋ฅผ ๊ฐ€์ ธ์™€ ์š”์ฒญ ํ•จ์ˆ˜ ์ƒ์„ฑ"""
30
  endpoint = os.getenv('API_ENDPOINT')
31
  if not endpoint:
 
32
  raise ValueError("API_ENDPOINT ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.")
33
+
34
+ def make_request(api_name, **kwargs):
35
+ try:
36
+ # gradio_client์™€ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ API ํ˜ธ์ถœ
37
+ if not endpoint.startswith('http'):
38
+ base_url = f"https://{endpoint}.hf.space"
39
+ else:
40
+ base_url = endpoint
41
+
42
+ # Gradio API ์—”๋“œํฌ์ธํŠธ ํ˜•์‹ ๋งž์ถ”๊ธฐ
43
+ url = f"{base_url}/call{api_name}"
44
+
45
+ # ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ˆœ์„œ๋Œ€๋กœ ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜
46
+ if api_name == "/process_search_results":
47
+ data = [kwargs.get('keyword', ''), kwargs.get('korean_only', True),
48
+ kwargs.get('apply_main_keyword', '๋ฉ”์ธํ‚ค์›Œ๋“œ ์ ์šฉ'), kwargs.get('exclude_zero_volume', False)]
49
+ elif api_name == "/search_with_loading":
50
+ data = [kwargs.get('keyword', ''), kwargs.get('korean_only', True),
51
+ kwargs.get('apply_main_keyword', '๋ฉ”์ธํ‚ค์›Œ๋“œ ์ ์šฉ'), kwargs.get('exclude_zero_volume', False)]
52
+ elif api_name == "/filter_and_sort_table":
53
+ data = [kwargs.get('selected_cat', '์ „์ฒด ๋ณด๊ธฐ'), kwargs.get('keyword_sort', '์ •๋ ฌ ์—†์Œ'),
54
+ kwargs.get('total_volume_sort', '์ •๋ ฌ ์—†์Œ'), kwargs.get('usage_count_sort', '์ •๋ ฌ ์—†์Œ'),
55
+ kwargs.get('selected_volume_range', '์ „์ฒด'), kwargs.get('exclude_zero_volume', False)]
56
+ elif api_name == "/update_category_selection":
57
+ data = [kwargs.get('selected_cat', '์ „์ฒด ๋ณด๊ธฐ')]
58
+ elif api_name == "/process_analyze_results":
59
+ data = [kwargs.get('analysis_keywords', ''), kwargs.get('selected_category', '์ „์ฒด ๋ณด๊ธฐ')]
60
+ elif api_name == "/analyze_with_loading":
61
+ data = [kwargs.get('analysis_keywords', ''), kwargs.get('selected_category', '์ „์ฒด ๋ณด๊ธฐ')]
62
+ elif api_name == "/reset_interface":
63
+ data = []
64
+ elif api_name == "/get_session_id":
65
+ data = []
66
+ else:
67
+ data = []
68
+
69
+ response = requests.post(url, json={"data": data}, timeout=60)
70
+
71
+ if response.status_code == 200:
72
+ result = response.json()
73
+ return result.get('data', [])
74
+ else:
75
+ raise Exception(f"API ํ˜ธ์ถœ ์‹คํŒจ: {response.status_code}")
76
+
77
+ except Exception as e:
78
+ raise Exception(f"API ์—ฐ๊ฒฐ ์˜ค๋ฅ˜: {str(e)}")
79
+
80
+ return type('APIClient', (), {'predict': lambda self, **kwargs: make_request(kwargs.pop('api_name'), **kwargs)})()
81
 
82
  def cleanup_huggingface_temp_folders():
83
  """ํ—ˆ๊น…ํŽ˜์ด์Šค ์ž„์‹œ ํด๋” ์ดˆ๊ธฐ ์ •๋ฆฌ"""
84
  try:
85
+ temp_dirs = [tempfile.gettempdir(), "/tmp", "/var/tmp"]
 
 
 
 
 
 
 
 
 
 
86
  cleanup_count = 0
87
 
88
  for temp_dir in temp_dirs:
89
  if os.path.exists(temp_dir):
90
  try:
 
91
  session_files = glob.glob(os.path.join(temp_dir, "session_*.xlsx"))
92
  session_files.extend(glob.glob(os.path.join(temp_dir, "session_*.csv")))
 
 
 
 
93
 
94
  for file_path in session_files:
95
  try:
 
96
  if os.path.getmtime(file_path) < time.time() - 3600:
97
  os.remove(file_path)
98
  cleanup_count += 1
99
+ except Exception:
100
+ pass
101
+ except Exception:
102
+ pass
 
 
 
 
103
 
104
+ logger.info(f"โœ… ์ž„์‹œ ํด๋” ์ •๋ฆฌ ์™„๋ฃŒ - {cleanup_count}๊ฐœ ํŒŒ์ผ ์‚ญ์ œ")
 
 
 
 
 
 
 
 
105
  except Exception as e:
106
+ logger.error(f"์ž„์‹œ ํด๋” ์ •๋ฆฌ ์ค‘ ์˜ค๋ฅ˜: {e}")
107
 
108
  def setup_clean_temp_environment():
109
  """๊นจ๋—ํ•œ ์ž„์‹œ ํ™˜๊ฒฝ ์„ค์ •"""
110
  try:
 
111
  cleanup_huggingface_temp_folders()
112
 
 
113
  app_temp_dir = os.path.join(tempfile.gettempdir(), "control_tower_app")
114
  if os.path.exists(app_temp_dir):
115
  shutil.rmtree(app_temp_dir, ignore_errors=True)
116
  os.makedirs(app_temp_dir, exist_ok=True)
117
 
 
118
  os.environ['CONTROL_TOWER_TEMP'] = app_temp_dir
119
 
120
  logger.info(f"โœ… ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์šฉ ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์„ค์ •: {app_temp_dir}")
 
121
  return app_temp_dir
 
122
  except Exception as e:
123
  logger.error(f"์ž„์‹œ ํ™˜๊ฒฝ ์„ค์ • ์‹คํŒจ: {e}")
124
  return tempfile.gettempdir()
 
128
  return os.environ.get('CONTROL_TOWER_TEMP', tempfile.gettempdir())
129
 
130
  def get_session_id():
131
+ """์„ธ์…˜ ID ์ƒ์„ฑ"""
132
  try:
133
  client = get_api_client()
134
  result = client.predict(api_name="/get_session_id")
135
+ return result[0] if result else str(uuid.uuid4())
136
+ except Exception:
 
137
  return str(uuid.uuid4())
138
 
139
  def cleanup_session_files(session_id, delay=300):
 
166
  sessions_to_remove = []
167
 
168
  for session_id, data in session_data.items():
169
+ if current_time - data.get('last_activity', 0) > 3600:
170
  sessions_to_remove.append(session_id)
171
 
172
  for session_id in sessions_to_remove:
 
173
  if session_id in session_temp_files:
174
  for file_path in session_temp_files[session_id]:
175
  try:
 
180
  logger.error(f"์˜ค๋ž˜๋œ ์„ธ์…˜ ํŒŒ์ผ ์‚ญ์ œ ์˜ค๋ฅ˜: {e}")
181
  del session_temp_files[session_id]
182
 
 
183
  if session_id in session_data:
184
  del session_data[session_id]
185
  logger.info(f"์˜ค๋ž˜๋œ ์„ธ์…˜ ๋ฐ์ดํ„ฐ ์‚ญ์ œ: {session_id[:8]}...")
 
191
  session_data[session_id]['last_activity'] = time.time()
192
 
193
  def create_session_temp_file(session_id, suffix='.xlsx'):
194
+ """์„ธ์…˜๋ณ„ ์ž„์‹œ ํŒŒ์ผ ์ƒ์„ฑ"""
195
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
196
  random_suffix = str(random.randint(1000, 9999))
197
 
 
198
  temp_dir = get_app_temp_dir()
199
  filename = f"session_{session_id[:8]}_{timestamp}_{random_suffix}{suffix}"
200
  temp_file_path = os.path.join(temp_dir, filename)
201
 
 
202
  with open(temp_file_path, 'w') as f:
203
  pass
204
 
205
  register_session_file(session_id, temp_file_path)
206
  return temp_file_path
207
 
 
 
208
  def search_with_loading(keyword, korean_only, apply_main_keyword, exclude_zero_volume):
209
+ """์›๋ณธ API: /search_with_loading"""
210
  try:
211
  client = get_api_client()
212
  result = client.predict(
 
216
  exclude_zero_volume=exclude_zero_volume,
217
  api_name="/search_with_loading"
218
  )
219
+ return result[0] if result else ""
220
  except Exception as e:
221
  logger.error(f"search_with_loading API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
222
+ return ""
223
 
224
  def process_search_results(keyword, korean_only, apply_main_keyword, exclude_zero_volume):
225
+ """์›๋ณธ API: /process_search_results"""
226
  try:
227
  client = get_api_client()
228
  result = client.predict(
 
233
  api_name="/process_search_results"
234
  )
235
 
236
+ # ๊ฒฐ๊ณผ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌ
237
+ if len(result) >= 5:
238
+ table_html, cat_choices, vol_choices, selected_cat, download_file = result[:5]
239
+
240
+ # ๋‹ค์šด๋กœ๋“œ ํŒŒ์ผ์ด ์žˆ๋Š” ๊ฒฝ์šฐ ๋กœ์ปฌ๋กœ ๋ณต์‚ฌ
241
+ local_download_file = None
242
+ if download_file:
243
+ session_id = get_session_id()
244
+ local_download_file = create_session_temp_file(session_id, '.xlsx')
245
+ try:
246
+ shutil.copy2(download_file, local_download_file)
247
+ except Exception as e:
248
+ logger.error(f"ํŒŒ์ผ ๋ณต์‚ฌ ์˜ค๋ฅ˜: {e}")
249
+ local_download_file = None
250
+
251
+ return table_html, cat_choices, vol_choices, selected_cat, local_download_file
252
  else:
 
253
  return (
254
+ "<p>๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.</p>",
255
+ ["์ „์ฒด ๋ณด๊ธฐ"], ["์ „์ฒด"], "์ „์ฒด ๋ณด๊ธฐ", None
256
  )
257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  except Exception as e:
259
+ logger.error(f"process_search_results API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
260
  return (
261
  "<p>์„œ๋น„์Šค ์—ฐ๊ฒฐ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.</p>",
262
+ ["์ „์ฒด ๋ณด๊ธฐ"], ["์ „์ฒด"], "์ „์ฒด ๋ณด๊ธฐ", None
263
  )
264
 
265
  def filter_and_sort_table(selected_cat, keyword_sort, total_volume_sort, usage_count_sort, selected_volume_range, exclude_zero_volume):
266
+ """์›๋ณธ API: /filter_and_sort_table"""
267
  try:
268
  client = get_api_client()
269
  result = client.predict(
 
275
  exclude_zero_volume=exclude_zero_volume,
276
  api_name="/filter_and_sort_table"
277
  )
278
+ return result[0] if result else ""
279
  except Exception as e:
280
  logger.error(f"filter_and_sort_table API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
281
  return "<p>ํ•„ํ„ฐ๋ง ์„œ๋น„์Šค ์—ฐ๊ฒฐ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.</p>"
282
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
  def update_category_selection(selected_cat):
284
+ """์›๋ณธ API: /update_category_selection"""
285
  try:
286
  client = get_api_client()
287
  result = client.predict(
288
  selected_cat=selected_cat,
289
  api_name="/update_category_selection"
290
  )
291
+ return gr.update(value=result[0] if result else selected_cat)
292
  except Exception as e:
293
  logger.error(f"update_category_selection API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
294
  return gr.update(value=selected_cat)
295
 
296
  def analyze_with_loading(analysis_keywords, selected_category):
297
+ """์›๋ณธ API: /analyze_with_loading"""
298
  try:
299
  client = get_api_client()
300
  result = client.predict(
 
302
  selected_category=selected_category,
303
  api_name="/analyze_with_loading"
304
  )
305
+ return result[0] if result else ""
306
  except Exception as e:
307
  logger.error(f"analyze_with_loading API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
308
+ return ""
309
 
310
  def process_analyze_results(analysis_keywords, selected_category):
311
+ """์›๋ณธ API: /process_analyze_results"""
312
  try:
313
  client = get_api_client()
314
  result = client.predict(
 
317
  api_name="/process_analyze_results"
318
  )
319
 
320
+ if len(result) >= 2:
321
+ analysis_result, download_file = result[:2]
322
+
323
+ # ๋‹ค์šด๋กœ๋“œ ํŒŒ์ผ์ด ์žˆ๋Š” ๊ฒฝ์šฐ ๋กœ์ปฌ๋กœ ๋ณต์‚ฌ
324
+ local_download_file = None
325
+ if download_file:
326
+ session_id = get_session_id()
327
+ local_download_file = create_session_temp_file(session_id, '.xlsx')
328
+ try:
329
+ shutil.copy2(download_file, local_download_file)
330
+ except Exception as e:
331
+ logger.error(f"๋ถ„์„ ๊ฒฐ๊ณผ ํŒŒ์ผ ๋ณต์‚ฌ ์˜ค๋ฅ˜: {e}")
332
+ local_download_file = None
333
+
334
+ return analysis_result, local_download_file
335
+ else:
336
+ return "๋ถ„์„ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.", None
337
 
338
  except Exception as e:
339
  logger.error(f"process_analyze_results API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
340
  return "๋ถ„์„ ์„œ๋น„์Šค ์—ฐ๊ฒฐ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.", None
341
 
342
  def reset_interface():
343
+ """์›๋ณธ API: /reset_interface"""
344
  try:
345
  client = get_api_client()
346
  result = client.predict(api_name="/reset_interface")
347
+ return result if result else get_default_reset_values()
348
  except Exception as e:
349
  logger.error(f"reset_interface API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
350
+ return get_default_reset_values()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
 
352
+ def get_default_reset_values():
353
+ """๊ธฐ๋ณธ ๋ฆฌ์…‹ ๊ฐ’ ๋ฐ˜ํ™˜"""
354
+ return (
355
+ "", True, False, "๋ฉ”์ธํ‚ค์›Œ๋“œ ์ ์šฉ", "", ["์ „์ฒด ๋ณด๊ธฐ"], "์ „์ฒด ๋ณด๊ธฐ",
356
+ ["์ „์ฒด"], "์ „์ฒด", "์ •๋ ฌ ์—†์Œ", "์ •๋ ฌ ์—†์Œ", ["์ „์ฒด ๋ณด๊ธฐ"], "์ „์ฒด ๋ณด๊ธฐ",
357
+ "", "", None
358
+ )
359
 
360
+ # UI ์ฒ˜๋ฆฌ ๋ž˜ํผ ํ•จ์ˆ˜๋“ค
361
  def wrapper_search_with_loading(keyword, korean_only, apply_main_keyword, exclude_zero_volume):
362
  """๊ฒ€์ƒ‰ ๋กœ๋”ฉ UI ์ฒ˜๋ฆฌ"""
363
+ search_with_loading(keyword, korean_only, apply_main_keyword, exclude_zero_volume)
364
  return (
365
  gr.update(visible=True), # progress_section
366
  gr.update(visible=False) # empty_table_html
 
370
  """๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ UI"""
371
  result = process_search_results(keyword, korean_only, apply_main_keyword, exclude_zero_volume)
372
 
373
+ table_html, cat_choices, vol_choices, selected_cat, download_file = result
 
 
 
 
 
 
 
 
 
 
 
374
 
375
  # UI ํ‘œ์‹œ ์—ฌ๋ถ€ ๊ฒฐ์ •
376
  if table_html and "๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค" not in table_html and "๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๏ฟฝ๏ฟฝ๏ฟฝ๋‹ˆ๋‹ค" not in table_html:
 
384
  empty_placeholder_vis = True
385
  execution_section_visibility = False
386
 
387
+ # ๊ฐ€์ƒ์˜ state_df
388
  state_df = pd.DataFrame()
389
 
390
  return (
 
404
 
405
  def wrapper_analyze_with_loading(analysis_keywords, selected_category, state_df):
406
  """๋ถ„์„ ๋กœ๋”ฉ UI ์ฒ˜๋ฆฌ"""
407
+ analyze_with_loading(analysis_keywords, selected_category)
408
  return gr.update(visible=True) # progress_section
409
 
410
  def wrapper_process_analyze_results(analysis_keywords, selected_category, state_df):
 
432
  """์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์‹œ ์ „์ฒด ์ •๋ฆฌ"""
433
  logger.info("๐Ÿงน ์ปจํŠธ๋กค ํƒ€์›Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ - ์ดˆ๊ธฐ ์ •๋ฆฌ ์ž‘์—… ์‹œ์ž‘...")
434
 
 
435
  cleanup_huggingface_temp_folders()
 
 
436
  app_temp_dir = setup_clean_temp_environment()
437
 
 
438
  global session_temp_files, session_data
439
  session_temp_files.clear()
440
  session_data.clear()
441
 
442
  logger.info(f"โœ… ์ดˆ๊ธฐ ์ •๋ฆฌ ์ž‘์—… ์™„๋ฃŒ - ์•ฑ ์ „์šฉ ๋””๋ ‰ํ† ๋ฆฌ: {app_temp_dir}")
 
443
  return app_temp_dir
444
 
445
  # Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ
 
468
  font-size: 16px !important;
469
  font-weight: bold !important;
470
  width: 100% !important;
 
 
 
 
471
  }
472
  """
473
 
 
749
  ]
750
  )
751
 
752
+ return demo
753
+
754
+ if __name__ == "__main__":
755
+ # ========== ์‹œ์ž‘ ์‹œ ์ „์ฒด ์ดˆ๊ธฐํ™” ==========
756
+ print("===== Application Startup at %s =====" % time.strftime("%Y-%m-%d %H:%M:%S"))
757
+ logger.info("๐Ÿš€ ์ปจํŠธ๋กค ํƒ€์›Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘...")
758
+
759
+ # 1. ์ฒซ ๋ฒˆ์งธ: ํ—ˆ๊น…ํŽ˜์ด์Šค ์ž„์‹œ ํด๋” ์ •๋ฆฌ ๋ฐ ํ™˜๊ฒฝ ์„ค์ •
760
+ app_temp_dir = cleanup_on_startup()
761
+
762
+ # 2. ์„ธ์…˜ ์ •๋ฆฌ ์Šค์ผ€์ค„๋Ÿฌ ์‹œ์ž‘
763
+ start_session_cleanup_scheduler()
764
+
765
+ # 3. API ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ
766
+ try:
767
+ test_client = get_api_client()
768
+ logger.info("โœ… API ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ ์„ฑ๊ณต")
769
+ except Exception as e:
770
+ logger.error("โŒ API ์—ฐ๊ฒฐ ์‹คํŒจ - ํ™˜๊ฒฝ๋ณ€์ˆ˜ API_ENDPOINT๋ฅผ ํ™•์ธํ•˜์„ธ์š”")
771
+ print("โŒ API_ENDPOINT ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
772
+ print("๐Ÿ’ก .env ํŒŒ์ผ์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •ํ•˜์„ธ์š”:")
773
+ print("API_ENDPOINT=your-endpoint-here")
774
+ raise SystemExit(1)
775
+
776
+ logger.info("===== ์ปจํŠธ๋กค ํƒ€์›Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์™„๋ฃŒ at %s =====", time.strftime("%Y-%m-%d %H:%M:%S"))
777
+ logger.info(f"๐Ÿ“ ์ž„์‹œ ํŒŒ์ผ ์ €์žฅ ์œ„์น˜: {app_temp_dir}")
778
+
779
+ # ========== ์•ฑ ์‹คํ–‰ ==========
780
+ try:
781
+ app = create_app()
782
+ print("๐Ÿš€ Gradio ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค...")
783
+ app.launch(
784
+ share=False, # ๋ณด์•ˆ์„ ์œ„ํ•ด share ๋น„ํ™œ์„ฑํ™”
785
+ server_name="0.0.0.0", # ๋ชจ๋“  IP์—์„œ ์ ‘๊ทผ ํ—ˆ์šฉ
786
+ server_port=7860, # ํฌํŠธ ์ง€์ •
787
+ max_threads=40, # ๋ฉ€ํ‹ฐ์œ ์ €๋ฅผ ์œ„ํ•œ ์Šค๋ ˆ๋“œ ์ˆ˜ ์ฆ๊ฐ€
788
+ auth=None, # ํ•„์š”์‹œ ์ธ์ฆ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ
789
+ show_error=True, # ์—๋Ÿฌ ํ‘œ์‹œ
790
+ quiet=False, # ๋กœ๊ทธ ํ‘œ์‹œ
791
+ favicon_path=None, # ํŒŒ๋น„์ฝ˜ ์„ค์ •
792
+ ssl_verify=False, # SSL ๊ฒ€์ฆ ๋น„ํ™œ์„ฑํ™” (๊ฐœ๋ฐœ์šฉ)
793
+ inbrowser=False, # ์ž๋™ ๋ธŒ๋ผ์šฐ์ € ์—ด๊ธฐ ๋น„ํ™œ์„ฑํ™”
794
+ prevent_thread_lock=False # ์Šค๋ ˆ๋“œ ์ž ๊ธˆ ๋ฐฉ์ง€ ๋น„ํ™œ์„ฑํ™”
795
+ )
796
+ except Exception as e:
797
+ logger.error(f"์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ์‹คํŒจ: {e}")
798
+ print(f"โŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ์‹คํŒจ: {e}")
799
+ raise SystemExit(1)
800
+ finally:
801
+ # ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ข…๋ฃŒ ์‹œ ์ •๋ฆฌ
802
+ logger.info("๐Ÿงน ์ปจํŠธ๋กค ํƒ€์›Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ข…๋ฃŒ - ์ตœ์ข… ์ •๋ฆฌ ์ž‘์—…...")
803
+ try:
804
+ cleanup_huggingface_temp_folders()
805
+ if os.path.exists(app_temp_dir):
806
+ shutil.rmtree(app_temp_dir, ignore_errors=True)
807
+ logger.info("โœ… ์ตœ์ข… ์ •๋ฆฌ ์™„๋ฃŒ")
808
+ except Exception as e:
809
+ logger.error(f"์ตœ์ข… ์ •๋ฆฌ ์ค‘ ์˜ค๋ฅ˜: {e}")