Spaces:
Running
Running
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 | |
from gradio_client import Client | |
# ๋ก๊น ์ค์ | |
logging.basicConfig( | |
level=logging.INFO, | |
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', | |
handlers=[ | |
logging.StreamHandler(), | |
logging.FileHandler('control_tower_app.log', mode='a') | |
] | |
) | |
logger = logging.getLogger(__name__) | |
# API ํด๋ผ์ด์ธํธ ์ค์ | |
def get_client(): | |
# ํ๊ฒฝ๋ณ์์์ API ์๋ํฌ์ธํธ ์ฝ๊ธฐ | |
endpoint = os.environ.get('API_ENDPOINT', '') | |
# ํ๊ฒฝ๋ณ์์ ํ์ผ ๋ด์ฉ์ด๋ ๋ถํ์ํ ํ ์คํธ๊ฐ ๋ค์ด์จ ๊ฒฝ์ฐ ์ ๋ฆฌ | |
if endpoint: | |
# ์ค๋ฐ๊ฟ์ผ๋ก ๋ถ๋ฆฌํด์ ์ฒซ ๋ฒ์งธ ์ ํจํ ๋ผ์ธ ์ฐพ๊ธฐ | |
lines = endpoint.split('\n') | |
for line in lines: | |
line = line.strip() | |
# ์ฃผ์์ด๋ ๋น์ด์๋ ๋ผ์ธ ์ ์ธ | |
if line and not line.startswith('#') and '/' in line: | |
# API_ENDPOINT= ๊ฐ์ ํค๊ฐ ์ ๊ฑฐ | |
if '=' in line: | |
line = line.split('=', 1)[1].strip() | |
# ๋ฐ์ดํ ์ ๊ฑฐ | |
line = line.strip('"\'') | |
if line and '/' in line and len(line) < 50: | |
return Client(line) | |
raise ValueError("์ฌ๋ฐ๋ฅธ API_ENDPOINT๋ฅผ ์ค์ ํด์ฃผ์ธ์ (์: username/repo-name)") | |
# ์ธ์ ๋ณ ์์ ํ์ผ ๊ด๋ฆฌ๋ฅผ ์ํ ๋์ ๋๋ฆฌ | |
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: | |
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}๊ฐ ํ์ผ ์ญ์ ") | |
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: | |
cleanup_huggingface_temp_folders() | |
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) | |
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: | |
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): | |
"""ํค์๋ ๊ฒ์ ๋ฐ ์ฒ๋ฆฌ ๋ํผ ํจ์ (API ์ฌ์ฉ)""" | |
update_session_activity(session_id) | |
try: | |
client = get_client() | |
result = client.predict( | |
keyword=keyword, | |
korean_only=korean_only, | |
apply_main_keyword=apply_main_keyword_option, | |
exclude_zero_volume=exclude_zero_volume, | |
api_name="/process_search_results" | |
) | |
# API ์๋ต ํ์ธ ๋ฐ ์ฒ๋ฆฌ (6๊ฐ ๊ฐ) | |
logger.info(f"API ์๋ต ํ์ : {type(result)}, ๊ธธ์ด: {len(result) if isinstance(result, (list, tuple)) else 'N/A'}") | |
logger.info(f"API ์๋ต ๋ด์ฉ: {result}") | |
if isinstance(result, (list, tuple)) and len(result) >= 6: | |
table_html, cat_choices, vol_choices, selected_cat, download_file, extra = result[:6] | |
logger.info(f"table_html ํ์ : {type(table_html)}") | |
logger.info(f"cat_choices: {cat_choices}") | |
logger.info(f"vol_choices: {vol_choices}") | |
logger.info(f"selected_cat: {selected_cat}") | |
logger.info(f"download_file: {download_file}") | |
elif isinstance(result, (list, tuple)) and len(result) >= 5: | |
table_html, cat_choices, vol_choices, selected_cat, download_file = result[:5] | |
extra = None | |
else: | |
# ์๋ต์ด ์์๊ณผ ๋ค๋ฅธ ๊ฒฝ์ฐ ๊ธฐ๋ณธ๊ฐ ์ฌ์ฉ | |
logger.warning(f"์์๊ณผ ๋ค๋ฅธ API ์๋ต: {result}") | |
table_html = "<p>๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ์ฒ๋ฆฌํ๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.</p>" | |
cat_choices = ["์ ์ฒด ๋ณด๊ธฐ"] | |
vol_choices = ["์ ์ฒด"] | |
selected_cat = "์ ์ฒด ๋ณด๊ธฐ" | |
download_file = None | |
# table_html ์ฒ๋ฆฌ (dict์ธ ๊ฒฝ์ฐ value ์ถ์ถ) | |
if isinstance(table_html, dict) and 'value' in table_html: | |
table_html = table_html['value'] | |
elif isinstance(table_html, dict): | |
table_html = str(table_html) # dict๋ฅผ ๋ฌธ์์ด๋ก ๋ณํ | |
# choices ํ์ ์ฒ๋ฆฌ (์ค์ฒฉ ๋ฆฌ์คํธ์ธ ๊ฒฝ์ฐ ์ฒซ ๋ฒ์งธ ๊ฐ๋ง ์ฌ์ฉ) | |
if isinstance(cat_choices, dict) and 'choices' in cat_choices: | |
cat_choices = [choice[0] if isinstance(choice, list) else choice for choice in cat_choices['choices']] | |
elif isinstance(cat_choices, list) and cat_choices and isinstance(cat_choices[0], list): | |
cat_choices = [choice[0] for choice in cat_choices] | |
if isinstance(vol_choices, dict) and 'choices' in vol_choices: | |
vol_choices = [choice[0] if isinstance(choice, list) else choice for choice in vol_choices['choices']] | |
elif isinstance(vol_choices, list) and vol_choices and isinstance(vol_choices[0], list): | |
vol_choices = [choice[0] for choice in vol_choices] | |
# selected_cat ์ฒ๋ฆฌ | |
if isinstance(selected_cat, dict) and 'value' in selected_cat: | |
selected_cat = selected_cat['value'] | |
elif isinstance(selected_cat, list): | |
selected_cat = selected_cat[0] if selected_cat else "์ ์ฒด ๋ณด๊ธฐ" | |
logger.info(f"์ฒ๋ฆฌ๋ cat_choices: {cat_choices}") | |
logger.info(f"์ฒ๋ฆฌ๋ vol_choices: {vol_choices}") | |
logger.info(f"์ฒ๋ฆฌ๋ selected_cat: {selected_cat}") | |
local_file = None | |
if download_file: | |
try: | |
local_file = create_session_temp_file(session_id, '.xlsx') | |
shutil.copy(download_file, local_file) | |
logger.info(f"ํ์ผ ๋ณต์ฌ ์๋ฃ: {local_file}") | |
except Exception as file_error: | |
logger.error(f"ํ์ผ ๋ณต์ฌ ์ค๋ฅ: {file_error}") | |
return ( | |
table_html, | |
gr.update(choices=cat_choices), | |
gr.update(choices=vol_choices), | |
None, | |
gr.update(choices=cat_choices, value=selected_cat), # ๋ถ์์ฉ ์นดํ ๊ณ ๋ฆฌ ์ ํ์ง์ ๊ฐ ์ ๋ฐ์ดํธ | |
local_file, | |
gr.update(visible=True), | |
gr.update(visible=True), | |
keyword | |
) | |
except Exception as e: | |
logger.error(f"API ํธ์ถ ์ค๋ฅ: {e}") | |
import traceback | |
logger.error(f"์์ธ ์ค๋ฅ: {traceback.format_exc()}") | |
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), | |
keyword | |
) | |
def analyze_with_auto_download(analysis_keywords, selected_category, state_df, session_id): | |
"""์นดํ ๊ณ ๋ฆฌ ์ผ์น ๋ถ์ ์คํ ๋ฐ ์๋ ๋ค์ด๋ก๋ (API ์ฌ์ฉ)""" | |
update_session_activity(session_id) | |
try: | |
client = get_client() | |
result = client.predict( | |
analysis_keywords=analysis_keywords, | |
selected_category=selected_category, | |
api_name="/process_analyze_results" | |
) | |
# API ์๋ต ํ์ธ ๋ฐ ์ฒ๋ฆฌ | |
logger.info(f"๋ถ์ API ์๋ต ํ์ : {type(result)}, ๊ธธ์ด: {len(result) if isinstance(result, (list, tuple)) else 'N/A'}") | |
if isinstance(result, (list, tuple)) and len(result) >= 2: | |
analysis_result, download_file = result[:2] | |
elif isinstance(result, str): | |
analysis_result = result | |
download_file = None | |
else: | |
logger.warning(f"์์๊ณผ ๋ค๋ฅธ ๋ถ์ API ์๋ต: {result}") | |
analysis_result = "๋ถ์ ๊ฒฐ๊ณผ๋ฅผ ์ฒ๋ฆฌํ๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค." | |
download_file = None | |
local_file = None | |
if download_file: | |
local_file = create_session_temp_file(session_id, '.xlsx') | |
shutil.copy(download_file, local_file) | |
return analysis_result, local_file, gr.update(visible=True) | |
except Exception as e: | |
logger.error(f"๋ถ์ API ํธ์ถ ์ค๋ฅ: {e}") | |
return "๋ถ์ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ๋ค์ ์๋ํด์ฃผ์ธ์.", None, gr.update(visible=False) | |
def filter_and_sort_table(df, selected_cat, keyword_sort, total_volume_sort, usage_count_sort, selected_volume_range, exclude_zero_volume, session_id): | |
"""ํ ์ด๋ธ ํํฐ๋ง ๋ฐ ์ ๋ ฌ ํจ์ (API ์ฌ์ฉ)""" | |
update_session_activity(session_id) | |
try: | |
client = get_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 | |
except Exception as e: | |
logger.error(f"ํํฐ๋ง API ํธ์ถ ์ค๋ฅ: {e}") | |
return "" | |
def update_category_selection(selected_cat, session_id): | |
"""์นดํ ๊ณ ๋ฆฌ ํํฐ ์ ํ ์ ๋ถ์ํ ์นดํ ๊ณ ๋ฆฌ๋ ๊ฐ์ ๊ฐ์ผ๋ก ์ ๋ฐ์ดํธ""" | |
update_session_activity(session_id) | |
logger.info(f"์นดํ ๊ณ ๋ฆฌ ์ ํ ๋ณ๊ฒฝ: {selected_cat}") | |
# ๋ก์ปฌ์์ ์ง์ ์ฒ๋ฆฌ (API ํธ์ถ ์์ด) | |
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] = [] | |
try: | |
client = get_client() | |
result = client.predict(api_name="/reset_interface") | |
return result | |
except Exception as e: | |
logger.error(f"๋ฆฌ์ API ํธ์ถ ์ค๋ฅ: {e}") | |
return ( | |
"", True, False, "๋ฉ์ธํค์๋ ์ ์ฉ", "", ["์ ์ฒด ๋ณด๊ธฐ"], "์ ์ฒด ๋ณด๊ธฐ", | |
["์ ์ฒด"], "์ ์ฒด", "์ ๋ ฌ ์์", "์ ๋ ฌ ์์", None, ["์ ์ฒด ๋ณด๊ธฐ"], | |
"์ ์ฒด ๋ณด๊ธฐ", "", "", None, gr.update(visible=False), | |
gr.update(visible=False), "" | |
) | |
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 table_html and table_html.get('value') if isinstance(table_html, dict) else table_html: | |
empty_placeholder_vis = False | |
keyword_section_visibility = True | |
execution_section_visibility = True | |
logger.info("ํ ์ด๋ธ ๋ฐ์ดํฐ๊ฐ ์์ - ์น์ ๋ค์ ํ์ํฉ๋๋ค") | |
else: | |
empty_placeholder_vis = True | |
keyword_section_visibility = False | |
execution_section_visibility = False | |
logger.info("ํ ์ด๋ธ ๋ฐ์ดํฐ๊ฐ ์์ - ๊ธฐ๋ณธ ์ํ๋ฅผ ์ ์งํฉ๋๋ค") | |
return ( | |
table_html, cat_choices, vol_choices, df, selected_cat, excel, | |
gr.update(visible=keyword_section_visibility), | |
gr.update(visible=True), # ์นดํ ๊ณ ๋ฆฌ ๋ถ์ ์น์ ์ ํญ์ ํ์ | |
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) | |
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 = "" | |
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) | |
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, | |
allow_custom_value=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, | |
allow_custom_value=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=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, # selected_category๊ฐ ๋ถ์์ฉ ์นดํ ๊ณ ๋ฆฌ | |
keyword_analysis_section, category_analysis_section, | |
progress_section, empty_table_html, execution_section, | |
keyword_state | |
] | |
) | |
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] | |
) | |
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] | |
) | |
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("๐ ๋ฉ์ธํค์๋ ๋ถ์ ์ ํ๋ฆฌ์ผ์ด์ ์์...") | |
app_temp_dir = cleanup_on_startup() | |
start_session_cleanup_scheduler() | |
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, | |
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 | |
) | |
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}") |