ssboost commited on
Commit
4f1bcfe
·
verified ·
1 Parent(s): d7fb11e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +47 -189
app.py CHANGED
@@ -12,13 +12,6 @@ import glob
12
  from datetime import datetime
13
  from gradio_client import Client
14
 
15
- # 환경변수 로드를 위한 dotenv 사용
16
- try:
17
- from dotenv import load_dotenv
18
- load_dotenv()
19
- except ImportError:
20
- pass
21
-
22
  # 로깅 설정
23
  logging.basicConfig(
24
  level=logging.INFO,
@@ -31,29 +24,9 @@ logging.basicConfig(
31
 
32
  logger = logging.getLogger(__name__)
33
 
34
- # API 클라이언트 설정
35
- def get_api_client():
36
- """환경변수에서 API 엔드포인트를 가져와 클라이언트 생성"""
37
- endpoint = os.getenv('API_ENDPOINT', '').strip()
38
-
39
- # 디버깅을 위한 환경변수 체크 (실제 값은 로그에 남기지 않음)
40
- if not endpoint:
41
- logger.error("API_ENDPOINT 환경변수가 설정되지 않았습니다.")
42
- logger.info("사용 가능한 환경변수들:")
43
- for key in os.environ.keys():
44
- if 'API' in key.upper():
45
- logger.info(f" {key}: {'설정됨' if os.environ[key] else '비어있음'}")
46
- raise ValueError("API_ENDPOINT 환경변수가 설정되지 않았습니다.")
47
-
48
- if endpoint.startswith('#') or len(endpoint) == 0:
49
- logger.error("API_ENDPOINT 환경변수가 올바르지 않은 형식입니다.")
50
- raise ValueError("API_ENDPOINT 환경변수가 올바르게 설정되지 않았습니다.")
51
-
52
- # 엔드포인트 형식 검증
53
- if '/' not in endpoint:
54
- logger.error("API_ENDPOINT는 'username/repo-name' 형식이어야 합니다.")
55
- raise ValueError("API_ENDPOINT 형식이 올바르지 않습니다.")
56
-
57
  return Client(endpoint)
58
 
59
  # 세션별 임시 파일 관리를 위한 딕셔너리
@@ -63,7 +36,6 @@ session_data = {}
63
  def cleanup_huggingface_temp_folders():
64
  """허깅페이스 임시 폴더 초기 정리"""
65
  try:
66
- # 일반적인 임시 디렉토리들
67
  temp_dirs = [
68
  tempfile.gettempdir(),
69
  "/tmp",
@@ -79,7 +51,6 @@ def cleanup_huggingface_temp_folders():
79
  for temp_dir in temp_dirs:
80
  if os.path.exists(temp_dir):
81
  try:
82
- # 기존 세션 파일들 정리
83
  session_files = glob.glob(os.path.join(temp_dir, "session_*.xlsx"))
84
  session_files.extend(glob.glob(os.path.join(temp_dir, "session_*.csv")))
85
  session_files.extend(glob.glob(os.path.join(temp_dir, "*keyword*.xlsx")))
@@ -89,7 +60,6 @@ def cleanup_huggingface_temp_folders():
89
 
90
  for file_path in session_files:
91
  try:
92
- # 파일이 1시간 이상 오래된 경우만 삭제
93
  if os.path.getmtime(file_path) < time.time() - 3600:
94
  os.remove(file_path)
95
  cleanup_count += 1
@@ -102,7 +72,6 @@ def cleanup_huggingface_temp_folders():
102
 
103
  logger.info(f"✅ 허깅페이스 임시 폴더 초기 정리 완료 - {cleanup_count}개 파일 삭제")
104
 
105
- # Gradio 캐시 폴더도 정리
106
  try:
107
  gradio_temp_dir = os.path.join(os.getcwd(), "gradio_cached_examples")
108
  if os.path.exists(gradio_temp_dir):
@@ -117,17 +86,14 @@ def cleanup_huggingface_temp_folders():
117
  def setup_clean_temp_environment():
118
  """깨끗한 임시 환경 설정"""
119
  try:
120
- # 1. 기존 임시 파일들 정리
121
  cleanup_huggingface_temp_folders()
122
 
123
- # 2. 애플리케이션 전용 임시 디렉토리 생성
124
- app_temp_dir = os.path.join(tempfile.gettempdir(), "control_tower_app")
125
  if os.path.exists(app_temp_dir):
126
  shutil.rmtree(app_temp_dir, ignore_errors=True)
127
  os.makedirs(app_temp_dir, exist_ok=True)
128
 
129
- # 3. 환경 변수 설정 (임시 디렉토리 지정)
130
- os.environ['CONTROL_TOWER_TEMP'] = app_temp_dir
131
 
132
  logger.info(f"✅ 애플리케이션 전용 임시 디렉토리 설정: {app_temp_dir}")
133
 
@@ -139,7 +105,7 @@ def setup_clean_temp_environment():
139
 
140
  def get_app_temp_dir():
141
  """애플리케이션 전용 임시 디렉토리 반환"""
142
- return os.environ.get('CONTROL_TOWER_TEMP', tempfile.gettempdir())
143
 
144
  def get_session_id():
145
  """세션 ID 생성"""
@@ -175,11 +141,10 @@ def cleanup_old_sessions():
175
  sessions_to_remove = []
176
 
177
  for session_id, data in session_data.items():
178
- if current_time - data.get('last_activity', 0) > 3600: # 1시간 초과
179
  sessions_to_remove.append(session_id)
180
 
181
  for session_id in sessions_to_remove:
182
- # 파일 정리
183
  if session_id in session_temp_files:
184
  for file_path in session_temp_files[session_id]:
185
  try:
@@ -190,7 +155,6 @@ def cleanup_old_sessions():
190
  logger.error(f"오래된 세션 파일 삭제 오류: {e}")
191
  del session_temp_files[session_id]
192
 
193
- # 세션 데이터 정리
194
  if session_id in session_data:
195
  del session_data[session_id]
196
  logger.info(f"오래된 세션 데이터 삭제: {session_id[:8]}...")
@@ -206,12 +170,10 @@ def create_session_temp_file(session_id, suffix='.xlsx'):
206
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
207
  random_suffix = str(random.randint(1000, 9999))
208
 
209
- # 애플리케이션 전용 임시 디렉토리 사용
210
  temp_dir = get_app_temp_dir()
211
  filename = f"session_{session_id[:8]}_{timestamp}_{random_suffix}{suffix}"
212
  temp_file_path = os.path.join(temp_dir, filename)
213
 
214
- # 빈 파일 생성
215
  with open(temp_file_path, 'w') as f:
216
  pass
217
 
@@ -219,11 +181,11 @@ def create_session_temp_file(session_id, suffix='.xlsx'):
219
  return temp_file_path
220
 
221
  def wrapper_modified(keyword, korean_only, apply_main_keyword_option, exclude_zero_volume, session_id):
222
- """키워드 검색 및 처리 래퍼 함수 (API 클라이언트 사용)"""
223
  update_session_activity(session_id)
224
 
225
  try:
226
- client = get_api_client()
227
  result = client.predict(
228
  keyword=keyword,
229
  korean_only=korean_only,
@@ -232,10 +194,8 @@ def wrapper_modified(keyword, korean_only, apply_main_keyword_option, exclude_ze
232
  api_name="/process_search_results"
233
  )
234
 
235
- # API 결과 처리
236
  table_html, cat_choices, vol_choices, selected_cat, download_file = result
237
 
238
- # 로컬 파일로 다운로드 파일 복사
239
  local_file = None
240
  if download_file:
241
  local_file = create_session_temp_file(session_id, '.xlsx')
@@ -245,7 +205,7 @@ def wrapper_modified(keyword, korean_only, apply_main_keyword_option, exclude_ze
245
  gr.update(value=table_html),
246
  gr.update(choices=cat_choices),
247
  gr.update(choices=vol_choices),
248
- None, # DataFrame은 클라이언트에서 관리하지 않음
249
  gr.update(choices=cat_choices, value=selected_cat),
250
  local_file,
251
  gr.update(visible=True),
@@ -256,7 +216,7 @@ def wrapper_modified(keyword, korean_only, apply_main_keyword_option, exclude_ze
256
  except Exception as e:
257
  logger.error(f"API 호출 오류: {e}")
258
  return (
259
- gr.update(value="<p>검색 오류가 발생했습니다. 다시 시도해주세요.</p>"),
260
  gr.update(choices=["전체 보기"]),
261
  gr.update(choices=["전체"]),
262
  None,
@@ -268,11 +228,11 @@ def wrapper_modified(keyword, korean_only, apply_main_keyword_option, exclude_ze
268
  )
269
 
270
  def analyze_with_auto_download(analysis_keywords, selected_category, state_df, session_id):
271
- """카테고리 일치 분석 실행 및 자동 다운로드 (API 클라이언트 사용)"""
272
  update_session_activity(session_id)
273
 
274
  try:
275
- client = get_api_client()
276
  result = client.predict(
277
  analysis_keywords=analysis_keywords,
278
  selected_category=selected_category,
@@ -281,7 +241,6 @@ def analyze_with_auto_download(analysis_keywords, selected_category, state_df, s
281
 
282
  analysis_result, download_file = result
283
 
284
- # 로컬 파일로 다운로드 파일 복사
285
  local_file = None
286
  if download_file:
287
  local_file = create_session_temp_file(session_id, '.xlsx')
@@ -294,11 +253,11 @@ def analyze_with_auto_download(analysis_keywords, selected_category, state_df, s
294
  return "분석 중 오류가 발생했습니다. 다시 시도해주세요.", None, gr.update(visible=False)
295
 
296
  def filter_and_sort_table(df, selected_cat, keyword_sort, total_volume_sort, usage_count_sort, selected_volume_range, exclude_zero_volume, session_id):
297
- """테이블 필터링 및 정렬 함수 (API 클라이언트 사용)"""
298
  update_session_activity(session_id)
299
 
300
  try:
301
- client = get_api_client()
302
  result = client.predict(
303
  selected_cat=selected_cat,
304
  keyword_sort=keyword_sort,
@@ -320,7 +279,7 @@ def update_category_selection(selected_cat, session_id):
320
  update_session_activity(session_id)
321
 
322
  try:
323
- client = get_api_client()
324
  result = client.predict(
325
  selected_cat=selected_cat,
326
  api_name="/update_category_selection"
@@ -336,7 +295,6 @@ def reset_interface(session_id):
336
  """인터페이스 리셋 함수 - 세션별 데이터 초기화"""
337
  update_session_activity(session_id)
338
 
339
- # 세션별 임시 파일 정리
340
  if session_id in session_temp_files:
341
  for file_path in session_temp_files[session_id]:
342
  try:
@@ -348,60 +306,22 @@ def reset_interface(session_id):
348
  session_temp_files[session_id] = []
349
 
350
  try:
351
- client = get_api_client()
352
- result = client.predict(
353
- api_name="/reset_interface"
354
- )
355
-
356
- # API 결과를 그대로 반환
357
  return result
358
 
359
  except Exception as e:
360
  logger.error(f"리셋 API 호출 오류: {e}")
361
  return (
362
- "", # 검색 키워드
363
- True, # 한글만 추출
364
- False, # 검색량 0 키워드 제외
365
- "메인키워드 적용", # 조합 방식
366
- "", # HTML 테이블
367
- ["전체 보기"], # 카테고리 필터
368
- "전체 보기", # 카테고리 필터 선택
369
- ["전체"], # 검색량 구간 필터
370
- "전체", # 검색량 구간 선택
371
- "정렬 없음", # 총검색량 정렬
372
- "정렬 없음", # 키워드 사용횟수 정렬
373
- ["전체 보기"], # 분석할 카테고리
374
- "전체 보기", # 분석할 카테고리 선택
375
- "", # 키워드 입력
376
- "", # 분석 결과
377
- None # 다운로드 파일
378
  )
379
 
380
- # 래퍼 함수들
381
  def search_with_loading(keyword, korean_only, apply_main_keyword, exclude_zero_volume, session_id):
382
  update_session_activity(session_id)
383
-
384
- try:
385
- client = get_api_client()
386
- result = client.predict(
387
- keyword=keyword,
388
- korean_only=korean_only,
389
- apply_main_keyword=apply_main_keyword,
390
- exclude_zero_volume=exclude_zero_volume,
391
- api_name="/search_with_loading"
392
- )
393
-
394
- return (
395
- gr.update(visible=True),
396
- gr.update(visible=False)
397
- )
398
-
399
- except Exception as e:
400
- logger.error(f"검색 로딩 API 호출 오류: {e}")
401
- return (
402
- gr.update(visible=False),
403
- gr.update(visible=True)
404
- )
405
 
406
  def process_search_results(keyword, korean_only, apply_main_keyword, exclude_zero_volume, session_id):
407
  update_session_activity(session_id)
@@ -431,49 +351,30 @@ def process_search_results(keyword, korean_only, apply_main_keyword, exclude_zer
431
 
432
  def analyze_with_loading(analysis_keywords, selected_category, state_df, session_id):
433
  update_session_activity(session_id)
434
-
435
- try:
436
- client = get_api_client()
437
- result = client.predict(
438
- analysis_keywords=analysis_keywords,
439
- selected_category=selected_category,
440
- api_name="/analyze_with_loading"
441
- )
442
-
443
- return gr.update(visible=True)
444
-
445
- except Exception as e:
446
- logger.error(f"분석 로딩 API 호출 오류: {e}")
447
- return gr.update(visible=False)
448
 
449
  def process_analyze_results(analysis_keywords, selected_category, state_df, session_id):
450
  update_session_activity(session_id)
451
  results = analyze_with_auto_download(analysis_keywords, selected_category, state_df, session_id)
452
  return results + (gr.update(visible=False),)
453
 
454
- # 세션 정리 스케줄러
455
  def start_session_cleanup_scheduler():
456
  """세션 정리 스케줄러 시작"""
457
  def cleanup_scheduler():
458
  while True:
459
- time.sleep(600) # 10분마다 실행
460
  cleanup_old_sessions()
461
- # 추가로 허깅페이스 임시 폴더도 주기적 정리
462
  cleanup_huggingface_temp_folders()
463
 
464
  threading.Thread(target=cleanup_scheduler, daemon=True).start()
465
 
466
  def cleanup_on_startup():
467
  """애플리케이션 시작 시 전체 정리"""
468
- logger.info("🧹 컨트롤 타워 애플리케이션 시작 - 초기 정리 작업 시작...")
469
 
470
- # 1. 허깅페이스 임시 폴더 정리
471
  cleanup_huggingface_temp_folders()
472
-
473
- # 2. 깨끗한 임시 환경 설정
474
  app_temp_dir = setup_clean_temp_environment()
475
 
476
- # 3. 전역 변수 초기화
477
  global session_temp_files, session_data
478
  session_temp_files.clear()
479
  session_data.clear()
@@ -482,7 +383,6 @@ def cleanup_on_startup():
482
 
483
  return app_temp_dir
484
 
485
- # Gradio 인터페이스 생성
486
  def create_app():
487
  fontawesome_html = """
488
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
@@ -490,7 +390,6 @@ def create_app():
490
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap">
491
  """
492
 
493
- # CSS 파일 로드
494
  try:
495
  with open('style.css', 'r', encoding='utf-8') as f:
496
  custom_css = f.read()
@@ -504,40 +403,24 @@ def create_app():
504
  )) as demo:
505
  gr.HTML(fontawesome_html)
506
 
507
- # 세션 ID 상태 (각 사용자별로 고유)
508
  session_id = gr.State(get_session_id)
509
-
510
- # 키워드 상태 관리
511
  keyword_state = gr.State("")
512
 
513
- # 입력 섹션
514
  with gr.Column(elem_classes="custom-frame fade-in"):
515
  gr.HTML('<div class="section-title"><i class="fas fa-search"></i> 검색 입력</div>')
516
 
517
  with gr.Row():
518
  with gr.Column(scale=1):
519
- keyword = gr.Textbox(
520
- label="메인 키워드",
521
- placeholder="예: 오징어"
522
- )
523
  with gr.Column(scale=1):
524
- search_btn = gr.Button(
525
- "메인키워드 분석",
526
- elem_classes="custom-button"
527
- )
528
 
529
  with gr.Accordion("옵션 설정", open=False):
530
  with gr.Row():
531
  with gr.Column(scale=1):
532
- korean_only = gr.Checkbox(
533
- label="한글만 추출",
534
- value=True
535
- )
536
  with gr.Column(scale=1):
537
- exclude_zero_volume = gr.Checkbox(
538
- label="검색량 0 키워드 제외",
539
- value=False
540
- )
541
 
542
  with gr.Row():
543
  with gr.Column(scale=1):
@@ -549,7 +432,6 @@ def create_app():
549
  with gr.Column(scale=1):
550
  gr.HTML("")
551
 
552
- # 진행 상태 표시 섹션
553
  with gr.Column(elem_classes="custom-frame fade-in", visible=False) as progress_section:
554
  gr.HTML('<div class="section-title"><i class="fas fa-spinner"></i> 분석 진행 상태</div>')
555
  progress_html = gr.HTML("""
@@ -564,7 +446,6 @@ def create_app():
564
  </div>
565
  """)
566
 
567
- # 메인키워드 분석 결과 섹션
568
  with gr.Column(elem_classes="custom-frame fade-in") as main_keyword_section:
569
  gr.HTML('<div class="section-title"><i class="fas fa-table"></i> 메인키워드 분석 결과</div>')
570
 
@@ -629,7 +510,6 @@ def create_app():
629
  gr.HTML("<div class='data-container' id='table_container'></div>")
630
  table_output = gr.HTML(elem_classes="fade-in")
631
 
632
- # 카테고리 분석 섹션
633
  with gr.Column(elem_classes="custom-frame fade-in", visible=False) as category_analysis_section:
634
  gr.HTML('<div class="section-title"><i class="fas fa-chart-bar"></i> 키워드 분석</div>')
635
 
@@ -649,7 +529,6 @@ def create_app():
649
  interactive=True
650
  )
651
 
652
- # 실행 섹션
653
  with gr.Column(elem_classes="execution-section", visible=False) as execution_section:
654
  gr.HTML('<div class="section-title"><i class="fas fa-play-circle"></i> 실행</div>')
655
  with gr.Row():
@@ -664,22 +543,16 @@ def create_app():
664
  elem_classes=["execution-button", "secondary-button"]
665
  )
666
 
667
- # 분석 결과 출력 섹션
668
  with gr.Column(elem_classes="custom-frame fade-in", visible=False) as analysis_output_section:
669
  gr.HTML('<div class="section-title"><i class="fas fa-list-ul"></i> 분석 결과 요약</div>')
670
 
671
  analysis_result = gr.HTML(elem_classes="fade-in")
672
 
673
  with gr.Row():
674
- download_output = gr.File(
675
- label="키워드 목록 다운로드",
676
- visible=True
677
- )
678
 
679
- # 상태 저장용 변수
680
  state_df = gr.State()
681
 
682
- # 이벤트 연결 - 모든 함수에 session_id 추가
683
  search_btn.click(
684
  fn=search_with_loading,
685
  inputs=[keyword, korean_only, apply_main_keyword, exclude_zero_volume, session_id],
@@ -696,7 +569,6 @@ def create_app():
696
  ]
697
  )
698
 
699
- # 필터 및 정렬 변경 이벤트 연결 - session_id 추가
700
  category_filter.change(
701
  fn=filter_and_sort_table,
702
  inputs=[
@@ -753,7 +625,6 @@ def create_app():
753
  outputs=[table_output]
754
  )
755
 
756
- # 카테고리 분석 버튼 이벤트 - session_id 추가
757
  analyze_btn.click(
758
  fn=analyze_with_loading,
759
  inputs=[analysis_keywords, selected_category, state_df, session_id],
@@ -764,7 +635,6 @@ def create_app():
764
  outputs=[analysis_result, download_output, analysis_output_section, progress_section]
765
  )
766
 
767
- # 리셋 버튼 이벤트 연결 - session_id 추가
768
  reset_btn.click(
769
  fn=reset_interface,
770
  inputs=[session_id],
@@ -773,54 +643,42 @@ def create_app():
773
  table_output, category_filter, category_filter,
774
  search_volume_filter, search_volume_filter,
775
  total_volume_sort, usage_count_sort,
776
- selected_category, selected_category,
777
- analysis_keywords, analysis_result, download_output
 
 
778
  ]
779
  )
780
 
781
  return demo
782
 
783
  if __name__ == "__main__":
784
- # ========== 시작 전체 초기화 ==========
785
- logger.info("🚀 컨트롤 타워 애플리케이션 시작...")
786
 
787
- # 1. 첫 번째: 허깅페이스 임시 폴더 정리 및 환경 설정
788
  app_temp_dir = cleanup_on_startup()
789
-
790
- # 2. 세션 정리 스케줄러 시작
791
  start_session_cleanup_scheduler()
792
 
793
- # 3. API 엔드포인트 확인
794
- try:
795
- client = get_api_client()
796
- logger.info("✅ API 클라이언트 연결 확인 완료")
797
- except Exception as e:
798
- logger.error(f"❌ API 클라이언트 연결 실패: {e}")
799
- raise
800
-
801
- logger.info("===== 컨트롤 타워 Application Startup at %s =====", time.strftime("%Y-%m-%d %H:%M:%S"))
802
  logger.info(f"📁 임시 파일 저장 위치: {app_temp_dir}")
803
 
804
- # ========== 앱 실행 ==========
805
  try:
806
  app = create_app()
807
  app.launch(
808
- share=False, # 보안을 위해 share 비활성화
809
- server_name="0.0.0.0", # 모든 IP에서 접근 허용
810
- server_port=7860, # 포트 지정
811
- max_threads=40, # 멀티유저를 위한 스레드 수 증가
812
- auth=None, # 필요시 인증 추가 가능
813
- show_error=True, # 에러 표시
814
- quiet=False, # 로그 표시
815
- favicon_path=None, # 파비콘 설정
816
- ssl_verify=False # SSL 검증 비활성화 (개발용)
817
  )
818
  except Exception as e:
819
  logger.error(f"애플리케이션 실행 실패: {e}")
820
  raise
821
  finally:
822
- # 애플리케이션 종료 정리
823
- logger.info("🧹 컨트롤 타워 애플리케이션 종료 - 최종 정리 작업...")
824
  try:
825
  cleanup_huggingface_temp_folders()
826
  if os.path.exists(app_temp_dir):
 
12
  from datetime import datetime
13
  from gradio_client import Client
14
 
 
 
 
 
 
 
 
15
  # 로깅 설정
16
  logging.basicConfig(
17
  level=logging.INFO,
 
24
 
25
  logger = logging.getLogger(__name__)
26
 
27
+ # API 클라이언트 설정 (환경변수로 숨김)
28
+ def get_client():
29
+ endpoint = os.getenv('API_ENDPOINT')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  return Client(endpoint)
31
 
32
  # 세션별 임시 파일 관리를 위한 딕셔너리
 
36
  def cleanup_huggingface_temp_folders():
37
  """허깅페이스 임시 폴더 초기 정리"""
38
  try:
 
39
  temp_dirs = [
40
  tempfile.gettempdir(),
41
  "/tmp",
 
51
  for temp_dir in temp_dirs:
52
  if os.path.exists(temp_dir):
53
  try:
 
54
  session_files = glob.glob(os.path.join(temp_dir, "session_*.xlsx"))
55
  session_files.extend(glob.glob(os.path.join(temp_dir, "session_*.csv")))
56
  session_files.extend(glob.glob(os.path.join(temp_dir, "*keyword*.xlsx")))
 
60
 
61
  for file_path in session_files:
62
  try:
 
63
  if os.path.getmtime(file_path) < time.time() - 3600:
64
  os.remove(file_path)
65
  cleanup_count += 1
 
72
 
73
  logger.info(f"✅ 허깅페이스 임시 폴더 초기 정리 완료 - {cleanup_count}개 파일 삭제")
74
 
 
75
  try:
76
  gradio_temp_dir = os.path.join(os.getcwd(), "gradio_cached_examples")
77
  if os.path.exists(gradio_temp_dir):
 
86
  def setup_clean_temp_environment():
87
  """깨끗한 임시 환경 설정"""
88
  try:
 
89
  cleanup_huggingface_temp_folders()
90
 
91
+ app_temp_dir = os.path.join(tempfile.gettempdir(), "keyword_app")
 
92
  if os.path.exists(app_temp_dir):
93
  shutil.rmtree(app_temp_dir, ignore_errors=True)
94
  os.makedirs(app_temp_dir, exist_ok=True)
95
 
96
+ os.environ['KEYWORD_APP_TEMP'] = app_temp_dir
 
97
 
98
  logger.info(f"✅ 애플리케이션 전용 임시 디렉토리 설정: {app_temp_dir}")
99
 
 
105
 
106
  def get_app_temp_dir():
107
  """애플리케이션 전용 임시 디렉토리 반환"""
108
+ return os.environ.get('KEYWORD_APP_TEMP', tempfile.gettempdir())
109
 
110
  def get_session_id():
111
  """세션 ID 생성"""
 
141
  sessions_to_remove = []
142
 
143
  for session_id, data in session_data.items():
144
+ if current_time - data.get('last_activity', 0) > 3600:
145
  sessions_to_remove.append(session_id)
146
 
147
  for session_id in sessions_to_remove:
 
148
  if session_id in session_temp_files:
149
  for file_path in session_temp_files[session_id]:
150
  try:
 
155
  logger.error(f"오래된 세션 파일 삭제 오류: {e}")
156
  del session_temp_files[session_id]
157
 
 
158
  if session_id in session_data:
159
  del session_data[session_id]
160
  logger.info(f"오래된 세션 데이터 삭제: {session_id[:8]}...")
 
170
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
171
  random_suffix = str(random.randint(1000, 9999))
172
 
 
173
  temp_dir = get_app_temp_dir()
174
  filename = f"session_{session_id[:8]}_{timestamp}_{random_suffix}{suffix}"
175
  temp_file_path = os.path.join(temp_dir, filename)
176
 
 
177
  with open(temp_file_path, 'w') as f:
178
  pass
179
 
 
181
  return temp_file_path
182
 
183
  def wrapper_modified(keyword, korean_only, apply_main_keyword_option, exclude_zero_volume, session_id):
184
+ """키워드 검색 및 처리 래퍼 함수 (API 사용)"""
185
  update_session_activity(session_id)
186
 
187
  try:
188
+ client = get_client()
189
  result = client.predict(
190
  keyword=keyword,
191
  korean_only=korean_only,
 
194
  api_name="/process_search_results"
195
  )
196
 
 
197
  table_html, cat_choices, vol_choices, selected_cat, download_file = result
198
 
 
199
  local_file = None
200
  if download_file:
201
  local_file = create_session_temp_file(session_id, '.xlsx')
 
205
  gr.update(value=table_html),
206
  gr.update(choices=cat_choices),
207
  gr.update(choices=vol_choices),
208
+ None,
209
  gr.update(choices=cat_choices, value=selected_cat),
210
  local_file,
211
  gr.update(visible=True),
 
216
  except Exception as e:
217
  logger.error(f"API 호출 오류: {e}")
218
  return (
219
+ gr.update(value="<p>검색 결과가 없습니다. 다른 키워드로 시도해보세요.</p>"),
220
  gr.update(choices=["전체 보기"]),
221
  gr.update(choices=["전체"]),
222
  None,
 
228
  )
229
 
230
  def analyze_with_auto_download(analysis_keywords, selected_category, state_df, session_id):
231
+ """카테고리 일치 분석 실행 및 자동 다운로드 (API 사용)"""
232
  update_session_activity(session_id)
233
 
234
  try:
235
+ client = get_client()
236
  result = client.predict(
237
  analysis_keywords=analysis_keywords,
238
  selected_category=selected_category,
 
241
 
242
  analysis_result, download_file = result
243
 
 
244
  local_file = None
245
  if download_file:
246
  local_file = create_session_temp_file(session_id, '.xlsx')
 
253
  return "분석 중 오류가 발생했습니다. 다시 시도해주세요.", None, gr.update(visible=False)
254
 
255
  def filter_and_sort_table(df, selected_cat, keyword_sort, total_volume_sort, usage_count_sort, selected_volume_range, exclude_zero_volume, session_id):
256
+ """테이블 필터링 및 정렬 함수 (API 사용)"""
257
  update_session_activity(session_id)
258
 
259
  try:
260
+ client = get_client()
261
  result = client.predict(
262
  selected_cat=selected_cat,
263
  keyword_sort=keyword_sort,
 
279
  update_session_activity(session_id)
280
 
281
  try:
282
+ client = get_client()
283
  result = client.predict(
284
  selected_cat=selected_cat,
285
  api_name="/update_category_selection"
 
295
  """인터페이스 리셋 함수 - 세션별 데이터 초기화"""
296
  update_session_activity(session_id)
297
 
 
298
  if session_id in session_temp_files:
299
  for file_path in session_temp_files[session_id]:
300
  try:
 
306
  session_temp_files[session_id] = []
307
 
308
  try:
309
+ client = get_client()
310
+ result = client.predict(api_name="/reset_interface")
 
 
 
 
311
  return result
312
 
313
  except Exception as e:
314
  logger.error(f"리셋 API 호출 오류: {e}")
315
  return (
316
+ "", True, False, "메인키워드 적용", "", ["전체 보기"], "전체 보기",
317
+ ["전체"], "전체", "정렬 없음", "정렬 없음", None, ["전체 보기"],
318
+ "전체 보기", "", "", None, gr.update(visible=False),
319
+ gr.update(visible=False), ""
 
 
 
 
 
 
 
 
 
 
 
 
320
  )
321
 
 
322
  def search_with_loading(keyword, korean_only, apply_main_keyword, exclude_zero_volume, session_id):
323
  update_session_activity(session_id)
324
+ return (gr.update(visible=True), gr.update(visible=False))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325
 
326
  def process_search_results(keyword, korean_only, apply_main_keyword, exclude_zero_volume, session_id):
327
  update_session_activity(session_id)
 
351
 
352
  def analyze_with_loading(analysis_keywords, selected_category, state_df, session_id):
353
  update_session_activity(session_id)
354
+ return gr.update(visible=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
355
 
356
  def process_analyze_results(analysis_keywords, selected_category, state_df, session_id):
357
  update_session_activity(session_id)
358
  results = analyze_with_auto_download(analysis_keywords, selected_category, state_df, session_id)
359
  return results + (gr.update(visible=False),)
360
 
 
361
  def start_session_cleanup_scheduler():
362
  """세션 정리 스케줄러 시작"""
363
  def cleanup_scheduler():
364
  while True:
365
+ time.sleep(600)
366
  cleanup_old_sessions()
 
367
  cleanup_huggingface_temp_folders()
368
 
369
  threading.Thread(target=cleanup_scheduler, daemon=True).start()
370
 
371
  def cleanup_on_startup():
372
  """애플리케이션 시작 시 전체 정리"""
373
+ logger.info("🧹 애플리케이션 시작 - 초기 정리 작업 시작...")
374
 
 
375
  cleanup_huggingface_temp_folders()
 
 
376
  app_temp_dir = setup_clean_temp_environment()
377
 
 
378
  global session_temp_files, session_data
379
  session_temp_files.clear()
380
  session_data.clear()
 
383
 
384
  return app_temp_dir
385
 
 
386
  def create_app():
387
  fontawesome_html = """
388
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
 
390
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap">
391
  """
392
 
 
393
  try:
394
  with open('style.css', 'r', encoding='utf-8') as f:
395
  custom_css = f.read()
 
403
  )) as demo:
404
  gr.HTML(fontawesome_html)
405
 
 
406
  session_id = gr.State(get_session_id)
 
 
407
  keyword_state = gr.State("")
408
 
 
409
  with gr.Column(elem_classes="custom-frame fade-in"):
410
  gr.HTML('<div class="section-title"><i class="fas fa-search"></i> 검색 입력</div>')
411
 
412
  with gr.Row():
413
  with gr.Column(scale=1):
414
+ keyword = gr.Textbox(label="메인 키워드", placeholder="예: 오징어")
 
 
 
415
  with gr.Column(scale=1):
416
+ search_btn = gr.Button("메인키워드 분석", elem_classes="custom-button")
 
 
 
417
 
418
  with gr.Accordion("옵션 설정", open=False):
419
  with gr.Row():
420
  with gr.Column(scale=1):
421
+ korean_only = gr.Checkbox(label="한글만 추출", value=True)
 
 
 
422
  with gr.Column(scale=1):
423
+ exclude_zero_volume = gr.Checkbox(label="검색량 0 키워드 제외", value=False)
 
 
 
424
 
425
  with gr.Row():
426
  with gr.Column(scale=1):
 
432
  with gr.Column(scale=1):
433
  gr.HTML("")
434
 
 
435
  with gr.Column(elem_classes="custom-frame fade-in", visible=False) as progress_section:
436
  gr.HTML('<div class="section-title"><i class="fas fa-spinner"></i> 분석 진행 상태</div>')
437
  progress_html = gr.HTML("""
 
446
  </div>
447
  """)
448
 
 
449
  with gr.Column(elem_classes="custom-frame fade-in") as main_keyword_section:
450
  gr.HTML('<div class="section-title"><i class="fas fa-table"></i> 메인키워드 분석 결과</div>')
451
 
 
510
  gr.HTML("<div class='data-container' id='table_container'></div>")
511
  table_output = gr.HTML(elem_classes="fade-in")
512
 
 
513
  with gr.Column(elem_classes="custom-frame fade-in", visible=False) as category_analysis_section:
514
  gr.HTML('<div class="section-title"><i class="fas fa-chart-bar"></i> 키워드 분석</div>')
515
 
 
529
  interactive=True
530
  )
531
 
 
532
  with gr.Column(elem_classes="execution-section", visible=False) as execution_section:
533
  gr.HTML('<div class="section-title"><i class="fas fa-play-circle"></i> 실행</div>')
534
  with gr.Row():
 
543
  elem_classes=["execution-button", "secondary-button"]
544
  )
545
 
 
546
  with gr.Column(elem_classes="custom-frame fade-in", visible=False) as analysis_output_section:
547
  gr.HTML('<div class="section-title"><i class="fas fa-list-ul"></i> 분석 결과 요약</div>')
548
 
549
  analysis_result = gr.HTML(elem_classes="fade-in")
550
 
551
  with gr.Row():
552
+ download_output = gr.File(label="키워드 목록 다운로드", visible=True)
 
 
 
553
 
 
554
  state_df = gr.State()
555
 
 
556
  search_btn.click(
557
  fn=search_with_loading,
558
  inputs=[keyword, korean_only, apply_main_keyword, exclude_zero_volume, session_id],
 
569
  ]
570
  )
571
 
 
572
  category_filter.change(
573
  fn=filter_and_sort_table,
574
  inputs=[
 
625
  outputs=[table_output]
626
  )
627
 
 
628
  analyze_btn.click(
629
  fn=analyze_with_loading,
630
  inputs=[analysis_keywords, selected_category, state_df, session_id],
 
635
  outputs=[analysis_result, download_output, analysis_output_section, progress_section]
636
  )
637
 
 
638
  reset_btn.click(
639
  fn=reset_interface,
640
  inputs=[session_id],
 
643
  table_output, category_filter, category_filter,
644
  search_volume_filter, search_volume_filter,
645
  total_volume_sort, usage_count_sort,
646
+ state_df, selected_category, selected_category,
647
+ analysis_keywords, analysis_result, download_output,
648
+ keyword_analysis_section, analysis_output_section,
649
+ keyword_state
650
  ]
651
  )
652
 
653
  return demo
654
 
655
  if __name__ == "__main__":
656
+ logger.info("🚀 메인키워드 분석 애플리케이션 시작...")
 
657
 
 
658
  app_temp_dir = cleanup_on_startup()
 
 
659
  start_session_cleanup_scheduler()
660
 
661
+ logger.info("===== 멀티유저 메인키워드 분석 Application Startup at %s =====", time.strftime("%Y-%m-%d %H:%M:%S"))
 
 
 
 
 
 
 
 
662
  logger.info(f"📁 임시 파일 저장 위치: {app_temp_dir}")
663
 
 
664
  try:
665
  app = create_app()
666
  app.launch(
667
+ share=False,
668
+ server_name="0.0.0.0",
669
+ server_port=7860,
670
+ max_threads=40,
671
+ auth=None,
672
+ show_error=True,
673
+ quiet=False,
674
+ favicon_path=None,
675
+ ssl_verify=False
676
  )
677
  except Exception as e:
678
  logger.error(f"애플리케이션 실행 실패: {e}")
679
  raise
680
  finally:
681
+ logger.info("🧹 애플리케이션 종료 - 최종 정리 작업...")
 
682
  try:
683
  cleanup_huggingface_temp_folders()
684
  if os.path.exists(app_temp_dir):