Download app.py from ssboost/F23ASFGGD: direct link, hf CLI and curl.
- Browser
- Download file 42 kB
-
https://huggingface.co/spaces/ssboost/F23ASFGGD/resolve/main/app.py
- Command line
-
hf download hf://spaces/ssboost/F23ASFGGD/app.py
-
curl -L -o app.py https://huggingface.co/spaces/ssboost/F23ASFGGD/resolve/main/app.py
42 kB
| import gradio as gr | |
| import pandas as pd | |
| import os | |
| import time | |
| import threading | |
| import tempfile | |
| import logging | |
| import random | |
| import uuid | |
| import shutil | |
| import glob | |
| from datetime import datetime | |
| import sys | |
| import types | |
| # ๋ก๊น ์ค์ | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', | |
| handlers=[ | |
| logging.StreamHandler(), | |
| logging.FileHandler('main_keyword_app.log', mode='a') | |
| ] | |
| ) | |
| logger = logging.getLogger(__name__) | |
| # ํ๊ฒฝ๋ณ์์์ ๋ชจ๋ ์ฝ๋ ๋ก๋ ๋ฐ ๋์ ์์ฑ | |
| def load_module_from_env(module_name, env_var_name): | |
| """ํ๊ฒฝ๋ณ์์์ ๋ชจ๋ ์ฝ๋๋ฅผ ๋ก๋ํ์ฌ ๋์ ์ผ๋ก ๋ชจ๋ ์์ฑ""" | |
| try: | |
| module_code = os.getenv(env_var_name) | |
| if not module_code: | |
| raise ValueError(f"ํ๊ฒฝ๋ณ์ {env_var_name}๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค.") | |
| # ์ ๋ชจ๋ ์์ฑ | |
| module = types.ModuleType(module_name) | |
| # ๋ชจ๋์ ํ์ํ ๊ธฐ๋ณธ ์ํฌํธ๋ค ์ถ๊ฐ | |
| module.__dict__.update({ | |
| 'os': __import__('os'), | |
| 'time': __import__('time'), | |
| 'logging': __import__('logging'), | |
| 'pandas': __import__('pandas'), | |
| 'requests': __import__('requests'), | |
| 'tempfile': __import__('tempfile'), | |
| 'threading': __import__('threading'), | |
| 're': __import__('re'), | |
| 'random': __import__('random'), | |
| 'uuid': __import__('uuid'), | |
| 'shutil': __import__('shutil'), | |
| 'glob': __import__('glob'), | |
| 'datetime': __import__('datetime'), | |
| 'types': __import__('types'), | |
| 'collections': __import__('collections'), | |
| 'Counter': __import__('collections').Counter, | |
| 'defaultdict': __import__('collections').defaultdict, | |
| 'hmac': __import__('hmac'), | |
| 'hashlib': __import__('hashlib'), | |
| 'base64': __import__('base64'), | |
| }) | |
| # ์ฝ๋ ์คํ | |
| exec(module_code, module.__dict__) | |
| # ์์คํ ๋ชจ๋์ ๋ฑ๋ก | |
| sys.modules[module_name] = module | |
| logger.info(f"โ ๋ชจ๋ {module_name} ๋ก๋ ์๋ฃ") | |
| return module | |
| except Exception as e: | |
| logger.error(f"โ ๋ชจ๋ {module_name} ๋ก๋ ์คํจ: {e}") | |
| raise | |
| # ํ์ํ ๋ชจ๋๋ค์ ํ๊ฒฝ๋ณ์์์ ๋ก๋ | |
| logger.info("๐ ๋ชจ๋ ๋ก๋ ์์...") | |
| try: | |
| # 1. api_utils ๋ชจ๋ ๋ก๋ | |
| api_utils = load_module_from_env('api_utils', 'API_UTILS_CODE') | |
| # 2. text_utils ๋ชจ๋ ๋ก๋ (๋ค๋ฅธ ๋ชจ๋๋ค์ด ์์กดํ๋ฏ๋ก ๋จผ์ ๋ก๋) | |
| text_utils = load_module_from_env('text_utils', 'TEXT_UTILS_CODE') | |
| # 3. keyword_search ๋ชจ๋ ๋ก๋ | |
| keyword_search = load_module_from_env('keyword_search', 'KEYWORD_SEARCH_CODE') | |
| # 4. product_search ๋ชจ๋ ๋ก๋ (text_utils, keyword_search ์์กด) | |
| product_search_module = load_module_from_env('product_search', 'PRODUCT_SEARCH_CODE') | |
| # product_search ๋ชจ๋์ ์์กด์ฑ ์ฃผ์ | |
| product_search_module.api_utils = api_utils | |
| product_search_module.text_utils = text_utils | |
| product_search = product_search_module | |
| # 5. keyword_processor ๋ชจ๋ ๋ก๋ | |
| keyword_processor_module = load_module_from_env('keyword_processor', 'KEYWORD_PROCESSOR_CODE') | |
| # keyword_processor ๋ชจ๋์ ์์กด์ฑ ์ฃผ์ | |
| keyword_processor_module.text_utils = text_utils | |
| keyword_processor_module.keyword_search = keyword_search | |
| keyword_processor_module.product_search = product_search | |
| keyword_processor = keyword_processor_module | |
| # 6. export_utils ๋ชจ๋ ๋ก๋ | |
| export_utils = load_module_from_env('export_utils', 'EXPORT_UTILS_CODE') | |
| # 7. category_analysis ๋ชจ๋ ๋ก๋ (๋ชจ๋ ๋ชจ๋ ์์กด) | |
| category_analysis_module = load_module_from_env('category_analysis', 'CATEGORY_ANALYSIS_CODE') | |
| # category_analysis ๋ชจ๋์ ์์กด์ฑ ์ฃผ์ | |
| category_analysis_module.text_utils = text_utils | |
| category_analysis_module.product_search = product_search | |
| category_analysis_module.keyword_search = keyword_search | |
| category_analysis = category_analysis_module | |
| logger.info("โ ๋ชจ๋ ๋ชจ๋ ๋ก๋ ์๋ฃ") | |
| except Exception as e: | |
| logger.error(f"โ ๋ชจ๋ ๋ก๋ ์ค ์น๋ช ์ ์ค๋ฅ: {e}") | |
| logger.error("ํ์ํ ํ๊ฒฝ๋ณ์๋ค์ด ์ค์ ๋์๋์ง ํ์ธํ์ธ์:") | |
| logger.error("- API_UTILS_CODE") | |
| logger.error("- TEXT_UTILS_CODE") | |
| logger.error("- KEYWORD_SEARCH_CODE") | |
| logger.error("- PRODUCT_SEARCH_CODE") | |
| logger.error("- KEYWORD_PROCESSOR_CODE") | |
| logger.error("- EXPORT_UTILS_CODE") | |
| logger.error("- CATEGORY_ANALYSIS_CODE") | |
| raise | |
| # ์ธ์ ๋ณ ์์ ํ์ผ ๊ด๋ฆฌ๋ฅผ ์ํ ๋์ ๋๋ฆฌ | |
| session_temp_files = {} | |
| session_data = {} | |
| def cleanup_huggingface_temp_folders(): | |
| """ํ๊น ํ์ด์ค ์์ ํด๋ ์ด๊ธฐ ์ ๋ฆฌ""" | |
| try: | |
| # ์ผ๋ฐ์ ์ธ ์์ ๋๋ ํ ๋ฆฌ๋ค | |
| temp_dirs = [ | |
| tempfile.gettempdir(), | |
| "/tmp", | |
| "/var/tmp", | |
| os.path.join(os.getcwd(), "temp"), | |
| os.path.join(os.getcwd(), "tmp"), | |
| "/gradio_cached_examples", | |
| "/flagged" | |
| ] | |
| cleanup_count = 0 | |
| for temp_dir in temp_dirs: | |
| if os.path.exists(temp_dir): | |
| try: | |
| # ๊ธฐ์กด ์ธ์ ํ์ผ๋ค ์ ๋ฆฌ | |
| session_files = glob.glob(os.path.join(temp_dir, "session_*.xlsx")) | |
| session_files.extend(glob.glob(os.path.join(temp_dir, "session_*.csv"))) | |
| session_files.extend(glob.glob(os.path.join(temp_dir, "*keyword*.xlsx"))) | |
| session_files.extend(glob.glob(os.path.join(temp_dir, "*keyword*.csv"))) | |
| session_files.extend(glob.glob(os.path.join(temp_dir, "tmp*.xlsx"))) | |
| session_files.extend(glob.glob(os.path.join(temp_dir, "tmp*.csv"))) | |
| for file_path in session_files: | |
| try: | |
| # ํ์ผ์ด 1์๊ฐ ์ด์ ์ค๋๋ ๊ฒฝ์ฐ๋ง ์ญ์ | |
| if os.path.getmtime(file_path) < time.time() - 3600: | |
| os.remove(file_path) | |
| cleanup_count += 1 | |
| logger.info(f"์ด๊ธฐ ์ ๋ฆฌ: ์ค๋๋ ์์ ํ์ผ ์ญ์ - {file_path}") | |
| except Exception as e: | |
| logger.warning(f"ํ์ผ ์ญ์ ์คํจ (๋ฌด์๋จ): {file_path} - {e}") | |
| except Exception as e: | |
| logger.warning(f"์์ ๋๋ ํ ๋ฆฌ ์ ๋ฆฌ ์คํจ (๋ฌด์๋จ): {temp_dir} - {e}") | |
| logger.info(f"โ ํ๊น ํ์ด์ค ์์ ํด๋ ์ด๊ธฐ ์ ๋ฆฌ ์๋ฃ - {cleanup_count}๊ฐ ํ์ผ ์ญ์ ") | |
| # Gradio ์บ์ ํด๋๋ ์ ๋ฆฌ | |
| try: | |
| gradio_temp_dir = os.path.join(os.getcwd(), "gradio_cached_examples") | |
| if os.path.exists(gradio_temp_dir): | |
| shutil.rmtree(gradio_temp_dir, ignore_errors=True) | |
| logger.info("Gradio ์บ์ ํด๋ ์ ๋ฆฌ ์๋ฃ") | |
| except Exception as e: | |
| logger.warning(f"Gradio ์บ์ ํด๋ ์ ๋ฆฌ ์คํจ (๋ฌด์๋จ): {e}") | |
| except Exception as e: | |
| logger.error(f"์ด๊ธฐ ์์ ํด๋ ์ ๋ฆฌ ์ค ์ค๋ฅ (๊ณ์ ์งํ): {e}") | |
| def setup_clean_temp_environment(): | |
| """๊นจ๋ํ ์์ ํ๊ฒฝ ์ค์ """ | |
| try: | |
| # 1. ๊ธฐ์กด ์์ ํ์ผ๋ค ์ ๋ฆฌ | |
| cleanup_huggingface_temp_folders() | |
| # 2. ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฉ ์์ ๋๋ ํ ๋ฆฌ ์์ฑ | |
| app_temp_dir = os.path.join(tempfile.gettempdir(), "keyword_app") | |
| if os.path.exists(app_temp_dir): | |
| shutil.rmtree(app_temp_dir, ignore_errors=True) | |
| os.makedirs(app_temp_dir, exist_ok=True) | |
| # 3. ํ๊ฒฝ ๋ณ์ ์ค์ (์์ ๋๋ ํ ๋ฆฌ ์ง์ ) | |
| os.environ['KEYWORD_APP_TEMP'] = app_temp_dir | |
| logger.info(f"โ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฉ ์์ ๋๋ ํ ๋ฆฌ ์ค์ : {app_temp_dir}") | |
| return app_temp_dir | |
| except Exception as e: | |
| logger.error(f"์์ ํ๊ฒฝ ์ค์ ์คํจ: {e}") | |
| return tempfile.gettempdir() | |
| def get_app_temp_dir(): | |
| """์ ํ๋ฆฌ์ผ์ด์ ์ ์ฉ ์์ ๋๋ ํ ๋ฆฌ ๋ฐํ""" | |
| return os.environ.get('KEYWORD_APP_TEMP', tempfile.gettempdir()) | |
| def get_session_id(): | |
| """์ธ์ ID ์์ฑ""" | |
| return str(uuid.uuid4()) | |
| def cleanup_session_files(session_id, delay=300): | |
| """์ธ์ ๋ณ ์์ ํ์ผ ์ ๋ฆฌ ํจ์""" | |
| def cleanup(): | |
| time.sleep(delay) | |
| if session_id in session_temp_files: | |
| files_to_remove = session_temp_files[session_id].copy() | |
| del session_temp_files[session_id] | |
| for file_path in files_to_remove: | |
| try: | |
| if os.path.exists(file_path): | |
| os.remove(file_path) | |
| logger.info(f"์ธ์ {session_id[:8]}... ์์ ํ์ผ ์ญ์ : {file_path}") | |
| except Exception as e: | |
| logger.error(f"์ธ์ {session_id[:8]}... ํ์ผ ์ญ์ ์ค๋ฅ: {e}") | |
| threading.Thread(target=cleanup, daemon=True).start() | |
| def register_session_file(session_id, file_path): | |
| """์ธ์ ๋ณ ํ์ผ ๋ฑ๋ก""" | |
| if session_id not in session_temp_files: | |
| session_temp_files[session_id] = [] | |
| session_temp_files[session_id].append(file_path) | |
| def cleanup_old_sessions(): | |
| """์ค๋๋ ์ธ์ ๋ฐ์ดํฐ ์ ๋ฆฌ""" | |
| current_time = time.time() | |
| sessions_to_remove = [] | |
| for session_id, data in session_data.items(): | |
| if current_time - data.get('last_activity', 0) > 3600: # 1์๊ฐ ์ด๊ณผ | |
| sessions_to_remove.append(session_id) | |
| for session_id in sessions_to_remove: | |
| # ํ์ผ ์ ๋ฆฌ | |
| if session_id in session_temp_files: | |
| for file_path in session_temp_files[session_id]: | |
| try: | |
| if os.path.exists(file_path): | |
| os.remove(file_path) | |
| logger.info(f"์ค๋๋ ์ธ์ {session_id[:8]}... ํ์ผ ์ญ์ : {file_path}") | |
| except Exception as e: | |
| logger.error(f"์ค๋๋ ์ธ์ ํ์ผ ์ญ์ ์ค๋ฅ: {e}") | |
| del session_temp_files[session_id] | |
| # ์ธ์ ๋ฐ์ดํฐ ์ ๋ฆฌ | |
| if session_id in session_data: | |
| del session_data[session_id] | |
| logger.info(f"์ค๋๋ ์ธ์ ๋ฐ์ดํฐ ์ญ์ : {session_id[:8]}...") | |
| def update_session_activity(session_id): | |
| """์ธ์ ํ๋ ์๊ฐ ์ ๋ฐ์ดํธ""" | |
| if session_id not in session_data: | |
| session_data[session_id] = {} | |
| session_data[session_id]['last_activity'] = time.time() | |
| def create_session_temp_file(session_id, suffix='.xlsx'): | |
| """์ธ์ ๋ณ ์์ ํ์ผ ์์ฑ (์ ์ฉ ๋๋ ํ ๋ฆฌ ์ฌ์ฉ)""" | |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| random_suffix = str(random.randint(1000, 9999)) | |
| # ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฉ ์์ ๋๋ ํ ๋ฆฌ ์ฌ์ฉ | |
| temp_dir = get_app_temp_dir() | |
| filename = f"session_{session_id[:8]}_{timestamp}_{random_suffix}{suffix}" | |
| temp_file_path = os.path.join(temp_dir, filename) | |
| # ๋น ํ์ผ ์์ฑ | |
| with open(temp_file_path, 'w') as f: | |
| pass | |
| register_session_file(session_id, temp_file_path) | |
| return temp_file_path | |
| def wrapper_modified(keyword, korean_only, apply_main_keyword_option, exclude_zero_volume, session_id): | |
| """ํค์๋ ๊ฒ์ ๋ฐ ์ฒ๋ฆฌ ๋ํผ ํจ์ (์ธ์ ID ์ถ๊ฐ)""" | |
| update_session_activity(session_id) | |
| # ํ์ฌ ํค์๋ ์ฌ์ฉ (์ธ์ ๋ณ๋ก ๊ด๋ฆฌ) | |
| current_keyword = keyword | |
| # ํค์๋๊ฐ ๋น์ด์๋ ๊ฒฝ์ฐ ์ฒ๋ฆฌ | |
| if not keyword: | |
| return (gr.update(value=""), gr.update(choices=["์ ์ฒด ๋ณด๊ธฐ"]), gr.update(choices=["์ ์ฒด"]), | |
| None, gr.update(choices=["์ ์ฒด ๋ณด๊ธฐ"], value="์ ์ฒด ๋ณด๊ธฐ"), None, | |
| gr.update(visible=False), gr.update(visible=False), current_keyword) | |
| # ๋ค์ด๋ฒ ์ผํ API ๊ฒ์ ์ํ | |
| search_results = product_search.fetch_naver_shopping_data(keyword, korean_only, apply_main_keyword_option == "๋ฉ์ธํค์๋ ์ ์ฉ") | |
| # ๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์๋ ๊ฒฝ์ฐ | |
| if not search_results.get("product_list"): | |
| return (gr.update(value="<p>๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค. ๋ค๋ฅธ ํค์๋๋ก ์๋ํด๋ณด์ธ์.</p>"), | |
| gr.update(choices=["์ ์ฒด ๋ณด๊ธฐ"]), gr.update(choices=["์ ์ฒด"]), | |
| None, gr.update(choices=["์ ์ฒด ๋ณด๊ธฐ"], value="์ ์ฒด ๋ณด๊ธฐ"), None, | |
| gr.update(visible=False), gr.update(visible=False), current_keyword) | |
| # ๊ฒ์ ๊ฒฐ๊ณผ ์ฒ๋ฆฌ - ํค์๋ ์ ๋ฌ ๋ฐ ๊ฒ์๋ 0 ํค์๋ ์ ์ธ ์ต์ ์ ๋ฌ | |
| result = keyword_processor.process_search_results(search_results, current_keyword, exclude_zero_volume) | |
| df_products = result["products_df"] | |
| df_keywords = result["keywords_df"] | |
| category_list = result["categories"] | |
| if df_keywords.empty: | |
| return (gr.update(value="<p>์ถ์ถ๋ ํค์๋๊ฐ ์์ต๋๋ค. ๋ค๋ฅธ ์ต์ ์ผ๋ก ์๋ํด๋ณด์ธ์.</p>"), | |
| gr.update(choices=["์ ์ฒด ๋ณด๊ธฐ"]), gr.update(choices=["์ ์ฒด"]), | |
| df_keywords, gr.update(choices=["์ ์ฒด ๋ณด๊ธฐ"], value="์ ์ฒด ๋ณด๊ธฐ"), None, | |
| gr.update(visible=False), gr.update(visible=False), current_keyword) | |
| # HTML ํ ์ด๋ธ ์์ฑ | |
| html = export_utils.create_table_without_checkboxes(df_keywords) | |
| # ํํฐ๋ง์ ์ํ ๊ณ ์ ๊ฐ ๋ฆฌ์คํธ ์์ฑ | |
| volume_range_choices = ["์ ์ฒด"] + sorted(df_keywords["๊ฒ์๋๊ตฌ๊ฐ"].unique().tolist()) | |
| # ๋ถ์ํ ์นดํ ๊ณ ๋ฆฌ ๋๋กญ๋ค์ด๋ ๊ฐ์ ์ ํ์ง๋ก ์ ๋ฐ์ดํธ | |
| first_category = category_list[0] if category_list else "์ ์ฒด ๋ณด๊ธฐ" | |
| # ์ธ์ ๋ณ ์์ ํ์ผ ์์ฑ | |
| excel_path = create_session_excel_file(df_keywords, session_id) | |
| # ๋ถ์ ์น์ ํ์ | |
| return (gr.update(value=html), gr.update(choices=category_list), gr.update(choices=volume_range_choices), | |
| df_keywords, gr.update(choices=category_list, value=first_category), excel_path, | |
| gr.update(visible=True), gr.update(visible=True), current_keyword) | |
| def create_session_excel_file(df, session_id): | |
| """์ธ์ ๋ณ ์์ ํ์ผ ์์ฑ""" | |
| try: | |
| excel_path = create_session_temp_file(session_id, '.xlsx') | |
| df.to_excel(excel_path, index=False, engine='openpyxl') | |
| logger.info(f"์ธ์ {session_id[:8]}... ์์ ํ์ผ ์์ฑ: {excel_path}") | |
| return excel_path | |
| except Exception as e: | |
| logger.error(f"์ธ์ ๋ณ ์์ ํ์ผ ์์ฑ ์ค๋ฅ: {e}") | |
| return None | |
| def analyze_with_auto_download(analysis_keywords, selected_category, state_df, session_id): | |
| """์นดํ ๊ณ ๋ฆฌ ์ผ์น ๋ถ์ ์คํ ๋ฐ ์๋ ๋ค์ด๋ก๋ (์ธ์ ID ์ถ๊ฐ)""" | |
| update_session_activity(session_id) | |
| # ๋ถ์ํ ํค์๋๋ ์นดํ ๊ณ ๋ฆฌ๊ฐ ์๋ ๊ฒฝ์ฐ | |
| if not analysis_keywords or not selected_category: | |
| return "ํค์๋์ ์นดํ ๊ณ ๋ฆฌ๋ฅผ ๋ชจ๋ ์ ํํด์ฃผ์ธ์.", None, gr.update(visible=False) | |
| # ๋ถ์ ์คํ - ๋์ ๋ก๋ฉ๋ category_analysis ๋ชจ๋ ์ฌ์ฉ | |
| analysis_result = category_analysis.analyze_keywords_by_category(analysis_keywords, selected_category, state_df) | |
| # ์ธ์ ๋ณ ์์ ํ์ผ ์์ฑ | |
| excel_path = create_session_excel_file(state_df, session_id) | |
| # ๋ถ์ ๊ฒฐ๊ณผ ์ถ๋ ฅ ์น์ ํ์ | |
| return analysis_result, excel_path, gr.update(visible=True) | |
| def filter_and_sort_table(df, selected_cat, keyword_sort, total_volume_sort, usage_count_sort, selected_volume_range, exclude_zero_volume, session_id): | |
| """ํ ์ด๋ธ ํํฐ๋ง ๋ฐ ์ ๋ ฌ ํจ์ (์ธ์ ID ์ถ๊ฐ)""" | |
| update_session_activity(session_id) | |
| if df is None or df.empty: | |
| return "" | |
| # ํํฐ๋ง ์ ์ฉ | |
| filtered_df = df.copy() | |
| # ์นดํ ๊ณ ๋ฆฌ ํํฐ ์ ์ฉ | |
| if selected_cat and selected_cat != "์ ์ฒด ๋ณด๊ธฐ": | |
| cat_name_to_filter = selected_cat.rsplit(" (", 1)[0] | |
| filtered_df = filtered_df[filtered_df["๊ด๋ จ ์นดํ ๊ณ ๋ฆฌ"].astype(str).str.contains(cat_name_to_filter, case=False, na=False)] | |
| def get_filtered_category_display(current_categories_str): | |
| if pd.isna(current_categories_str): | |
| return "" | |
| categories = str(current_categories_str).split('\n') | |
| matched_categories = [cat for cat in categories if cat_name_to_filter.lower() in cat.lower()] | |
| if matched_categories: | |
| return "\n".join(matched_categories) | |
| return current_categories_str | |
| filtered_df['๊ด๋ จ ์นดํ ๊ณ ๋ฆฌ'] = filtered_df['๊ด๋ จ ์นดํ ๊ณ ๋ฆฌ'].apply(get_filtered_category_display) | |
| # ๊ฒ์๋ ๊ตฌ๊ฐ ํํฐ ์ ์ฉ | |
| if selected_volume_range and selected_volume_range != "์ ์ฒด": | |
| filtered_df = filtered_df[filtered_df["๊ฒ์๋๊ตฌ๊ฐ"] == selected_volume_range] | |
| # ๊ฒ์๋ 0 ์ ์ธ ํํฐ ์ ์ฉ | |
| if exclude_zero_volume: | |
| filtered_df = filtered_df[filtered_df["์ด๊ฒ์๋"] > 0] | |
| logger.info(f"์ธ์ {session_id[:8]}... ๊ฒ์๋ 0 ์ ์ธ ํํฐ ์ ์ฉ - ๋จ์ ํค์๋ ์: {len(filtered_df)}") | |
| # ์ ๋ ฌ ์ ์ฉ | |
| if keyword_sort != "์ ๋ ฌ ์์": | |
| is_ascending = keyword_sort == "์ค๋ฆ์ฐจ์" | |
| filtered_df = filtered_df.sort_values(by="์กฐํฉ ํค์๋", ascending=is_ascending) | |
| if total_volume_sort != "์ ๋ ฌ ์์": | |
| is_ascending = total_volume_sort == "์ค๋ฆ์ฐจ์" | |
| filtered_df = filtered_df.sort_values(by="์ด๊ฒ์๋", ascending=is_ascending) | |
| # ํค์๋ ์ฌ์ฉํ์ ์ ๋ ฌ ์ ์ฉ | |
| if usage_count_sort != "์ ๋ ฌ ์์": | |
| is_ascending = usage_count_sort == "์ค๋ฆ์ฐจ์" | |
| filtered_df = filtered_df.sort_values(by="ํค์๋ ์ฌ์ฉํ์", ascending=is_ascending) | |
| # ์๋ฒ์ 1๋ถํฐ ์์ฐจ์ ์ผ๋ก ์ ์งํ๊ธฐ ์ํด ํ ์ธ๋ฑ์ค ์ฌ์ค์ | |
| filtered_df = filtered_df.reset_index(drop=True) | |
| # ์๋ฒ์ ํฌํจํ HTML ํ ์ด๋ธ ์์ฑ | |
| html = export_utils.create_table_without_checkboxes(filtered_df) | |
| return html | |
| def update_category_selection(selected_cat, session_id): | |
| """์นดํ ๊ณ ๋ฆฌ ํํฐ ์ ํ ์ ๋ถ์ํ ์นดํ ๊ณ ๋ฆฌ๋ ๊ฐ์ ๊ฐ์ผ๋ก ์ ๋ฐ์ดํธ""" | |
| update_session_activity(session_id) | |
| logger.debug(f"์ธ์ {session_id[:8]}... ์นดํ ๊ณ ๋ฆฌ ์ ํ ๋ณ๊ฒฝ: {selected_cat}") | |
| return gr.update(value=selected_cat) | |
| def reset_interface(session_id): | |
| """์ธํฐํ์ด์ค ๋ฆฌ์ ํจ์ - ์ธ์ ๋ณ ๋ฐ์ดํฐ ์ด๊ธฐํ""" | |
| update_session_activity(session_id) | |
| # ์ธ์ ๋ณ ์์ ํ์ผ ์ ๋ฆฌ | |
| if session_id in session_temp_files: | |
| for file_path in session_temp_files[session_id]: | |
| try: | |
| if os.path.exists(file_path): | |
| os.remove(file_path) | |
| logger.info(f"์ธ์ {session_id[:8]}... ๋ฆฌ์ ์ ํ์ผ ์ญ์ : {file_path}") | |
| except Exception as e: | |
| logger.error(f"์ธ์ {session_id[:8]}... ๋ฆฌ์ ์ ํ์ผ ์ญ์ ์ค๋ฅ: {e}") | |
| session_temp_files[session_id] = [] | |
| return ( | |
| "", # ๊ฒ์ ํค์๋ | |
| True, # ํ๊ธ๋ง ์ถ์ถ | |
| False, # ๊ฒ์๋ 0 ํค์๋ ์ ์ธ | |
| "๋ฉ์ธํค์๋ ์ ์ฉ", # ์กฐํฉ ๋ฐฉ์ | |
| "", # HTML ํ ์ด๋ธ | |
| ["์ ์ฒด ๋ณด๊ธฐ"], # ์นดํ ๊ณ ๋ฆฌ ํํฐ | |
| "์ ์ฒด ๋ณด๊ธฐ", # ์นดํ ๊ณ ๋ฆฌ ํํฐ ์ ํ | |
| ["์ ์ฒด"], # ๊ฒ์๋ ๊ตฌ๊ฐ ํํฐ | |
| "์ ์ฒด", # ๊ฒ์๋ ๊ตฌ๊ฐ ์ ํ | |
| "์ ๋ ฌ ์์", # ์ด๊ฒ์๋ ์ ๋ ฌ | |
| "์ ๋ ฌ ์์", # ํค์๋ ์ฌ์ฉํ์ ์ ๋ ฌ | |
| None, # ์ํ DataFrame | |
| ["์ ์ฒด ๋ณด๊ธฐ"], # ๋ถ์ํ ์นดํ ๊ณ ๋ฆฌ | |
| "์ ์ฒด ๋ณด๊ธฐ", # ๋ถ์ํ ์นดํ ๊ณ ๋ฆฌ ์ ํ | |
| "", # ํค์๋ ์ ๋ ฅ | |
| "", # ๋ถ์ ๊ฒฐ๊ณผ | |
| None, # ๋ค์ด๋ก๋ ํ์ผ | |
| gr.update(visible=False), # ํค์๋ ๋ถ์ ์น์ | |
| gr.update(visible=False), # ๋ถ์ ๊ฒฐ๊ณผ ์ถ๋ ฅ ์น์ | |
| "" # ํค์๋ ์ํ | |
| ) | |
| # ๋ํผ ํจ์๋ค๋ ์ธ์ ID ์ถ๊ฐ | |
| def search_with_loading(keyword, korean_only, apply_main_keyword, exclude_zero_volume, session_id): | |
| update_session_activity(session_id) | |
| return ( | |
| gr.update(visible=True), | |
| gr.update(visible=False) | |
| ) | |
| def process_search_results(keyword, korean_only, apply_main_keyword, exclude_zero_volume, session_id): | |
| update_session_activity(session_id) | |
| result = wrapper_modified(keyword, korean_only, apply_main_keyword, exclude_zero_volume, session_id) | |
| table_html, cat_choices, vol_choices, df, selected_cat, excel, keyword_section_vis, cat_section_vis, new_keyword_state = result | |
| if not isinstance(df, type(None)) and not df.empty: | |
| empty_placeholder_vis = False | |
| keyword_section_visibility = True | |
| execution_section_visibility = True | |
| else: | |
| empty_placeholder_vis = True | |
| keyword_section_visibility = False | |
| execution_section_visibility = False | |
| return ( | |
| table_html, cat_choices, vol_choices, df, selected_cat, excel, | |
| gr.update(visible=keyword_section_visibility), | |
| gr.update(visible=cat_section_vis), | |
| gr.update(visible=False), | |
| gr.update(visible=empty_placeholder_vis), | |
| gr.update(visible=execution_section_visibility), | |
| new_keyword_state | |
| ) | |
| def analyze_with_loading(analysis_keywords, selected_category, state_df, session_id): | |
| update_session_activity(session_id) | |
| return gr.update(visible=True) | |
| def process_analyze_results(analysis_keywords, selected_category, state_df, session_id): | |
| update_session_activity(session_id) | |
| results = analyze_with_auto_download(analysis_keywords, selected_category, state_df, session_id) | |
| return results + (gr.update(visible=False),) | |
| # ์ธ์ ์ ๋ฆฌ ์ค์ผ์ค๋ฌ | |
| def start_session_cleanup_scheduler(): | |
| """์ธ์ ์ ๋ฆฌ ์ค์ผ์ค๋ฌ ์์""" | |
| def cleanup_scheduler(): | |
| while True: | |
| time.sleep(600) # 10๋ถ๋ง๋ค ์คํ | |
| cleanup_old_sessions() | |
| # ์ถ๊ฐ๋ก ํ๊น ํ์ด์ค ์์ ํด๋๋ ์ฃผ๊ธฐ์ ์ ๋ฆฌ | |
| cleanup_huggingface_temp_folders() | |
| threading.Thread(target=cleanup_scheduler, daemon=True).start() | |
| def cleanup_on_startup(): | |
| """์ ํ๋ฆฌ์ผ์ด์ ์์ ์ ์ ์ฒด ์ ๋ฆฌ""" | |
| logger.info("๐งน ์ ํ๋ฆฌ์ผ์ด์ ์์ - ์ด๊ธฐ ์ ๋ฆฌ ์์ ์์...") | |
| # 1. ํ๊น ํ์ด์ค ์์ ํด๋ ์ ๋ฆฌ | |
| cleanup_huggingface_temp_folders() | |
| # 2. ๊นจ๋ํ ์์ ํ๊ฒฝ ์ค์ | |
| app_temp_dir = setup_clean_temp_environment() | |
| # 3. ์ ์ญ ๋ณ์ ์ด๊ธฐํ | |
| global session_temp_files, session_data | |
| session_temp_files.clear() | |
| session_data.clear() | |
| logger.info(f"โ ์ด๊ธฐ ์ ๋ฆฌ ์์ ์๋ฃ - ์ฑ ์ ์ฉ ๋๋ ํ ๋ฆฌ: {app_temp_dir}") | |
| return app_temp_dir | |
| # Gradio ์ธํฐํ์ด์ค ์์ฑ | |
| def create_app(): | |
| fontawesome_html = """ | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css"> | |
| <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap"> | |
| """ | |
| # CSS ํ์ผ ๋ก๋ | |
| try: | |
| with open('style.css', 'r', encoding='utf-8') as f: | |
| custom_css = f.read() | |
| except: | |
| custom_css = """ | |
| :root { | |
| --primary-color: #FB7F0D; | |
| --secondary-color: #ff9a8b; | |
| } | |
| .custom-button { | |
| background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important; | |
| color: white !important; | |
| border-radius: 30px !important; | |
| height: 45px !important; | |
| font-size: 16px !important; | |
| font-weight: bold !important; | |
| width: 100% !important; | |
| text-align: center !important; | |
| display: flex !important; | |
| align-items: center !important; | |
| justify-content: center !important; | |
| } | |
| .reset-button { | |
| background: linear-gradient(135deg, #6c757d, #495057) !important; | |
| color: white !important; | |
| border-radius: 30px !important; | |
| height: 45px !important; | |
| font-size: 16px !important; | |
| font-weight: bold !important; | |
| width: 100% !important; | |
| text-align: center !important; | |
| display: flex !important; | |
| align-items: center !important; | |
| justify-content: center !important; | |
| } | |
| .section-title { | |
| border-bottom: 2px solid #FB7F0D; | |
| font-weight: bold; | |
| padding-bottom: 5px; | |
| margin-bottom: 15px; | |
| } | |
| .loading-indicator { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 15px; | |
| background-color: #f8f9fa; | |
| border-radius: 5px; | |
| margin: 10px 0; | |
| border: 1px solid #ddd; | |
| } | |
| .loading-spinner { | |
| border: 4px solid rgba(0, 0, 0, 0.1); | |
| width: 24px; | |
| height: 24px; | |
| border-radius: 50%; | |
| border-left-color: #FB7F0D; | |
| animation: spin 1s linear infinite; | |
| margin-right: 10px; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .progress-bar { | |
| height: 10px; | |
| background-color: #FB7F0D; | |
| border-radius: 5px; | |
| width: 0%; | |
| animation: progressAnim 2s ease-in-out infinite; | |
| } | |
| @keyframes progressAnim { | |
| 0% { width: 10%; } | |
| 50% { width: 70%; } | |
| 100% { width: 10%; } | |
| } | |
| .empty-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| font-size: 14px; | |
| margin-top: 20px; | |
| } | |
| .empty-table th { | |
| background-color: #FB7F0D; | |
| color: white; | |
| text-align: left; | |
| padding: 12px; | |
| border: 1px solid #ddd; | |
| } | |
| .empty-table td { | |
| padding: 10px; | |
| border: 1px solid #ddd; | |
| text-align: center; | |
| color: #999; | |
| } | |
| .button-container { | |
| margin-top: 20px; | |
| display: flex; | |
| gap: 15px; | |
| } | |
| .execution-section { | |
| margin-top: 20px; | |
| background-color: #f9f9f9; | |
| border-radius: 8px; | |
| padding: 15px; | |
| border: 1px solid #e5e5e5; | |
| } | |
| .session-info { | |
| background-color: #e8f4f8; | |
| padding: 8px 12px; | |
| border-radius: 4px; | |
| font-size: 12px; | |
| color: #0c5460; | |
| margin-bottom: 10px; | |
| text-align: center; | |
| } | |
| """ | |
| with gr.Blocks(css=custom_css, theme=gr.themes.Default( | |
| primary_hue="orange", | |
| secondary_hue="orange", | |
| font=[gr.themes.GoogleFont("Noto Sans KR"), "ui-sans-serif", "system-ui"] | |
| )) as demo: | |
| gr.HTML(fontawesome_html) | |
| # ์ธ์ ID ์ํ (๊ฐ ์ฌ์ฉ์๋ณ๋ก ๊ณ ์ ) | |
| session_id = gr.State(get_session_id) | |
| # ํค์๋ ์ํ ๊ด๋ฆฌ | |
| keyword_state = gr.State("") | |
| # ์ ๋ ฅ ์น์ | |
| with gr.Column(elem_classes="custom-frame fade-in"): | |
| gr.HTML('<div class="section-title"><i class="fas fa-search"></i> ๊ฒ์ ์ ๋ ฅ</div>') | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| keyword = gr.Textbox( | |
| label="๋ฉ์ธ ํค์๋", | |
| placeholder="์: ์ค์ง์ด" | |
| ) | |
| with gr.Column(scale=1): | |
| search_btn = gr.Button( | |
| "๋ฉ์ธํค์๋ ๋ถ์", | |
| elem_classes="custom-button" | |
| ) | |
| with gr.Accordion("์ต์ ์ค์ ", open=False): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| korean_only = gr.Checkbox( | |
| label="ํ๊ธ๋ง ์ถ์ถ", | |
| value=True | |
| ) | |
| with gr.Column(scale=1): | |
| exclude_zero_volume = gr.Checkbox( | |
| label="๊ฒ์๋ 0 ํค์๋ ์ ์ธ", | |
| value=False | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| apply_main_keyword = gr.Radio( | |
| ["๋ฉ์ธํค์๋ ์ ์ฉ", "๋ฉ์ธํค์๋ ๋ฏธ์ ์ฉ"], | |
| label="์กฐํฉ ๋ฐฉ์", | |
| value="๋ฉ์ธํค์๋ ์ ์ฉ" | |
| ) | |
| with gr.Column(scale=1): | |
| gr.HTML("") | |
| # ์งํ ์ํ ํ์ ์น์ | |
| with gr.Column(elem_classes="custom-frame fade-in", visible=False) as progress_section: | |
| gr.HTML('<div class="section-title"><i class="fas fa-spinner"></i> ๋ถ์ ์งํ ์ํ</div>') | |
| progress_html = gr.HTML(""" | |
| <div style="padding: 15px; background-color: #f9f9f9; border-radius: 5px; margin: 10px 0; border: 1px solid #ddd;"> | |
| <div style="margin-bottom: 10px; display: flex; align-items: center;"> | |
| <i class="fas fa-spinner fa-spin" style="color: #FB7F0D; margin-right: 10px;"></i> | |
| <span>ํค์๋ ๋ฐ์ดํฐ๋ฅผ ๋ถ์์ค์ ๋๋ค. ์ ์๋ง ๊ธฐ๋ค๋ ค์ฃผ์ธ์...</span> | |
| </div> | |
| <div style="background-color: #e9ecef; height: 10px; border-radius: 5px; overflow: hidden;"> | |
| <div class="progress-bar"></div> | |
| </div> | |
| </div> | |
| """) | |
| # ๋ฉ์ธํค์๋ ๋ถ์ ๊ฒฐ๊ณผ ์น์ | |
| with gr.Column(elem_classes="custom-frame fade-in") as main_keyword_section: | |
| gr.HTML('<div class="section-title"><i class="fas fa-table"></i> ๋ฉ์ธํค์๋ ๋ถ์ ๊ฒฐ๊ณผ</div>') | |
| empty_table_html = gr.HTML(""" | |
| <table class="empty-table"> | |
| <thead> | |
| <tr> | |
| <th>์๋ฒ</th> | |
| <th>์กฐํฉ ํค์๋</th> | |
| <th>PC๊ฒ์๋</th> | |
| <th>๋ชจ๋ฐ์ผ๊ฒ์๋</th> | |
| <th>์ด๊ฒ์๋</th> | |
| <th>๊ฒ์๋๊ตฌ๊ฐ</th> | |
| <th>ํค์๋ ์ฌ์ฉ์์์</th> | |
| <th>ํค์๋ ์ฌ์ฉํ์</th> | |
| <th>์ํ ๋ฑ๋ก ์นดํ ๊ณ ๋ฆฌ</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td colspan="9" style="padding: 30px; text-align: center;"> | |
| ๊ฒ์์ ์คํํ๋ฉด ์ฌ๊ธฐ์ ๊ฒฐ๊ณผ๊ฐ ํ์๋ฉ๋๋ค | |
| </td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| """) | |
| with gr.Column(visible=False) as keyword_analysis_section: | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| category_filter = gr.Dropdown( | |
| choices=["์ ์ฒด ๋ณด๊ธฐ"], | |
| label="์นดํ ๊ณ ๋ฆฌ ํํฐ", | |
| value="์ ์ฒด ๋ณด๊ธฐ", | |
| interactive=True | |
| ) | |
| with gr.Column(scale=1): | |
| total_volume_sort = gr.Dropdown( | |
| choices=["์ ๋ ฌ ์์", "์ค๋ฆ์ฐจ์", "๋ด๋ฆผ์ฐจ์"], | |
| label="์ด๊ฒ์๋ ์ ๋ ฌ", | |
| value="์ ๋ ฌ ์์", | |
| interactive=True | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| search_volume_filter = gr.Dropdown( | |
| choices=["์ ์ฒด"], | |
| label="๊ฒ์๋ ๊ตฌ๊ฐ ํํฐ", | |
| value="์ ์ฒด", | |
| interactive=True | |
| ) | |
| with gr.Column(scale=1): | |
| usage_count_sort = gr.Dropdown( | |
| choices=["์ ๋ ฌ ์์", "์ค๋ฆ์ฐจ์", "๋ด๋ฆผ์ฐจ์"], | |
| label="ํค์๋ ์ฌ์ฉํ์ ์ ๋ ฌ", | |
| value="์ ๋ ฌ ์์", | |
| interactive=True | |
| ) | |
| gr.HTML("<div class='data-container' id='table_container'></div>") | |
| table_output = gr.HTML(elem_classes="fade-in") | |
| # ์นดํ ๊ณ ๋ฆฌ ๋ถ์ ์น์ | |
| with gr.Column(elem_classes="custom-frame fade-in", visible=False) as category_analysis_section: | |
| gr.HTML('<div class="section-title"><i class="fas fa-chart-bar"></i> ํค์๋ ๋ถ์</div>') | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| analysis_keywords = gr.Textbox( | |
| label="ํค์๋ ์ ๋ ฅ (์ต๋ 20๊ฐ, ์ผํ ๋๋ ์ํฐ๋ก ๊ตฌ๋ถ)", | |
| placeholder="์: ์ค์ง์ด๋ณถ์, ์ค์ง์ด ์์ง, ์ค์ง์ด ์๋ฆฌ...", | |
| lines=5 | |
| ) | |
| with gr.Column(scale=1): | |
| selected_category = gr.Dropdown( | |
| label="๋ถ์ํ ์นดํ ๊ณ ๋ฆฌ(๋ถ์ ์ ๋ฐ๋์ ์ ํํด์ฃผ์ธ์)", | |
| choices=["์ ์ฒด ๋ณด๊ธฐ"], | |
| value="์ ์ฒด ๋ณด๊ธฐ", | |
| interactive=True | |
| ) | |
| # ์คํ ์น์ | |
| with gr.Column(elem_classes="execution-section", visible=False) as execution_section: | |
| gr.HTML('<div class="section-title"><i class="fas fa-play-circle"></i> ์คํ</div>') | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| analyze_btn = gr.Button( | |
| "์นดํ ๊ณ ๋ฆฌ ์ผ์น ๋ถ์", | |
| elem_classes=["execution-button", "primary-button"] | |
| ) | |
| with gr.Column(scale=1): | |
| reset_btn = gr.Button( | |
| "๋ชจ๋ ์ ๋ ฅ ์ด๊ธฐํ", | |
| elem_classes=["execution-button", "secondary-button"] | |
| ) | |
| # ๋ถ์ ๊ฒฐ๊ณผ ์ถ๋ ฅ ์น์ | |
| with gr.Column(elem_classes="custom-frame fade-in", visible=False) as analysis_output_section: | |
| gr.HTML('<div class="section-title"><i class="fas fa-list-ul"></i> ๋ถ์ ๊ฒฐ๊ณผ ์์ฝ</div>') | |
| analysis_result = gr.HTML(elem_classes="fade-in") | |
| with gr.Row(): | |
| download_output = gr.File( | |
| label="ํค์๋ ๋ชฉ๋ก ๋ค์ด๋ก๋", | |
| visible=True | |
| ) | |
| # ์ํ ์ ์ฅ์ฉ ๋ณ์ | |
| state_df = gr.State() | |
| # ์ด๋ฒคํธ ์ฐ๊ฒฐ - ๋ชจ๋ ํจ์์ session_id ์ถ๊ฐ | |
| search_btn.click( | |
| fn=search_with_loading, | |
| inputs=[keyword, korean_only, apply_main_keyword, exclude_zero_volume, session_id], | |
| outputs=[progress_section, empty_table_html] | |
| ).then( | |
| fn=process_search_results, | |
| inputs=[keyword, korean_only, apply_main_keyword, exclude_zero_volume, session_id], | |
| outputs=[ | |
| table_output, category_filter, search_volume_filter, | |
| state_df, selected_category, download_output, | |
| keyword_analysis_section, category_analysis_section, | |
| progress_section, empty_table_html, execution_section, | |
| keyword_state | |
| ] | |
| ) | |
| # ํํฐ ๋ฐ ์ ๋ ฌ ๋ณ๊ฒฝ ์ด๋ฒคํธ ์ฐ๊ฒฐ - session_id ์ถ๊ฐ | |
| category_filter.change( | |
| fn=filter_and_sort_table, | |
| inputs=[ | |
| state_df, category_filter, gr.Textbox(value="์ ๋ ฌ ์์", visible=False), | |
| total_volume_sort, usage_count_sort, | |
| search_volume_filter, exclude_zero_volume, session_id | |
| ], | |
| outputs=[table_output] | |
| ) | |
| category_filter.change( | |
| fn=update_category_selection, | |
| inputs=[category_filter, session_id], | |
| outputs=[selected_category] | |
| ) | |
| total_volume_sort.change( | |
| fn=filter_and_sort_table, | |
| inputs=[ | |
| state_df, category_filter, gr.Textbox(value="์ ๋ ฌ ์์", visible=False), | |
| total_volume_sort, usage_count_sort, | |
| search_volume_filter, exclude_zero_volume, session_id | |
| ], | |
| outputs=[table_output] | |
| ) | |
| usage_count_sort.change( | |
| fn=filter_and_sort_table, | |
| inputs=[ | |
| state_df, category_filter, gr.Textbox(value="์ ๋ ฌ ์์", visible=False), | |
| total_volume_sort, usage_count_sort, | |
| search_volume_filter, exclude_zero_volume, session_id | |
| ], | |
| outputs=[table_output] | |
| ) | |
| search_volume_filter.change( | |
| fn=filter_and_sort_table, | |
| inputs=[ | |
| state_df, category_filter, gr.Textbox(value="์ ๋ ฌ ์์", visible=False), | |
| total_volume_sort, usage_count_sort, | |
| search_volume_filter, exclude_zero_volume, session_id | |
| ], | |
| outputs=[table_output] | |
| ) | |
| exclude_zero_volume.change( | |
| fn=filter_and_sort_table, | |
| inputs=[ | |
| state_df, category_filter, gr.Textbox(value="์ ๋ ฌ ์์", visible=False), | |
| total_volume_sort, usage_count_sort, | |
| search_volume_filter, exclude_zero_volume, session_id | |
| ], | |
| outputs=[table_output] | |
| ) | |
| # ์นดํ ๊ณ ๋ฆฌ ๋ถ์ ๋ฒํผ ์ด๋ฒคํธ - session_id ์ถ๊ฐ | |
| analyze_btn.click( | |
| fn=analyze_with_loading, | |
| inputs=[analysis_keywords, selected_category, state_df, session_id], | |
| outputs=[progress_section] | |
| ).then( | |
| fn=process_analyze_results, | |
| inputs=[analysis_keywords, selected_category, state_df, session_id], | |
| outputs=[analysis_result, download_output, analysis_output_section, progress_section] | |
| ) | |
| # ๋ฆฌ์ ๋ฒํผ ์ด๋ฒคํธ ์ฐ๊ฒฐ - session_id ์ถ๊ฐ | |
| reset_btn.click( | |
| fn=reset_interface, | |
| inputs=[session_id], | |
| outputs=[ | |
| keyword, korean_only, exclude_zero_volume, apply_main_keyword, | |
| table_output, category_filter, category_filter, | |
| search_volume_filter, search_volume_filter, | |
| total_volume_sort, usage_count_sort, | |
| state_df, selected_category, selected_category, | |
| analysis_keywords, analysis_result, download_output, | |
| keyword_analysis_section, analysis_output_section, | |
| keyword_state | |
| ] | |
| ) | |
| return demo | |
| if __name__ == "__main__": | |
| # ========== ์์ ์ ์ ์ฒด ์ด๊ธฐํ ========== | |
| logger.info("๐ ๋ฉ์ธํค์๋ ๋ถ์ ์ ํ๋ฆฌ์ผ์ด์ ์์...") | |
| # 1. ์ฒซ ๋ฒ์งธ: ํ๊น ํ์ด์ค ์์ ํด๋ ์ ๋ฆฌ ๋ฐ ํ๊ฒฝ ์ค์ | |
| app_temp_dir = cleanup_on_startup() | |
| # 2. ์ธ์ ์ ๋ฆฌ ์ค์ผ์ค๋ฌ ์์ | |
| start_session_cleanup_scheduler() | |
| # 3. API ์ค์ ์ด๊ธฐํ | |
| try: | |
| api_utils.initialize_api_configs() | |
| except Exception as e: | |
| logger.warning(f"API ์ค์ ์ด๊ธฐํ ์ค ์ค๋ฅ (๊ณ์ ์งํ): {e}") | |
| # 4. Gemini ๋ชจ๋ธ ์ด๊ธฐํ | |
| try: | |
| gemini_model = text_utils.get_gemini_model() | |
| except Exception as e: | |
| logger.warning(f"Gemini ๋ชจ๋ธ ์ด๊ธฐํ ์ค ์ค๋ฅ (๊ณ์ ์งํ): {e}") | |
| logger.info("===== ๋ฉํฐ์ ์ ๋ฉ์ธํค์๋ ๋ถ์ Application Startup at %s =====", time.strftime("%Y-%m-%d %H:%M:%S")) | |
| logger.info(f"๐ ์์ ํ์ผ ์ ์ฅ ์์น: {app_temp_dir}") | |
| # ========== ์ฑ ์คํ ========== | |
| try: | |
| app = create_app() | |
| app.launch( | |
| share=False, # ๋ณด์์ ์ํด share ๋นํ์ฑํ | |
| server_name="0.0.0.0", # ๋ชจ๋ IP์์ ์ ๊ทผ ํ์ฉ | |
| server_port=7860, # ํฌํธ ์ง์ | |
| max_threads=40, # ๋ฉํฐ์ ์ ๋ฅผ ์ํ ์ค๋ ๋ ์ ์ฆ๊ฐ | |
| auth=None, # ํ์์ ์ธ์ฆ ์ถ๊ฐ ๊ฐ๋ฅ | |
| show_error=True, # ์๋ฌ ํ์ | |
| quiet=False, # ๋ก๊ทธ ํ์ | |
| favicon_path=None, # ํ๋น์ฝ ์ค์ | |
| ssl_verify=False # SSL ๊ฒ์ฆ ๋นํ์ฑํ (๊ฐ๋ฐ์ฉ) | |
| ) | |
| except Exception as e: | |
| logger.error(f"์ ํ๋ฆฌ์ผ์ด์ ์คํ ์คํจ: {e}") | |
| raise | |
| finally: | |
| # ์ ํ๋ฆฌ์ผ์ด์ ์ข ๋ฃ ์ ์ ๋ฆฌ | |
| logger.info("๐งน ์ ํ๋ฆฌ์ผ์ด์ ์ข ๋ฃ - ์ต์ข ์ ๋ฆฌ ์์ ...") | |
| try: | |
| cleanup_huggingface_temp_folders() | |
| if os.path.exists(app_temp_dir): | |
| shutil.rmtree(app_temp_dir, ignore_errors=True) | |
| logger.info("โ ์ต์ข ์ ๋ฆฌ ์๋ฃ") | |
| except Exception as e: | |
| logger.error(f"์ต์ข ์ ๋ฆฌ ์ค ์ค๋ฅ: {e}") |