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}")