ssboost/4G23WAS3
0
1import gradio as gr2import pandas as pd3import os4import time5import threading6import tempfile7import logging8import uuid9import shutil10import glob11from datetime import datetime12import sys13import types14 15# 로깅 설정16logging.basicConfig(17 level=logging.INFO,18 format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',19 handlers=[20 logging.StreamHandler(),21 logging.FileHandler('category_analysis_app.log', mode='a')22 ]23)24 25logger = logging.getLogger(__name__)26 27# 환경변수에서 모듈 코드 로드 및 동적 생성28def load_module_from_env(module_name, env_var_name):29 """환경변수에서 모듈 코드를 로드하여 동적으로 모듈 생성"""30 try:31 module_code = os.getenv(env_var_name)32 if not module_code:33 raise ValueError(f"환경변수 {env_var_name}가 설정되지 않았습니다.")34 35 # 새 모듈 생성36 module = types.ModuleType(module_name)37 38 # 모듈에 필요한 기본 임포트들 추가39 module.__dict__.update({40 'os': __import__('os'),41 'time': __import__('time'),42 'logging': __import__('logging'),43 'pandas': __import__('pandas'),44 'requests': __import__('requests'),45 'tempfile': __import__('tempfile'),46 'threading': __import__('threading'),47 're': __import__('re'),48 'random': __import__('random'),49 'uuid': __import__('uuid'),50 'shutil': __import__('shutil'),51 'glob': __import__('glob'),52 'datetime': __import__('datetime'),53 'types': __import__('types'),54 'collections': __import__('collections'),55 'Counter': __import__('collections').Counter,56 'defaultdict': __import__('collections').defaultdict,57 'hmac': __import__('hmac'),58 'hashlib': __import__('hashlib'),59 'base64': __import__('base64'),60 })61 62 # 코드 실행63 exec(module_code, module.__dict__)64 65 # 시스템 모듈에 등록66 sys.modules[module_name] = module67 68 logger.info(f"✅ 모듈 {module_name} 로드 완료")69 return module70 71 except Exception as e:72 logger.error(f"❌ 모듈 {module_name} 로드 실패: {e}")73 raise74 75# 필요한 모듈들을 환경변수에서 로드76logger.info("🔄 모듈 로드 시작...")77 78try:79 # 1. api_utils 모듈 로드80 api_utils = load_module_from_env('api_utils', 'API_UTILS_CODE')81 82 # 2. text_utils 모듈 로드 (다른 모듈들이 의존하므로 먼저 로드)83 text_utils = load_module_from_env('text_utils', 'TEXT_UTILS_CODE')84 85 # 3. keyword_search 모듈 로드86 keyword_search = load_module_from_env('keyword_search', 'KEYWORD_SEARCH_CODE')87 88 # 4. product_search 모듈 로드 (text_utils, keyword_search 의존)89 product_search_module = load_module_from_env('product_search', 'PRODUCT_SEARCH_CODE')90 # product_search 모듈에 의존성 주입91 product_search_module.api_utils = api_utils92 product_search_module.text_utils = text_utils93 product_search = product_search_module94 95 # 5. keyword_processor 모듈 로드96 keyword_processor_module = load_module_from_env('keyword_processor', 'KEYWORD_PROCESSOR_CODE')97 # keyword_processor 모듈에 의존성 주입98 keyword_processor_module.text_utils = text_utils99 keyword_processor_module.keyword_search = keyword_search100 keyword_processor_module.product_search = product_search101 keyword_processor = keyword_processor_module102 103 # 6. export_utils 모듈 로드104 export_utils = load_module_from_env('export_utils', 'EXPORT_UTILS_CODE')105 106 # 7. category_analysis 모듈 로드 (모든 모듈 의존)107 category_analysis_module = load_module_from_env('category_analysis', 'CATEGORY_ANALYSIS_CODE')108 # category_analysis 모듈에 의존성 주입109 category_analysis_module.text_utils = text_utils110 category_analysis_module.product_search = product_search111 category_analysis_module.keyword_search = keyword_search112 category_analysis = category_analysis_module113 114 logger.info("✅ 모든 모듈 로드 완료")115 116except Exception as e:117 logger.error(f"❌ 모듈 로드 중 치명적 오류: {e}")118 logger.error("필요한 환경변수들이 설정되었는지 확인하세요:")119 logger.error("- API_UTILS_CODE")120 logger.error("- TEXT_UTILS_CODE") 121 logger.error("- KEYWORD_SEARCH_CODE")122 logger.error("- PRODUCT_SEARCH_CODE")123 logger.error("- KEYWORD_PROCESSOR_CODE")124 logger.error("- EXPORT_UTILS_CODE")125 logger.error("- CATEGORY_ANALYSIS_CODE")126 raise127 128# 세션별 임시 파일 관리를 위한 딕셔너리129session_temp_files = {}130session_data = {}131 132def cleanup_huggingface_temp_folders():133 """허깅페이스 임시 폴더 초기 정리"""134 try:135 # 일반적인 임시 디렉토리들136 temp_dirs = [137 tempfile.gettempdir(),138 "/tmp",139 "/var/tmp",140 os.path.join(os.getcwd(), "temp"),141 os.path.join(os.getcwd(), "tmp"),142 "/gradio_cached_examples",143 "/flagged"144 ]145 146 cleanup_count = 0147 148 for temp_dir in temp_dirs:149 if os.path.exists(temp_dir):150 try:151 # 기존 세션 파일들 정리152 session_files = glob.glob(os.path.join(temp_dir, "session_*.xlsx"))153 session_files.extend(glob.glob(os.path.join(temp_dir, "session_*.csv")))154 session_files.extend(glob.glob(os.path.join(temp_dir, "*category*.xlsx")))155 session_files.extend(glob.glob(os.path.join(temp_dir, "*category*.csv")))156 session_files.extend(glob.glob(os.path.join(temp_dir, "*analysis*.xlsx")))157 session_files.extend(glob.glob(os.path.join(temp_dir, "*analysis*.csv")))158 session_files.extend(glob.glob(os.path.join(temp_dir, "tmp*.xlsx")))159 session_files.extend(glob.glob(os.path.join(temp_dir, "tmp*.csv")))160 161 for file_path in session_files:162 try:163 # 파일이 1시간 이상 오래된 경우만 삭제164 if os.path.getmtime(file_path) < time.time() - 3600:165 os.remove(file_path)166 cleanup_count += 1167 logger.info(f"초기 정리: 오래된 임시 파일 삭제 - {file_path}")168 except Exception as e:169 logger.warning(f"파일 삭제 실패 (무시됨): {file_path} - {e}")170 171 except Exception as e:172 logger.warning(f"임시 디렉토리 정리 실패 (무시됨): {temp_dir} - {e}")173 174 logger.info(f"✅ 허깅페이스 임시 폴더 초기 정리 완료 - {cleanup_count}개 파일 삭제")175 176 # Gradio 캐시 폴더도 정리177 try:178 gradio_temp_dir = os.path.join(os.getcwd(), "gradio_cached_examples")179 if os.path.exists(gradio_temp_dir):180 shutil.rmtree(gradio_temp_dir, ignore_errors=True)181 logger.info("Gradio 캐시 폴더 정리 완료")182 except Exception as e:183 logger.warning(f"Gradio 캐시 폴더 정리 실패 (무시됨): {e}")184 185 except Exception as e:186 logger.error(f"초기 임시 폴더 정리 중 오류 (계속 진행): {e}")187 188def setup_clean_temp_environment():189 """깨끗한 임시 환경 설정"""190 try:191 # 1. 기존 임시 파일들 정리192 cleanup_huggingface_temp_folders()193 194 # 2. 애플리케이션 전용 임시 디렉토리 생성195 app_temp_dir = os.path.join(tempfile.gettempdir(), "category_analysis_app")196 if os.path.exists(app_temp_dir):197 shutil.rmtree(app_temp_dir, ignore_errors=True)198 os.makedirs(app_temp_dir, exist_ok=True)199 200 # 3. 환경 변수 설정 (임시 디렉토리 지정)201 os.environ['CATEGORY_APP_TEMP'] = app_temp_dir202 203 logger.info(f"✅ 애플리케이션 전용 임시 디렉토리 설정: {app_temp_dir}")204 205 return app_temp_dir206 207 except Exception as e:208 logger.error(f"임시 환경 설정 실패: {e}")209 return tempfile.gettempdir()210 211def get_app_temp_dir():212 """애플리케이션 전용 임시 디렉토리 반환"""213 return os.environ.get('CATEGORY_APP_TEMP', tempfile.gettempdir())214 215def get_session_id():216 """세션 ID 생성"""217 return str(uuid.uuid4())218 219def cleanup_session_files(session_id, delay=300):220 """세션별 임시 파일 정리 함수"""221 def cleanup():222 time.sleep(delay)223 if session_id in session_temp_files:224 files_to_remove = session_temp_files[session_id].copy()225 del session_temp_files[session_id]226 227 for file_path in files_to_remove:228 try:229 if os.path.exists(file_path):230 os.remove(file_path)231 logger.info(f"세션 {session_id[:8]}... 임시 파일 삭제: {file_path}")232 except Exception as e:233 logger.error(f"세션 {session_id[:8]}... 파일 삭제 오류: {e}")234 235 threading.Thread(target=cleanup, daemon=True).start()236 237def register_session_file(session_id, file_path):238 """세션별 파일 등록"""239 if session_id not in session_temp_files:240 session_temp_files[session_id] = []241 session_temp_files[session_id].append(file_path)242 243def cleanup_old_sessions():244 """오래된 세션 데이터 정리"""245 current_time = time.time()246 sessions_to_remove = []247 248 for session_id, data in session_data.items():249 if current_time - data.get('last_activity', 0) > 3600: # 1시간 초과250 sessions_to_remove.append(session_id)251 252 for session_id in sessions_to_remove:253 # 파일 정리254 if session_id in session_temp_files:255 for file_path in session_temp_files[session_id]:256 try:257 if os.path.exists(file_path):258 os.remove(file_path)259 logger.info(f"오래된 세션 {session_id[:8]}... 파일 삭제: {file_path}")260 except Exception as e:261 logger.error(f"오래된 세션 파일 삭제 오류: {e}")262 del session_temp_files[session_id]263 264 # 세션 데이터 정리265 if session_id in session_data:266 del session_data[session_id]267 logger.info(f"오래된 세션 데이터 삭제: {session_id[:8]}...")268 269def update_session_activity(session_id):270 """세션 활동 시간 업데이트"""271 if session_id not in session_data:272 session_data[session_id] = {}273 session_data[session_id]['last_activity'] = time.time()274 275def create_session_temp_file(session_id, suffix='.xlsx'):276 """세션별 임시 파일 생성 (전용 디렉토리 사용)"""277 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")278 random_suffix = str(time.time_ns())[-4:]279 280 # 애플리케이션 전용 임시 디렉토리 사용281 temp_dir = get_app_temp_dir()282 filename = f"session_{session_id[:8]}_{timestamp}_{random_suffix}{suffix}"283 temp_file_path = os.path.join(temp_dir, filename)284 285 # 빈 파일 생성286 with open(temp_file_path, 'w') as f:287 pass288 289 register_session_file(session_id, temp_file_path)290 return temp_file_path291 292def analyze_product_terms_wrapper(product_name, main_keyword, current_state, session_id):293 """상품명 키워드 분석 래퍼 함수 - 세션 ID 추가"""294 update_session_activity(session_id)295 296 if not product_name:297 return "상품명을 입력해주세요.", current_state, None, gr.update(visible=False)298 299 # 분석 수행 - HTML 결과와 키워드 분석 결과 함께 받기300 result_html, keyword_results = category_analysis.analyze_product_terms(product_name, main_keyword)301 302 # 새로운 상태 생성303 if current_state is None or not isinstance(current_state, dict):304 current_state = {}305 306 # 분석 결과를 상태에 추가307 current_state["keyword_analysis_results"] = keyword_results308 current_state["product_name"] = product_name309 current_state["main_keyword"] = main_keyword310 311 # 세션별 엑셀 파일 다운로드 - 자동 다운로드312 excel_path = download_analysis(current_state, session_id)313 314 # 출력 섹션 표시315 return result_html, current_state, excel_path, gr.update(visible=True)316 317def download_analysis(result, session_id):318 """카테고리 분석 결과 다운로드 (세션별)"""319 update_session_activity(session_id)320 321 if not result or not isinstance(result, dict):322 logger.warning(f"세션 {session_id[:8]}... 분석 결과가 없습니다.")323 return None324 325 try:326 # 상품명 분석 결과가 있는지 확인327 if "keyword_analysis_results" in result:328 logger.info(f"세션 {session_id[:8]}... 키워드 분석 결과 포함하여 다운로드: {len(result['keyword_analysis_results'])}개 키워드")329 330 # 세션별 임시 파일 생성331 temp_filename = create_session_temp_file(session_id, '.xlsx')332 333 # 데이터프레임 생성334 keywords = []335 pc_volumes = []336 mobile_volumes = []337 total_volumes = []338 ranges = []339 category_items = []340 341 for kw_result in result["keyword_analysis_results"]:342 keywords.append(kw_result.get("키워드", ""))343 pc_volumes.append(kw_result.get("PC검색량", 0))344 mobile_volumes.append(kw_result.get("모바일검색량", 0))345 total_volumes.append(kw_result.get("총검색량", 0))346 ranges.append(kw_result.get("검색량구간", ""))347 category_items.append(kw_result.get("카테고리항목", ""))348 349 # 데이터프레임으로 변환350 df = pd.DataFrame({351 "키워드": keywords,352 "PC검색량": pc_volumes,353 "모바일검색량": mobile_volumes,354 "총검색량": total_volumes,355 "검색량구간": ranges,356 "카테고리항목": category_items357 })358 359 with pd.ExcelWriter(temp_filename, engine="xlsxwriter") as writer:360 df.to_excel(writer, sheet_name="상품명 검증 결과", index=False)361 362 ws = writer.sheets["상품명 검증 결과"]363 364 # 줄바꿈 + 위쪽 정렬 서식365 wrap_fmt = writer.book.add_format({366 "text_wrap": True,367 "valign": "top"368 })369 370 # F열('카테고리항목') 전체에 서식 적용 + 열 너비371 ws.set_column("F:F", 40, wrap_fmt)372 373 # 열 너비 설정374 worksheet = writer.sheets['상품명 검증 결과']375 worksheet.set_column('A:A', 20) # 키워드376 worksheet.set_column('B:B', 12) # PC검색량377 worksheet.set_column('C:C', 12) # 모바일검색량378 worksheet.set_column('D:D', 12) # 총검색량379 worksheet.set_column('E:E', 12) # 검색량구간380 worksheet.set_column('F:F', 40) # 카테고리항목381 382 # 헤더 서식 지정383 header_format = writer.book.add_format({384 'bold': True,385 'bg_color': '#FB7F0D',386 'color': 'white',387 'border': 1388 })389 390 # 헤더에 서식 적용391 for col_num, value in enumerate(df.columns.values):392 worksheet.write(0, col_num, value, header_format)393 394 logger.info(f"세션 {session_id[:8]}... 엑셀 파일 저장 완료: {temp_filename}")395 return temp_filename396 else:397 logger.warning(f"세션 {session_id[:8]}... 키워드 분석 결과가 없습니다.")398 return None399 except Exception as e:400 logger.error(f"세션 {session_id[:8]}... 다운로드 중 오류 발생: {e}")401 import traceback402 logger.error(traceback.format_exc())403 return None404 405def reset_interface(session_id):406 """인터페이스 리셋 함수 - 세션별 데이터 초기화"""407 update_session_activity(session_id)408 409 # 세션별 임시 파일 정리410 if session_id in session_temp_files:411 for file_path in session_temp_files[session_id]:412 try:413 if os.path.exists(file_path):414 os.remove(file_path)415 logger.info(f"세션 {session_id[:8]}... 리셋 시 파일 삭제: {file_path}")416 except Exception as e:417 logger.error(f"세션 {session_id[:8]}... 리셋 시 파일 삭제 오류: {e}")418 session_temp_files[session_id] = []419 420 return (421 "", # 메인 키워드 입력422 "", # 상품명 입력423 "", # 분석 결과 출력424 None, # 다운로드 파일425 None, # 상태 변수426 gr.update(visible=False) # 분석 결과 섹션427 )428 429def product_analyze_with_loading(product_name, main_keyword, current_state, session_id):430 """로딩 표시 함수"""431 update_session_activity(session_id)432 return gr.update(visible=True)433 434def process_product_analyze(product_name, main_keyword, current_state, session_id):435 """실제 분석 수행"""436 update_session_activity(session_id)437 results = analyze_product_terms_wrapper(product_name, main_keyword, current_state, session_id)438 # 로딩 인디케이터 숨기기439 return results + (gr.update(visible=False),)440 441# 세션 정리 스케줄러442def start_session_cleanup_scheduler():443 """세션 정리 스케줄러 시작"""444 def cleanup_scheduler():445 while True:446 time.sleep(600) # 10분마다 실행447 cleanup_old_sessions()448 # 추가로 허깅페이스 임시 폴더도 주기적 정리449 cleanup_huggingface_temp_folders()450 451 threading.Thread(target=cleanup_scheduler, daemon=True).start()452 453def cleanup_on_startup():454 """애플리케이션 시작 시 전체 정리"""455 logger.info("🧹 카테고리 분석 애플리케이션 시작 - 초기 정리 작업 시작...")456 457 # 1. 허깅페이스 임시 폴더 정리458 cleanup_huggingface_temp_folders()459 460 # 2. 깨끗한 임시 환경 설정461 app_temp_dir = setup_clean_temp_environment()462 463 # 3. 전역 변수 초기화464 global session_temp_files, session_data465 session_temp_files.clear()466 session_data.clear()467 468 logger.info(f"✅ 초기 정리 작업 완료 - 앱 전용 디렉토리: {app_temp_dir}")469 470 return app_temp_dir471 472# Gradio 인터페이스 생성473def create_app():474 # FontAwesome 아이콘 포함475 fontawesome_html = """476 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">477 <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css">478 <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap">479 """480 481 # CSS 파일 로드482 try:483 with open('style.css', 'r', encoding='utf-8') as f:484 custom_css = f.read()485 except:486 # CSS 파일이 없는 경우 기본 스타일 사용487 custom_css = """488 :root {489 --primary-color: #FB7F0D;490 --secondary-color: #ff9a8b;491 }492 .custom-button {493 background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important;494 color: white !important;495 border-radius: 30px !important;496 height: 45px !important;497 font-size: 16px !important;498 font-weight: bold !important;499 width: 100% !important;500 text-align: center !important;501 display: flex !important;502 align-items: center !important;503 justify-content: center !important;504 }505 .reset-button {506 background: linear-gradient(135deg, #6c757d, #495057) !important;507 color: white !important;508 border-radius: 30px !important;509 height: 45px !important;510 font-size: 16px !important;511 font-weight: bold !important;512 width: 100% !important;513 text-align: center !important;514 display: flex !important;515 align-items: center !important;516 justify-content: center !important;517 }518 .section-title {519 border-bottom: 2px solid #FB7F0D;520 font-weight: bold;521 padding-bottom: 5px;522 margin-bottom: 15px;523 }524 .loading-indicator {525 display: flex;526 align-items: center;527 justify-content: center;528 padding: 15px;529 background-color: #f8f9fa;530 border-radius: 5px;531 margin: 10px 0;532 border: 1px solid #ddd;533 }534 .loading-spinner {535 border: 4px solid rgba(0, 0, 0, 0.1);536 width: 24px;537 height: 24px;538 border-radius: 50%;539 border-left-color: #FB7F0D;540 animation: spin 1s linear infinite;541 margin-right: 10px;542 }543 @keyframes spin {544 0% { transform: rotate(0deg); }545 100% { transform: rotate(360deg); }546 }547 .progress-bar {548 height: 10px;549 background-color: #FB7F0D;550 border-radius: 5px;551 width: 0%;552 animation: progressAnim 2s ease-in-out infinite;553 }554 @keyframes progressAnim {555 0% { width: 10%; }556 50% { width: 70%; }557 100% { width: 10%; }558 }559 .execution-section {560 margin-top: 20px;561 background-color: #f9f9f9;562 border-radius: 8px;563 padding: 15px;564 border: 1px solid #e5e5e5;565 }566 .session-info {567 background-color: #e8f4f8;568 padding: 8px 12px;569 border-radius: 4px;570 font-size: 12px;571 color: #0c5460;572 margin-bottom: 10px;573 text-align: center;574 }575 """576 577 with gr.Blocks(css=custom_css, theme=gr.themes.Default(578 primary_hue="orange",579 secondary_hue="orange",580 font=[gr.themes.GoogleFont("Noto Sans KR"), "ui-sans-serif", "system-ui"]581 )) as demo:582 gr.HTML(fontawesome_html)583 584 # 세션 ID 상태 (각 사용자별로 고유)585 session_id = gr.State(get_session_id)586 587 # 입력 섹션588 with gr.Column(elem_classes="custom-frame fade-in"):589 gr.HTML('<div class="section-title"><i class="fas fa-tag"></i> 상품명 분석 입력</div>')590 591 # 메인 키워드와 상품명 입력을 한 줄에 배치592 with gr.Row():593 with gr.Column(scale=1):594 main_keyword = gr.Textbox(595 label="메인 키워드", 596 placeholder="예: 오징어"597 )598 with gr.Column(scale=1):599 product_name = gr.Textbox(600 label="상품명", 601 placeholder="예: 손질 오징어 촉촉한 진미채"602 )603 604 # 실행 섹션 - 버튼 통합605 with gr.Column(elem_classes="execution-section"):606 gr.HTML('<div class="section-title"><i class="fas fa-play-circle"></i> 실행</div>')607 with gr.Row():608 with gr.Column(scale=1):609 analyze_product_btn = gr.Button(610 "상품명 분석", 611 elem_classes=["execution-button", "primary-button"]612 )613 with gr.Column(scale=1):614 reset_btn = gr.Button(615 "모든 입력 초기화", 616 elem_classes=["execution-button", "secondary-button"]617 )618 619 # 진행 상태 표시 섹션 (초기에는 숨김)620 with gr.Column(elem_classes="custom-frame fade-in", visible=False) as progress_section:621 gr.HTML('<div class="section-title"><i class="fas fa-spinner"></i> 분석 진행 상태</div>')622 # 사용자 친화적인 진행 상태 표시623 progress_html = gr.HTML("""624 <div style="padding: 15px; background-color: #f9f9f9; border-radius: 5px; margin: 10px 0; border: 1px solid #ddd;">625 <div style="margin-bottom: 10px; display: flex; align-items: center;">626 <i class="fas fa-spinner fa-spin" style="color: #FB7F0D; margin-right: 10px;"></i>627 <span>상품명 분석중입니다. 잠시만 기다려주세요...</span>628 </div>629 <div style="background-color: #e9ecef; height: 10px; border-radius: 5px; overflow: hidden;">630 <div class="progress-bar"></div>631 </div>632 </div>633 """)634 635 # 상품명 키워드 분석 결과 섹션 (초기에는 숨김)636 with gr.Column(elem_classes="custom-frame fade-in", visible=False) as product_analysis_section:637 gr.HTML('<div class="section-title"><i class="fas fa-table"></i> 상품명 키워드 분석 결과</div>')638 639 # 상품명 분석 결과 640 product_analysis_result = gr.HTML(elem_classes="fade-in")641 642 # 엑셀 다운로드 파일643 download_file = gr.File(644 label="분석 결과 다운로드", 645 visible=True646 )647 648 # 상태 저장용 변수 - 분석 결과 저장649 analysis_result_state = gr.State()650 651 # 상품명 분석 버튼 연결 - 로딩 표시 후 자동 다운로드 (세션 ID 추가)652 analyze_product_btn.click(653 fn=product_analyze_with_loading,654 inputs=[product_name, main_keyword, analysis_result_state, session_id],655 outputs=[progress_section]656 ).then(657 fn=process_product_analyze,658 inputs=[product_name, main_keyword, analysis_result_state, session_id],659 outputs=[660 product_analysis_result, analysis_result_state, 661 download_file, product_analysis_section, progress_section662 ]663 )664 665 # 리셋 버튼 이벤트 연결 (세션 ID 추가)666 reset_btn.click(667 fn=reset_interface,668 inputs=[session_id],669 outputs=[670 main_keyword, product_name, product_analysis_result, 671 download_file, analysis_result_state, product_analysis_section672 ]673 )674 675 return demo676 677if __name__ == "__main__":678 # ========== 시작 시 전체 초기화 ==========679 logger.info("🚀 카테고리 분석 애플리케이션 시작...")680 681 # 1. 첫 번째: 허깅페이스 임시 폴더 정리 및 환경 설정682 app_temp_dir = cleanup_on_startup()683 684 # 2. 세션 정리 스케줄러 시작685 start_session_cleanup_scheduler()686 687 # 3. API 설정 초기화688 api_utils.initialize_api_configs()689 690 # 4. Gemini 모델 초기화691 gemini_model = text_utils.get_gemini_model()692 693 logger.info("===== 멀티유저 카테고리 분석 Application Startup at %s =====", time.strftime("%Y-%m-%d %H:%M:%S"))694 logger.info(f"📁 임시 파일 저장 위치: {app_temp_dir}")695 696 # ========== 앱 실행 ==========697 try:698 app = create_app()699 app.launch(700 share=False, # 보안을 위해 share 비활성화701 server_name="0.0.0.0", # 모든 IP에서 접근 허용702 server_port=7860, # 포트 지정703 max_threads=40, # 멀티유저를 위한 스레드 수 증가704 auth=None, # 필요시 인증 추가 가능705 show_error=True, # 에러 표시706 quiet=False, # 로그 표시707 favicon_path=None, # 파비콘 설정708 ssl_verify=False # SSL 검증 비활성화 (개발용)709 )710 except Exception as e:711 logger.error(f"애플리케이션 실행 실패: {e}")712 raise713 finally:714 # 애플리케이션 종료 시 정리715 logger.info("🧹 애플리케이션 종료 - 최종 정리 작업...")716 try:717 cleanup_huggingface_temp_folders()718 if os.path.exists(app_temp_dir):719 shutil.rmtree(app_temp_dir, ignore_errors=True)720 logger.info("✅ 최종 정리 완료")721 except Exception as e:722 logger.error(f"최종 정리 중 오류: {e}")