| 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 requests |
| import json |
| from dotenv import load_dotenv |
|
|
| |
| load_dotenv() |
|
|
| |
| logging.basicConfig(level=logging.WARNING) |
| logger = logging.getLogger(__name__) |
|
|
| |
| session_temp_files = {} |
| session_data = {} |
|
|
| def get_api_client(): |
| """ํ๊ฒฝ๋ณ์์์ API ์๋ํฌ์ธํธ๋ฅผ ๊ฐ์ ธ์ ์์ฒญ ํจ์ ์์ฑ""" |
| endpoint = os.getenv('API_ENDPOINT') |
| if not endpoint: |
| raise ValueError("API_ENDPOINT ํ๊ฒฝ๋ณ์๊ฐ ํ์ํฉ๋๋ค.") |
| |
| def make_request(api_name, **kwargs): |
| try: |
| |
| if not endpoint.startswith('http'): |
| base_url = f"https://{endpoint}.hf.space" |
| else: |
| base_url = endpoint |
| |
| |
| url = f"{base_url}/call{api_name}" |
| |
| |
| if api_name == "/process_search_results": |
| data = [kwargs.get('keyword', ''), kwargs.get('korean_only', True), |
| kwargs.get('apply_main_keyword', '๋ฉ์ธํค์๋ ์ ์ฉ'), kwargs.get('exclude_zero_volume', False)] |
| elif api_name == "/search_with_loading": |
| data = [kwargs.get('keyword', ''), kwargs.get('korean_only', True), |
| kwargs.get('apply_main_keyword', '๋ฉ์ธํค์๋ ์ ์ฉ'), kwargs.get('exclude_zero_volume', False)] |
| elif api_name == "/filter_and_sort_table": |
| data = [kwargs.get('selected_cat', '์ ์ฒด ๋ณด๊ธฐ'), kwargs.get('keyword_sort', '์ ๋ ฌ ์์'), |
| kwargs.get('total_volume_sort', '์ ๋ ฌ ์์'), kwargs.get('usage_count_sort', '์ ๋ ฌ ์์'), |
| kwargs.get('selected_volume_range', '์ ์ฒด'), kwargs.get('exclude_zero_volume', False)] |
| elif api_name == "/update_category_selection": |
| data = [kwargs.get('selected_cat', '์ ์ฒด ๋ณด๊ธฐ')] |
| elif api_name == "/process_analyze_results": |
| data = [kwargs.get('analysis_keywords', ''), kwargs.get('selected_category', '์ ์ฒด ๋ณด๊ธฐ')] |
| elif api_name == "/analyze_with_loading": |
| data = [kwargs.get('analysis_keywords', ''), kwargs.get('selected_category', '์ ์ฒด ๋ณด๊ธฐ')] |
| elif api_name == "/reset_interface": |
| data = [] |
| elif api_name == "/get_session_id": |
| data = [] |
| else: |
| data = [] |
| |
| response = requests.post(url, json={"data": data}, timeout=60) |
| |
| if response.status_code == 200: |
| result = response.json() |
| return result.get('data', []) |
| else: |
| raise Exception(f"API ํธ์ถ ์คํจ: {response.status_code}") |
| |
| except Exception as e: |
| raise Exception(f"API ์ฐ๊ฒฐ ์ค๋ฅ: {str(e)}") |
| |
| return type('APIClient', (), {'predict': lambda self, **kwargs: make_request(kwargs.pop('api_name'), **kwargs)})() |
|
|
| def cleanup_huggingface_temp_folders(): |
| """ํ๊น
ํ์ด์ค ์์ ํด๋ ์ด๊ธฐ ์ ๋ฆฌ""" |
| try: |
| temp_dirs = [tempfile.gettempdir(), "/tmp", "/var/tmp"] |
| 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"))) |
| |
| for file_path in session_files: |
| try: |
| if os.path.getmtime(file_path) < time.time() - 3600: |
| os.remove(file_path) |
| cleanup_count += 1 |
| except Exception: |
| pass |
| except Exception: |
| pass |
| |
| logger.info(f"โ
์์ ํด๋ ์ ๋ฆฌ ์๋ฃ - {cleanup_count}๊ฐ ํ์ผ ์ญ์ ") |
| except Exception as e: |
| logger.error(f"์์ ํด๋ ์ ๋ฆฌ ์ค ์ค๋ฅ: {e}") |
|
|
| def setup_clean_temp_environment(): |
| """๊นจ๋ํ ์์ ํ๊ฒฝ ์ค์ """ |
| try: |
| cleanup_huggingface_temp_folders() |
| |
| app_temp_dir = os.path.join(tempfile.gettempdir(), "control_tower_app") |
| if os.path.exists(app_temp_dir): |
| shutil.rmtree(app_temp_dir, ignore_errors=True) |
| os.makedirs(app_temp_dir, exist_ok=True) |
| |
| os.environ['CONTROL_TOWER_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('CONTROL_TOWER_TEMP', tempfile.gettempdir()) |
|
|
| def get_session_id(): |
| """์ธ์
ID ์์ฑ""" |
| try: |
| client = get_api_client() |
| result = client.predict(api_name="/get_session_id") |
| return result[0] if result else str(uuid.uuid4()) |
| except Exception: |
| 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: |
| 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 search_with_loading(keyword, korean_only, apply_main_keyword, exclude_zero_volume): |
| """์๋ณธ API: /search_with_loading""" |
| try: |
| client = get_api_client() |
| result = client.predict( |
| keyword=keyword, |
| korean_only=korean_only, |
| apply_main_keyword=apply_main_keyword, |
| exclude_zero_volume=exclude_zero_volume, |
| api_name="/search_with_loading" |
| ) |
| return result[0] if result else "" |
| except Exception as e: |
| logger.error(f"search_with_loading API ํธ์ถ ์ค๋ฅ: {e}") |
| return "" |
|
|
| def process_search_results(keyword, korean_only, apply_main_keyword, exclude_zero_volume): |
| """์๋ณธ API: /process_search_results""" |
| try: |
| client = get_api_client() |
| result = client.predict( |
| keyword=keyword, |
| korean_only=korean_only, |
| apply_main_keyword=apply_main_keyword, |
| exclude_zero_volume=exclude_zero_volume, |
| api_name="/process_search_results" |
| ) |
| |
| |
| if len(result) >= 5: |
| table_html, cat_choices, vol_choices, selected_cat, download_file = result[:5] |
| |
| |
| local_download_file = None |
| if download_file: |
| session_id = get_session_id() |
| local_download_file = create_session_temp_file(session_id, '.xlsx') |
| try: |
| shutil.copy2(download_file, local_download_file) |
| except Exception as e: |
| logger.error(f"ํ์ผ ๋ณต์ฌ ์ค๋ฅ: {e}") |
| local_download_file = None |
| |
| return table_html, cat_choices, vol_choices, selected_cat, local_download_file |
| else: |
| return ( |
| "<p>๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค.</p>", |
| ["์ ์ฒด ๋ณด๊ธฐ"], ["์ ์ฒด"], "์ ์ฒด ๋ณด๊ธฐ", None |
| ) |
| |
| except Exception as e: |
| logger.error(f"process_search_results API ํธ์ถ ์ค๋ฅ: {e}") |
| return ( |
| "<p>์๋น์ค ์ฐ๊ฒฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.</p>", |
| ["์ ์ฒด ๋ณด๊ธฐ"], ["์ ์ฒด"], "์ ์ฒด ๋ณด๊ธฐ", None |
| ) |
|
|
| def filter_and_sort_table(selected_cat, keyword_sort, total_volume_sort, usage_count_sort, selected_volume_range, exclude_zero_volume): |
| """์๋ณธ API: /filter_and_sort_table""" |
| try: |
| client = get_api_client() |
| result = client.predict( |
| selected_cat=selected_cat, |
| keyword_sort=keyword_sort, |
| total_volume_sort=total_volume_sort, |
| usage_count_sort=usage_count_sort, |
| selected_volume_range=selected_volume_range, |
| exclude_zero_volume=exclude_zero_volume, |
| api_name="/filter_and_sort_table" |
| ) |
| return result[0] if result else "" |
| except Exception as e: |
| logger.error(f"filter_and_sort_table API ํธ์ถ ์ค๋ฅ: {e}") |
| return "<p>ํํฐ๋ง ์๋น์ค ์ฐ๊ฒฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค.</p>" |
|
|
| def update_category_selection(selected_cat): |
| """์๋ณธ API: /update_category_selection""" |
| try: |
| client = get_api_client() |
| result = client.predict( |
| selected_cat=selected_cat, |
| api_name="/update_category_selection" |
| ) |
| return gr.update(value=result[0] if result else selected_cat) |
| except Exception as e: |
| logger.error(f"update_category_selection API ํธ์ถ ์ค๋ฅ: {e}") |
| return gr.update(value=selected_cat) |
|
|
| def analyze_with_loading(analysis_keywords, selected_category): |
| """์๋ณธ API: /analyze_with_loading""" |
| try: |
| client = get_api_client() |
| result = client.predict( |
| analysis_keywords=analysis_keywords, |
| selected_category=selected_category, |
| api_name="/analyze_with_loading" |
| ) |
| return result[0] if result else "" |
| except Exception as e: |
| logger.error(f"analyze_with_loading API ํธ์ถ ์ค๋ฅ: {e}") |
| return "" |
|
|
| def process_analyze_results(analysis_keywords, selected_category): |
| """์๋ณธ API: /process_analyze_results""" |
| try: |
| client = get_api_client() |
| result = client.predict( |
| analysis_keywords=analysis_keywords, |
| selected_category=selected_category, |
| api_name="/process_analyze_results" |
| ) |
| |
| if len(result) >= 2: |
| analysis_result, download_file = result[:2] |
| |
| |
| local_download_file = None |
| if download_file: |
| session_id = get_session_id() |
| local_download_file = create_session_temp_file(session_id, '.xlsx') |
| try: |
| shutil.copy2(download_file, local_download_file) |
| except Exception as e: |
| logger.error(f"๋ถ์ ๊ฒฐ๊ณผ ํ์ผ ๋ณต์ฌ ์ค๋ฅ: {e}") |
| local_download_file = None |
| |
| return analysis_result, local_download_file |
| else: |
| return "๋ถ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค.", None |
| |
| except Exception as e: |
| logger.error(f"process_analyze_results API ํธ์ถ ์ค๋ฅ: {e}") |
| return "๋ถ์ ์๋น์ค ์ฐ๊ฒฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.", None |
|
|
| def reset_interface(): |
| """์๋ณธ API: /reset_interface""" |
| try: |
| client = get_api_client() |
| result = client.predict(api_name="/reset_interface") |
| return result if result else get_default_reset_values() |
| except Exception as e: |
| logger.error(f"reset_interface API ํธ์ถ ์ค๋ฅ: {e}") |
| return get_default_reset_values() |
|
|
| def get_default_reset_values(): |
| """๊ธฐ๋ณธ ๋ฆฌ์
๊ฐ ๋ฐํ""" |
| return ( |
| "", True, False, "๋ฉ์ธํค์๋ ์ ์ฉ", "", ["์ ์ฒด ๋ณด๊ธฐ"], "์ ์ฒด ๋ณด๊ธฐ", |
| ["์ ์ฒด"], "์ ์ฒด", "์ ๋ ฌ ์์", "์ ๋ ฌ ์์", ["์ ์ฒด ๋ณด๊ธฐ"], "์ ์ฒด ๋ณด๊ธฐ", |
| "", "", None |
| ) |
|
|
| |
| def wrapper_search_with_loading(keyword, korean_only, apply_main_keyword, exclude_zero_volume): |
| """๊ฒ์ ๋ก๋ฉ UI ์ฒ๋ฆฌ""" |
| search_with_loading(keyword, korean_only, apply_main_keyword, exclude_zero_volume) |
| return ( |
| gr.update(visible=True), |
| gr.update(visible=False) |
| ) |
|
|
| def wrapper_process_search_results(keyword, korean_only, apply_main_keyword, exclude_zero_volume): |
| """๊ฒ์ ๊ฒฐ๊ณผ ์ฒ๋ฆฌ UI""" |
| result = process_search_results(keyword, korean_only, apply_main_keyword, exclude_zero_volume) |
| |
| table_html, cat_choices, vol_choices, selected_cat, download_file = result |
| |
| |
| if table_html and "๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค" not in table_html and "๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค" not in table_html: |
| keyword_section_visibility = True |
| category_section_visibility = True |
| empty_placeholder_vis = False |
| execution_section_visibility = True |
| else: |
| keyword_section_visibility = False |
| category_section_visibility = False |
| empty_placeholder_vis = True |
| execution_section_visibility = False |
| |
| |
| state_df = pd.DataFrame() |
| |
| return ( |
| table_html, |
| cat_choices, |
| vol_choices, |
| state_df, |
| selected_cat, |
| download_file, |
| gr.update(visible=keyword_section_visibility), |
| gr.update(visible=category_section_visibility), |
| gr.update(visible=False), |
| gr.update(visible=empty_placeholder_vis), |
| gr.update(visible=execution_section_visibility), |
| keyword |
| ) |
|
|
| def wrapper_analyze_with_loading(analysis_keywords, selected_category, state_df): |
| """๋ถ์ ๋ก๋ฉ UI ์ฒ๋ฆฌ""" |
| analyze_with_loading(analysis_keywords, selected_category) |
| return gr.update(visible=True) |
|
|
| def wrapper_process_analyze_results(analysis_keywords, selected_category, state_df): |
| """๋ถ์ ๊ฒฐ๊ณผ ์ฒ๋ฆฌ UI""" |
| analysis_result, download_file = process_analyze_results(analysis_keywords, selected_category) |
| return ( |
| analysis_result, |
| download_file, |
| gr.update(visible=True), |
| gr.update(visible=False) |
| ) |
|
|
| |
| def start_session_cleanup_scheduler(): |
| """์ธ์
์ ๋ฆฌ ์ค์ผ์ค๋ฌ ์์""" |
| def cleanup_scheduler(): |
| while True: |
| time.sleep(600) |
| cleanup_old_sessions() |
| cleanup_huggingface_temp_folders() |
| |
| threading.Thread(target=cleanup_scheduler, daemon=True).start() |
|
|
| def cleanup_on_startup(): |
| """์ ํ๋ฆฌ์ผ์ด์
์์ ์ ์ ์ฒด ์ ๋ฆฌ""" |
| logger.info("๐งน ์ปจํธ๋กค ํ์ ์ ํ๋ฆฌ์ผ์ด์
์์ - ์ด๊ธฐ ์ ๋ฆฌ ์์
์์...") |
| |
| cleanup_huggingface_temp_folders() |
| app_temp_dir = setup_clean_temp_environment() |
| |
| global session_temp_files, session_data |
| session_temp_files.clear() |
| session_data.clear() |
| |
| logger.info(f"โ
์ด๊ธฐ ์ ๋ฆฌ ์์
์๋ฃ - ์ฑ ์ ์ฉ ๋๋ ํ ๋ฆฌ: {app_temp_dir}") |
| return app_temp_dir |
|
|
| |
| 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"> |
| """ |
| |
| |
| 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; |
| } |
| """ |
| |
| 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) |
| |
| |
| 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() |
| |
| |
| search_btn.click( |
| fn=wrapper_search_with_loading, |
| inputs=[keyword, korean_only, apply_main_keyword, exclude_zero_volume], |
| outputs=[progress_section, empty_table_html] |
| ).then( |
| fn=wrapper_process_search_results, |
| inputs=[keyword, korean_only, apply_main_keyword, exclude_zero_volume], |
| 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 |
| ] |
| ) |
| |
| |
| category_filter.change( |
| fn=filter_and_sort_table, |
| inputs=[ |
| category_filter, gr.Textbox(value="์ ๋ ฌ ์์", visible=False), |
| total_volume_sort, usage_count_sort, |
| search_volume_filter, exclude_zero_volume |
| ], |
| outputs=[table_output] |
| ) |
| |
| category_filter.change( |
| fn=update_category_selection, |
| inputs=[category_filter], |
| outputs=[selected_category] |
| ) |
| |
| total_volume_sort.change( |
| fn=filter_and_sort_table, |
| inputs=[ |
| category_filter, gr.Textbox(value="์ ๋ ฌ ์์", visible=False), |
| total_volume_sort, usage_count_sort, |
| search_volume_filter, exclude_zero_volume |
| ], |
| outputs=[table_output] |
| ) |
| |
| usage_count_sort.change( |
| fn=filter_and_sort_table, |
| inputs=[ |
| category_filter, gr.Textbox(value="์ ๋ ฌ ์์", visible=False), |
| total_volume_sort, usage_count_sort, |
| search_volume_filter, exclude_zero_volume |
| ], |
| outputs=[table_output] |
| ) |
| |
| search_volume_filter.change( |
| fn=filter_and_sort_table, |
| inputs=[ |
| category_filter, gr.Textbox(value="์ ๋ ฌ ์์", visible=False), |
| total_volume_sort, usage_count_sort, |
| search_volume_filter, exclude_zero_volume |
| ], |
| outputs=[table_output] |
| ) |
| |
| exclude_zero_volume.change( |
| fn=filter_and_sort_table, |
| inputs=[ |
| category_filter, gr.Textbox(value="์ ๋ ฌ ์์", visible=False), |
| total_volume_sort, usage_count_sort, |
| search_volume_filter, exclude_zero_volume |
| ], |
| outputs=[table_output] |
| ) |
| |
| |
| analyze_btn.click( |
| fn=wrapper_analyze_with_loading, |
| inputs=[analysis_keywords, selected_category, state_df], |
| outputs=[progress_section] |
| ).then( |
| fn=wrapper_process_analyze_results, |
| inputs=[analysis_keywords, selected_category, state_df], |
| outputs=[analysis_result, download_output, analysis_output_section, progress_section] |
| ) |
| |
| |
| reset_btn.click( |
| fn=reset_interface, |
| inputs=[], |
| 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, |
| selected_category, selected_category, |
| analysis_keywords, analysis_result, download_output |
| ] |
| ) |
| |
| return demo |
|
|
| if __name__ == "__main__": |
| |
| print("===== Application Startup at %s =====" % time.strftime("%Y-%m-%d %H:%M:%S")) |
| logger.info("๐ ์ปจํธ๋กค ํ์ ์ ํ๋ฆฌ์ผ์ด์
์์...") |
| |
| |
| app_temp_dir = cleanup_on_startup() |
| |
| |
| start_session_cleanup_scheduler() |
| |
| |
| try: |
| test_client = get_api_client() |
| logger.info("โ
API ์ฐ๊ฒฐ ํ
์คํธ ์ฑ๊ณต") |
| except Exception as e: |
| logger.error("โ API ์ฐ๊ฒฐ ์คํจ - ํ๊ฒฝ๋ณ์ API_ENDPOINT๋ฅผ ํ์ธํ์ธ์") |
| print("โ API_ENDPOINT ํ๊ฒฝ๋ณ์๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค.") |
| print("๐ก .env ํ์ผ์ ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํ์ธ์:") |
| print("API_ENDPOINT=your-endpoint-here") |
| raise SystemExit(1) |
| |
| logger.info("===== ์ปจํธ๋กค ํ์ ์ ํ๋ฆฌ์ผ์ด์
์์ ์๋ฃ at %s =====", time.strftime("%Y-%m-%d %H:%M:%S")) |
| logger.info(f"๐ ์์ ํ์ผ ์ ์ฅ ์์น: {app_temp_dir}") |
| |
| |
| try: |
| app = create_app() |
| print("๐ Gradio ์ ํ๋ฆฌ์ผ์ด์
์ด ์์๋ฉ๋๋ค...") |
| app.launch( |
| share=False, |
| server_name="0.0.0.0", |
| server_port=7860, |
| max_threads=40, |
| auth=None, |
| show_error=True, |
| quiet=False, |
| favicon_path=None, |
| ssl_verify=False, |
| inbrowser=False, |
| prevent_thread_lock=False |
| ) |
| except Exception as e: |
| logger.error(f"์ ํ๋ฆฌ์ผ์ด์
์คํ ์คํจ: {e}") |
| print(f"โ ์ ํ๋ฆฌ์ผ์ด์
์คํ ์คํจ: {e}") |
| raise SystemExit(1) |
| 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}") |