F23ASFGGD / app.py
ssboost's picture
Update app.py
9889788 verified
Raw History Blame Contribute Delete
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}")