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 | |
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) | |
# λΆμ μ€ν | |
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; | |
} | |
""" |