|
import gradio as gr |
|
import pandas as pd |
|
import os |
|
import logging |
|
from datetime import datetime |
|
import pytz |
|
import time |
|
import tempfile |
|
import zipfile |
|
import re |
|
import json |
|
|
|
|
|
logging.basicConfig(level=logging.WARNING, format='%(asctime)s - %(levelname)s - %(message)s') |
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
logging.getLogger('gradio').setLevel(logging.WARNING) |
|
logging.getLogger('gradio_client').setLevel(logging.WARNING) |
|
logging.getLogger('httpx').setLevel(logging.WARNING) |
|
logging.getLogger('urllib3').setLevel(logging.WARNING) |
|
|
|
|
|
def get_api_client(): |
|
"""ํ๊ฒฝ๋ณ์์์ API ์๋ํฌ์ธํธ๋ฅผ ๊ฐ์ ธ์ ํด๋ผ์ด์ธํธ ์์ฑ""" |
|
try: |
|
from gradio_client import Client |
|
|
|
|
|
api_endpoint = os.getenv('API_ENDPOINT') |
|
|
|
if not api_endpoint: |
|
logger.error("API_ENDPOINT ํ๊ฒฝ๋ณ์๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค.") |
|
raise ValueError("API_ENDPOINT ํ๊ฒฝ๋ณ์๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค.") |
|
|
|
client = Client(api_endpoint) |
|
logger.info("์๊ฒฉ API ํด๋ผ์ด์ธํธ ์ด๊ธฐํ ์ฑ๊ณต") |
|
return client |
|
|
|
except Exception as e: |
|
logger.error(f"API ํด๋ผ์ด์ธํธ ์ด๊ธฐํ ์คํจ: {e}") |
|
return None |
|
|
|
|
|
def get_korean_time(): |
|
"""ํ๊ตญ์๊ฐ ๋ฐํ""" |
|
korea_tz = pytz.timezone('Asia/Seoul') |
|
return datetime.now(korea_tz) |
|
|
|
def format_korean_datetime(dt=None, format_type="filename"): |
|
"""ํ๊ตญ์๊ฐ ํฌ๋งทํ
""" |
|
if dt is None: |
|
dt = get_korean_time() |
|
|
|
if format_type == "filename": |
|
return dt.strftime("%y%m%d_%H%M") |
|
elif format_type == "display": |
|
return dt.strftime('%Y๋
%m์ %d์ผ %H์ %M๋ถ') |
|
elif format_type == "full": |
|
return dt.strftime('%Y-%m-%d %H:%M:%S') |
|
else: |
|
return dt.strftime("%y%m%d_%H%M") |
|
|
|
|
|
def create_export_data_from_html(analysis_keyword, main_keyword, analysis_html, step1_data=None): |
|
"""๋ถ์ HTML๊ณผ 1๋จ๊ณ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก export์ฉ ๋ฐ์ดํฐ ๊ตฌ์กฐ ์์ฑ (๋๋ฏธ ๋ฐ์ดํฐ ์ ๊ฑฐ)""" |
|
logger.info("=== ๐ Export ๋ฐ์ดํฐ ๊ตฌ์กฐ ์์ฑ ์์ (๋๋ฏธ ๋ฐ์ดํฐ ์ ๊ฑฐ ๋ฒ์ ) ===") |
|
|
|
|
|
export_data = { |
|
"main_keyword": main_keyword or analysis_keyword, |
|
"analysis_keyword": analysis_keyword, |
|
"analysis_html": analysis_html, |
|
"main_keywords_df": None, |
|
"related_keywords_df": None, |
|
"analysis_completed": True, |
|
"created_at": get_korean_time().isoformat() |
|
} |
|
|
|
|
|
if step1_data and isinstance(step1_data, dict): |
|
if "keywords_df" in step1_data: |
|
keywords_df = step1_data["keywords_df"] |
|
if isinstance(keywords_df, dict): |
|
try: |
|
export_data["main_keywords_df"] = pd.DataFrame(keywords_df) |
|
logger.info(f"โ
1๋จ๊ณ ํค์๋ ๋ฐ์ดํฐ๋ฅผ DataFrame์ผ๋ก ๋ณํ: {export_data['main_keywords_df'].shape}") |
|
except Exception as e: |
|
logger.warning(f"โ ๏ธ 1๋จ๊ณ ๋ฐ์ดํฐ ๋ณํ ์คํจ: {e}") |
|
export_data["main_keywords_df"] = None |
|
elif hasattr(keywords_df, 'shape'): |
|
export_data["main_keywords_df"] = keywords_df |
|
logger.info(f"โ
1๋จ๊ณ ํค์๋ DataFrame ์ฌ์ฉ: {keywords_df.shape}") |
|
else: |
|
logger.info("๐ 1๋จ๊ณ ํค์๋ ๋ฐ์ดํฐ๊ฐ ์ ํจํ์ง ์์ - None์ผ๋ก ์ ์ง") |
|
export_data["main_keywords_df"] = None |
|
|
|
|
|
if analysis_html and "์ฐ๊ด๊ฒ์์ด ๋ถ์" in analysis_html: |
|
logger.info("๐ ๋ถ์ HTML์์ ์ฐ๊ด๊ฒ์์ด ์ ๋ณด ๋ฐ๊ฒฌ - ์ค์ ํ์ฑ ํ์") |
|
|
|
|
|
export_data["related_keywords_df"] = None |
|
logger.info("๐ก ์ค์ HTML ํ์ฑ ๋ก์ง ๊ตฌํ ํ์ - ์ฐ๊ด๊ฒ์์ด ๋ฐ์ดํฐ๋ None์ผ๋ก ์ ์ง") |
|
|
|
logger.info(f"๐ Export ๋ฐ์ดํฐ ๊ตฌ์กฐ ์์ฑ ์๋ฃ (๋๋ฏธ ๋ฐ์ดํฐ ์์):") |
|
logger.info(f" - analysis_keyword: {export_data['analysis_keyword']}") |
|
logger.info(f" - main_keywords_df: {export_data['main_keywords_df'].shape if export_data['main_keywords_df'] is not None else 'None'}") |
|
logger.info(f" - related_keywords_df: {export_data['related_keywords_df'].shape if export_data['related_keywords_df'] is not None else 'None'}") |
|
logger.info(f" - analysis_html: {len(str(export_data['analysis_html']))} ๋ฌธ์") |
|
|
|
return export_data |
|
|
|
def validate_and_repair_export_data(export_data): |
|
"""Export ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฌ ๋ฐ ๋ณต๊ตฌ (๋๋ฏธ ๋ฐ์ดํฐ ์ ๊ฑฐ)""" |
|
logger.info("๐ง Export ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฌ ๋ฐ ๋ณต๊ตฌ ์์ (๋๋ฏธ ๋ฐ์ดํฐ ์ ๊ฑฐ ๋ฒ์ )") |
|
|
|
if not export_data or not isinstance(export_data, dict): |
|
logger.warning("โ ๏ธ Export ๋ฐ์ดํฐ๊ฐ ์๊ฑฐ๋ ๋์
๋๋ฆฌ๊ฐ ์๋ - ๊ธฐ๋ณธ ๊ตฌ์กฐ ์์ฑ") |
|
return { |
|
"main_keyword": "๊ธฐ๋ณธํค์๋", |
|
"analysis_keyword": "๊ธฐ๋ณธ๋ถ์ํค์๋", |
|
"analysis_html": "<div>๊ธฐ๋ณธ ๋ถ์ ๊ฒฐ๊ณผ</div>", |
|
"main_keywords_df": None, |
|
"related_keywords_df": None, |
|
"analysis_completed": True |
|
} |
|
|
|
|
|
required_keys = { |
|
"analysis_keyword": "๋ถ์ํค์๋", |
|
"main_keyword": "๋ฉ์ธํค์๋", |
|
"analysis_html": "<div>๋ถ์ ์๋ฃ</div>", |
|
"analysis_completed": True |
|
} |
|
|
|
for key, default_value in required_keys.items(): |
|
if key not in export_data or not export_data[key]: |
|
export_data[key] = default_value |
|
logger.info(f"๐ง {key} ํค ๋ณต๊ตฌ: {default_value}") |
|
|
|
|
|
for df_key in ["main_keywords_df", "related_keywords_df"]: |
|
if df_key in export_data and export_data[df_key] is not None: |
|
df_data = export_data[df_key] |
|
|
|
|
|
if isinstance(df_data, dict): |
|
try: |
|
|
|
if not df_data: |
|
export_data[df_key] = None |
|
logger.info(f"๐ {df_key} ๋น ๋์
๋๋ฆฌ - None์ผ๋ก ์ค์ ") |
|
else: |
|
export_data[df_key] = pd.DataFrame(df_data) |
|
logger.info(f"โ
{df_key} ๋์
๋๋ฆฌ๋ฅผ DataFrame์ผ๋ก ๋ณํ ์ฑ๊ณต") |
|
except Exception as e: |
|
logger.warning(f"โ ๏ธ {df_key} ๋ณํ ์คํจ: {e}") |
|
export_data[df_key] = None |
|
elif not hasattr(df_data, 'shape'): |
|
logger.warning(f"โ ๏ธ {df_key}๊ฐ DataFrame์ด ์๋ - None์ผ๋ก ์ค์ ") |
|
export_data[df_key] = None |
|
|
|
logger.info("โ
Export ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฌ ๋ฐ ๋ณต๊ตฌ ์๋ฃ (๋๋ฏธ ๋ฐ์ดํฐ ์์)") |
|
return export_data |
|
|
|
|
|
def create_timestamp_filename(analysis_keyword): |
|
"""ํ์์คํฌํ๊ฐ ํฌํจ๋ ํ์ผ๋ช
์์ฑ - ํ๊ตญ์๊ฐ ์ ์ฉ""" |
|
timestamp = format_korean_datetime(format_type="filename") |
|
safe_keyword = re.sub(r'[^\w\s-]', '', analysis_keyword).strip() |
|
safe_keyword = re.sub(r'[-\s]+', '_', safe_keyword) |
|
return f"{safe_keyword}_{timestamp}_๋ถ์๊ฒฐ๊ณผ" |
|
|
|
def export_to_excel(main_keyword, main_keywords_df, analysis_keyword, related_keywords_df, filename_base): |
|
"""์์
ํ์ผ๋ก ์ถ๋ ฅ (์ค์ ๋ฐ์ดํฐ๋ง)""" |
|
try: |
|
|
|
has_main_data = main_keywords_df is not None and not main_keywords_df.empty |
|
has_related_data = related_keywords_df is not None and not related_keywords_df.empty |
|
|
|
if not has_main_data and not has_related_data: |
|
logger.info("๐ ์์ฑํ ๋ฐ์ดํฐ๊ฐ ์์ด ์์
ํ์ผ ์์ฑ ๊ฑด๋๋") |
|
return None |
|
|
|
excel_filename = f"{filename_base}.xlsx" |
|
excel_path = os.path.join(tempfile.gettempdir(), excel_filename) |
|
|
|
with pd.ExcelWriter(excel_path, engine='xlsxwriter') as writer: |
|
|
|
workbook = writer.book |
|
|
|
|
|
header_format = workbook.add_format({ |
|
'bold': True, |
|
'text_wrap': True, |
|
'valign': 'top', |
|
'fg_color': '#D7E4BC', |
|
'border': 1 |
|
}) |
|
|
|
|
|
data_format = workbook.add_format({ |
|
'text_wrap': True, |
|
'valign': 'top', |
|
'border': 1 |
|
}) |
|
|
|
|
|
number_format = workbook.add_format({ |
|
'num_format': '#,##0', |
|
'text_wrap': True, |
|
'valign': 'top', |
|
'border': 1 |
|
}) |
|
|
|
|
|
if has_main_data: |
|
main_keywords_df.to_excel(writer, sheet_name=f'{main_keyword}_์กฐํฉํค์๋', index=False) |
|
worksheet1 = writer.sheets[f'{main_keyword}_์กฐํฉํค์๋'] |
|
|
|
|
|
for col_num, value in enumerate(main_keywords_df.columns.values): |
|
worksheet1.write(0, col_num, value, header_format) |
|
|
|
|
|
for row_num in range(1, len(main_keywords_df) + 1): |
|
for col_num, value in enumerate(main_keywords_df.iloc[row_num-1]): |
|
if isinstance(value, (int, float)) and col_num in [1, 2, 3]: |
|
worksheet1.write(row_num, col_num, value, number_format) |
|
else: |
|
worksheet1.write(row_num, col_num, value, data_format) |
|
|
|
|
|
for i, col in enumerate(main_keywords_df.columns): |
|
max_len = max( |
|
main_keywords_df[col].astype(str).map(len).max(), |
|
len(str(col)) |
|
) |
|
worksheet1.set_column(i, i, min(max_len + 2, 50)) |
|
|
|
logger.info(f"โ
๋ฉ์ธํค์๋ ์ํธ ์์ฑ: {main_keywords_df.shape}") |
|
|
|
|
|
if has_related_data: |
|
related_keywords_df.to_excel(writer, sheet_name=f'{analysis_keyword}_์ฐ๊ด๊ฒ์์ด', index=False) |
|
worksheet2 = writer.sheets[f'{analysis_keyword}_์ฐ๊ด๊ฒ์์ด'] |
|
|
|
|
|
for col_num, value in enumerate(related_keywords_df.columns.values): |
|
worksheet2.write(0, col_num, value, header_format) |
|
|
|
|
|
for row_num in range(1, len(related_keywords_df) + 1): |
|
for col_num, value in enumerate(related_keywords_df.iloc[row_num-1]): |
|
if isinstance(value, (int, float)) and col_num in [1, 2, 3]: |
|
worksheet2.write(row_num, col_num, value, number_format) |
|
else: |
|
worksheet2.write(row_num, col_num, value, data_format) |
|
|
|
|
|
for i, col in enumerate(related_keywords_df.columns): |
|
max_len = max( |
|
related_keywords_df[col].astype(str).map(len).max(), |
|
len(str(col)) |
|
) |
|
worksheet2.set_column(i, i, min(max_len + 2, 50)) |
|
|
|
logger.info(f"โ
์ฐ๊ด๊ฒ์์ด ์ํธ ์์ฑ: {related_keywords_df.shape}") |
|
|
|
logger.info(f"์์
ํ์ผ ์์ฑ ์๋ฃ: {excel_path}") |
|
return excel_path |
|
|
|
except Exception as e: |
|
logger.error(f"์์
ํ์ผ ์์ฑ ์ค๋ฅ: {e}") |
|
return None |
|
|
|
def export_to_html(analysis_html, filename_base): |
|
"""HTML ํ์ผ๋ก ์ถ๋ ฅ - ํ๊ตญ์๊ฐ ์ ์ฉ""" |
|
try: |
|
html_filename = f"{filename_base}.html" |
|
html_path = os.path.join(tempfile.gettempdir(), html_filename) |
|
|
|
|
|
korean_time = format_korean_datetime(format_type="display") |
|
|
|
|
|
full_html = f""" |
|
<!DOCTYPE html> |
|
<html lang="ko"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>ํค์๋ ์ฌ์ถฉ๋ถ์ ๊ฒฐ๊ณผ</title> |
|
<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"> |
|
<style> |
|
body {{ |
|
font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; |
|
margin: 0; |
|
padding: 20px; |
|
background-color: #f5f5f5; |
|
line-height: 1.6; |
|
}} |
|
.container {{ |
|
max-width: 1200px; |
|
margin: 0 auto; |
|
background: white; |
|
border-radius: 12px; |
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1); |
|
overflow: hidden; |
|
}} |
|
.header {{ |
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
color: white; |
|
padding: 30px; |
|
text-align: center; |
|
}} |
|
.header h1 {{ |
|
margin: 0; |
|
font-size: 28px; |
|
font-weight: 700; |
|
}} |
|
.header p {{ |
|
margin: 10px 0 0 0; |
|
font-size: 16px; |
|
opacity: 0.9; |
|
}} |
|
.content {{ |
|
padding: 30px; |
|
}} |
|
.timestamp {{ |
|
text-align: center; |
|
padding: 20px; |
|
background: #f8f9fa; |
|
color: #6c757d; |
|
font-size: 14px; |
|
border-top: 1px solid #dee2e6; |
|
}} |
|
|
|
/* ์ฐจํธ ์คํ์ผ ๊ฐ์ */ |
|
.chart-container {{ |
|
margin: 20px 0; |
|
padding: 20px; |
|
background: white; |
|
border-radius: 8px; |
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1); |
|
}} |
|
|
|
/* ๋ฐ์ํ ์คํ์ผ */ |
|
@media (max-width: 768px) {{ |
|
.container {{ |
|
margin: 10px; |
|
border-radius: 8px; |
|
}} |
|
.header {{ |
|
padding: 20px; |
|
}} |
|
.header h1 {{ |
|
font-size: 24px; |
|
}} |
|
.content {{ |
|
padding: 20px; |
|
}} |
|
}} |
|
|
|
/* ์ ๋๋ฉ์ด์
*/ |
|
@keyframes spin {{ |
|
0% {{ transform: rotate(0deg); }} |
|
100% {{ transform: rotate(360deg); }} |
|
}} |
|
|
|
@keyframes progress {{ |
|
0% {{ transform: translateX(-100%); }} |
|
100% {{ transform: translateX(100%); }} |
|
}} |
|
|
|
/* ํ๋ฆฐํธ ์คํ์ผ */ |
|
@media print {{ |
|
body {{ |
|
background: white; |
|
padding: 0; |
|
}} |
|
.container {{ |
|
box-shadow: none; |
|
border-radius: 0; |
|
}} |
|
.header {{ |
|
background: #667eea !important; |
|
-webkit-print-color-adjust: exact; |
|
}} |
|
}} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="container"> |
|
<div class="header"> |
|
<h1><i class="fas fa-chart-line"></i> ํค์๋ ์ฌ์ถฉ๋ถ์ ๊ฒฐ๊ณผ</h1> |
|
<p>AI ์ํ ์์ฑ ๋ถ์ ์์คํ
v3.2 (๋๋ฏธ ๋ฐ์ดํฐ ์ ๊ฑฐ ๋ฒ์ )</p> |
|
</div> |
|
<div class="content"> |
|
{analysis_html} |
|
</div> |
|
<div class="timestamp"> |
|
<i class="fas fa-clock"></i> ์์ฑ ์๊ฐ: {korean_time} (ํ๊ตญ์๊ฐ) |
|
</div> |
|
</div> |
|
</body> |
|
</html> |
|
""" |
|
|
|
with open(html_path, 'w', encoding='utf-8') as f: |
|
f.write(full_html) |
|
|
|
logger.info(f"HTML ํ์ผ ์์ฑ ์๋ฃ: {html_path}") |
|
return html_path |
|
|
|
except Exception as e: |
|
logger.error(f"HTML ํ์ผ ์์ฑ ์ค๋ฅ: {e}") |
|
return None |
|
|
|
def create_zip_file(excel_path, html_path, filename_base): |
|
"""์์ถ ํ์ผ ์์ฑ""" |
|
try: |
|
zip_filename = f"{filename_base}.zip" |
|
zip_path = os.path.join(tempfile.gettempdir(), zip_filename) |
|
|
|
files_added = 0 |
|
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf: |
|
if excel_path and os.path.exists(excel_path): |
|
zipf.write(excel_path, f"{filename_base}.xlsx") |
|
logger.info(f"์์
ํ์ผ ์์ถ ์ถ๊ฐ: {filename_base}.xlsx") |
|
files_added += 1 |
|
|
|
if html_path and os.path.exists(html_path): |
|
zipf.write(html_path, f"{filename_base}.html") |
|
logger.info(f"HTML ํ์ผ ์์ถ ์ถ๊ฐ: {filename_base}.html") |
|
files_added += 1 |
|
|
|
if files_added == 0: |
|
logger.warning("์์ถํ ํ์ผ์ด ์์") |
|
return None |
|
|
|
logger.info(f"์์ถ ํ์ผ ์์ฑ ์๋ฃ: {zip_path} ({files_added}๊ฐ ํ์ผ)") |
|
return zip_path |
|
|
|
except Exception as e: |
|
logger.error(f"์์ถ ํ์ผ ์์ฑ ์ค๋ฅ: {e}") |
|
return None |
|
|
|
def export_analysis_results_enhanced(export_data): |
|
"""๊ฐํ๋ ๋ถ์ ๊ฒฐ๊ณผ ์ถ๋ ฅ ๋ฉ์ธ ํจ์ (๋๋ฏธ ๋ฐ์ดํฐ ์ ๊ฑฐ)""" |
|
try: |
|
logger.info("=== ๐ ๊ฐํ๋ ์ถ๋ ฅ ํจ์ ์์ (๋๋ฏธ ๋ฐ์ดํฐ ์ ๊ฑฐ ๋ฒ์ ) ===") |
|
|
|
|
|
export_data = validate_and_repair_export_data(export_data) |
|
|
|
analysis_keyword = export_data.get("analysis_keyword", "๊ธฐ๋ณธํค์๋") |
|
analysis_html = export_data.get("analysis_html", "<div>๋ถ์ ์๋ฃ</div>") |
|
main_keyword = export_data.get("main_keyword", analysis_keyword) |
|
main_keywords_df = export_data.get("main_keywords_df") |
|
related_keywords_df = export_data.get("related_keywords_df") |
|
|
|
logger.info(f"๐ ์ฒ๋ฆฌํ ๋ฐ์ดํฐ:") |
|
logger.info(f" - analysis_keyword: '{analysis_keyword}'") |
|
logger.info(f" - main_keyword: '{main_keyword}'") |
|
logger.info(f" - analysis_html: {len(str(analysis_html))} ๋ฌธ์") |
|
logger.info(f" - main_keywords_df: {main_keywords_df.shape if main_keywords_df is not None else 'None'}") |
|
logger.info(f" - related_keywords_df: {related_keywords_df.shape if related_keywords_df is not None else 'None'}") |
|
|
|
|
|
filename_base = create_timestamp_filename(analysis_keyword) |
|
logger.info(f"๐ ์ถ๋ ฅ ํ์ผ๋ช
: {filename_base}") |
|
|
|
|
|
html_path = None |
|
if analysis_html and len(str(analysis_html).strip()) > 20: |
|
logger.info("๐ HTML ํ์ผ ์์ฑ ์์...") |
|
html_path = export_to_html(analysis_html, filename_base) |
|
if html_path: |
|
logger.info(f"โ
HTML ํ์ผ ์์ฑ ์ฑ๊ณต: {html_path}") |
|
else: |
|
logger.error("โ HTML ํ์ผ ์์ฑ ์คํจ") |
|
else: |
|
logger.info("๐ ๋ถ์ HTML์ด ์์ด HTML ํ์ผ ์์ฑ ๊ฑด๋๋") |
|
|
|
|
|
excel_path = None |
|
if (main_keywords_df is not None and not main_keywords_df.empty) or \ |
|
(related_keywords_df is not None and not related_keywords_df.empty): |
|
logger.info("๐ ์์
ํ์ผ ์์ฑ ์์...") |
|
excel_path = export_to_excel( |
|
main_keyword, |
|
main_keywords_df, |
|
analysis_keyword, |
|
related_keywords_df, |
|
filename_base |
|
) |
|
if excel_path: |
|
logger.info(f"โ
์์
ํ์ผ ์์ฑ ์ฑ๊ณต: {excel_path}") |
|
else: |
|
logger.warning("โ ๏ธ ์์
ํ์ผ ์์ฑ ์คํจ") |
|
else: |
|
logger.info("๐ ์ค์ DataFrame ๋ฐ์ดํฐ๊ฐ ์์ด ์์
ํ์ผ ์์ฑ ์๋ต") |
|
|
|
|
|
if not html_path and not excel_path: |
|
logger.warning("โ ๏ธ ์์ฑ๋ ํ์ผ์ด ์์") |
|
return None, "โ ๏ธ ์์ฑํ ์ ์๋ ๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค. ๋ถ์์ ๋จผ์ ์๋ฃํด์ฃผ์ธ์." |
|
|
|
|
|
logger.info("๐ฆ ์์ถ ํ์ผ ์์ฑ ์์...") |
|
zip_path = create_zip_file(excel_path, html_path, filename_base) |
|
if zip_path: |
|
file_types = [] |
|
if html_path: |
|
file_types.append("HTML") |
|
if excel_path: |
|
file_types.append("์์
") |
|
|
|
file_list = " + ".join(file_types) |
|
logger.info(f"โ
์์ถ ํ์ผ ์์ฑ ์ฑ๊ณต: {zip_path} ({file_list})") |
|
return zip_path, f"โ
๋ถ์ ๊ฒฐ๊ณผ๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์ถ๋ ฅ๋์์ต๋๋ค!\nํ์ผ๋ช
: {filename_base}.zip\nํฌํจ ํ์ผ: {file_list}\n\n๐ก ๋๋ฏธ ๋ฐ์ดํฐ ์ ๊ฑฐ ๋ฒ์ - ์ค์ ๋ถ์ ๋ฐ์ดํฐ๋ง ํฌํจ๋ฉ๋๋ค." |
|
else: |
|
logger.error("โ ์์ถ ํ์ผ ์์ฑ ์คํจ") |
|
return None, "์์ถ ํ์ผ ์์ฑ์ ์คํจํ์ต๋๋ค." |
|
|
|
except Exception as e: |
|
logger.error(f"โ ๊ฐํ๋ ์ถ๋ ฅ ํจ์ ์ ์ฒด ์ค๋ฅ: {e}") |
|
import traceback |
|
logger.error(f"์คํ ํธ๋ ์ด์ค:\n{traceback.format_exc()}") |
|
return None, f"์ถ๋ ฅ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {str(e)}" |
|
|
|
|
|
def create_loading_animation(): |
|
"""๋ก๋ฉ ์ ๋๋ฉ์ด์
HTML""" |
|
return """ |
|
<div style="display: flex; flex-direction: column; align-items: center; padding: 40px; background: white; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1);"> |
|
<div style="width: 60px; height: 60px; border: 4px solid #f3f3f3; border-top: 4px solid #FB7F0D; border-radius: 50%; animation: spin 1s linear infinite; margin-bottom: 20px;"></div> |
|
<h3 style="color: #FB7F0D; margin: 10px 0; font-size: 18px;">๋ถ์ ์ค์
๋๋ค...</h3> |
|
<p style="color: #666; margin: 5px 0; text-align: center;">์๊ฒฉ ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ์์งํ๊ณ AI๊ฐ ๋ถ์ํ๊ณ ์์ต๋๋ค.<br>์ ์๋ง ๊ธฐ๋ค๋ ค์ฃผ์ธ์.</p> |
|
<div style="width: 200px; height: 4px; background: #f0f0f0; border-radius: 2px; margin-top: 15px; overflow: hidden;"> |
|
<div style="width: 100%; height: 100%; background: linear-gradient(90deg, #FB7F0D, #ff9a8b); border-radius: 2px; animation: progress 2s ease-in-out infinite;"></div> |
|
</div> |
|
</div> |
|
|
|
<style> |
|
@keyframes spin { |
|
0% { transform: rotate(0deg); } |
|
100% { transform: rotate(360deg); } |
|
} |
|
|
|
@keyframes progress { |
|
0% { transform: translateX(-100%); } |
|
100% { transform: translateX(100%); } |
|
} |
|
</style> |
|
""" |
|
|
|
|
|
def generate_error_response(error_message): |
|
"""์๋ฌ ์๋ต ์์ฑ""" |
|
return f''' |
|
<div style="color: red; padding: 30px; text-align: center; width: 100%; |
|
background-color: #f8d7da; border-radius: 12px; border: 1px solid #f5c6cb;"> |
|
<h3 style="margin-bottom: 15px;">โ ์ฐ๊ฒฐ ์ค๋ฅ</h3> |
|
<p style="margin-bottom: 20px;">{error_message}</p> |
|
<div style="background: white; padding: 15px; border-radius: 8px; color: #333;"> |
|
<h4>ํด๊ฒฐ ๋ฐฉ๋ฒ:</h4> |
|
<ul style="text-align: left; padding-left: 20px;"> |
|
<li>๋คํธ์ํฌ ์ฐ๊ฒฐ์ ํ์ธํด์ฃผ์ธ์</li> |
|
<li>์๊ฒฉ ์๋ฒ ์ํ๋ฅผ ํ์ธํด์ฃผ์ธ์</li> |
|
<li>์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์</li> |
|
<li>๋ฌธ์ ๊ฐ ์ง์๋๋ฉด ๊ด๋ฆฌ์์๊ฒ ๋ฌธ์ํ์ธ์</li> |
|
</ul> |
|
</div> |
|
</div> |
|
''' |
|
|
|
|
|
def call_collect_data_api(keyword): |
|
"""1๋จ๊ณ: ์ํ ๋ฐ์ดํฐ ์์ง API ํธ์ถ""" |
|
try: |
|
client = get_api_client() |
|
if not client: |
|
return generate_error_response("API ํด๋ผ์ด์ธํธ๋ฅผ ์ด๊ธฐํํ ์ ์์ต๋๋ค."), {} |
|
|
|
logger.info("์๊ฒฉ API ํธ์ถ: ์ํ ๋ฐ์ดํฐ ์์ง") |
|
result = client.predict( |
|
keyword=keyword, |
|
api_name="/on_collect_data" |
|
) |
|
|
|
logger.info(f"๋ฐ์ดํฐ ์์ง API ๊ฒฐ๊ณผ ํ์
: {type(result)}") |
|
|
|
|
|
if isinstance(result, tuple) and len(result) == 2: |
|
html_result, session_data = result |
|
|
|
|
|
if isinstance(session_data, dict): |
|
logger.info(f"๋ฐ์ดํฐ ์์ง ์ธ์
๋ฐ์ดํฐ ์์ : {list(session_data.keys()) if session_data else '๋น ๋์
๋๋ฆฌ'}") |
|
return html_result, session_data |
|
else: |
|
logger.warning("์ธ์
๋ฐ์ดํฐ๊ฐ ๋์
๋๋ฆฌ๊ฐ ์๋๋๋ค.") |
|
return html_result, {} |
|
else: |
|
logger.warning("์์๊ณผ ๋ค๋ฅธ ๋ฐ์ดํฐ ์์ง ๊ฒฐ๊ณผ ํํ") |
|
return str(result), {"keywords_collected": True} |
|
|
|
except Exception as e: |
|
logger.error(f"์ํ ๋ฐ์ดํฐ ์์ง API ํธ์ถ ์ค๋ฅ: {e}") |
|
return generate_error_response(f"์๊ฒฉ ์๋ฒ ์ฐ๊ฒฐ ์คํจ: {str(e)}"), {} |
|
|
|
def call_analyze_keyword_api_enhanced(analysis_keyword, base_keyword, keywords_data): |
|
"""3๋จ๊ณ: ๊ฐํ๋ ํค์๋ ์ฌ์ถฉ๋ถ์ API ํธ์ถ (๋๋ฏธ ๋ฐ์ดํฐ ์ ๊ฑฐ)""" |
|
try: |
|
client = get_api_client() |
|
if not client: |
|
return generate_error_response("API ํด๋ผ์ด์ธํธ๋ฅผ ์ด๊ธฐํํ ์ ์์ต๋๋ค."), {} |
|
|
|
logger.info("=== ๐ ๊ฐํ๋ ํค์๋ ์ฌ์ถฉ๋ถ์ API ํธ์ถ (๋๋ฏธ ๋ฐ์ดํฐ ์ ๊ฑฐ) ===") |
|
logger.info(f"ํ๋ผ๋ฏธํฐ - analysis_keyword: '{analysis_keyword}'") |
|
logger.info(f"ํ๋ผ๋ฏธํฐ - base_keyword: '{base_keyword}'") |
|
logger.info(f"ํ๋ผ๋ฏธํฐ - keywords_data ํ์
: {type(keywords_data)}") |
|
|
|
|
|
result = client.predict( |
|
analysis_keyword, |
|
base_keyword, |
|
keywords_data, |
|
api_name="/on_analyze_keyword" |
|
) |
|
|
|
logger.info(f"๐ก ์๊ฒฉ API ์๋ต ์์ :") |
|
logger.info(f" - ์๋ต ํ์
: {type(result)}") |
|
logger.info(f" - ์๋ต ๊ธธ์ด: {len(result) if hasattr(result, '__len__') else 'N/A'}") |
|
|
|
|
|
if isinstance(result, tuple) and len(result) == 2: |
|
html_result, remote_export_data = result |
|
|
|
logger.info(f"๐ ์๊ฒฉ export ๋ฐ์ดํฐ:") |
|
logger.info(f" - ํ์
: {type(remote_export_data)}") |
|
logger.info(f" - ํค๋ค: {list(remote_export_data.keys()) if isinstance(remote_export_data, dict) else 'None'}") |
|
|
|
|
|
if html_result: |
|
logger.info("๐ง Export ๋ฐ์ดํฐ ๊ตฌ์กฐ ์์ฑ ์์ (๋๋ฏธ ๋ฐ์ดํฐ ์ ๊ฑฐ)") |
|
enhanced_export_data = create_export_data_from_html( |
|
analysis_keyword=analysis_keyword, |
|
main_keyword=base_keyword, |
|
analysis_html=html_result, |
|
step1_data=keywords_data |
|
) |
|
|
|
|
|
if isinstance(remote_export_data, dict) and remote_export_data: |
|
logger.info("๐ ์๊ฒฉ ์ค์ ๋ฐ์ดํฐ์ ๋ก์ปฌ ๋ฐ์ดํฐ ๋ณํฉ") |
|
for key, value in remote_export_data.items(): |
|
if value is not None and key in ["main_keywords_df", "related_keywords_df"]: |
|
|
|
if isinstance(value, dict) and value: |
|
enhanced_export_data[key] = value |
|
logger.info(f" - {key} ์๊ฒฉ ์ค์ ๋ฐ์ดํฐ๋ก ์
๋ฐ์ดํธ") |
|
elif hasattr(value, 'shape') and not value.empty: |
|
enhanced_export_data[key] = value |
|
logger.info(f" - {key} ์๊ฒฉ DataFrame ๋ฐ์ดํฐ๋ก ์
๋ฐ์ดํธ") |
|
elif value is not None and key not in ["main_keywords_df", "related_keywords_df"]: |
|
enhanced_export_data[key] = value |
|
logger.info(f" - {key} ์๊ฒฉ ๋ฐ์ดํฐ๋ก ์
๋ฐ์ดํธ") |
|
|
|
logger.info(f"โ
์ต์ข
Export ๋ฐ์ดํฐ ๊ตฌ์กฐ (๋๋ฏธ ๋ฐ์ดํฐ ์์):") |
|
logger.info(f" - ํค ๊ฐ์: {len(enhanced_export_data)}") |
|
logger.info(f" - ํค ๋ชฉ๋ก: {list(enhanced_export_data.keys())}") |
|
|
|
return html_result, enhanced_export_data |
|
else: |
|
logger.warning("โ ๏ธ HTML ๊ฒฐ๊ณผ๊ฐ ๋น์ด์์") |
|
return str(result), {} |
|
else: |
|
logger.warning("โ ๏ธ ์์๊ณผ ๋ค๋ฅธ API ์๋ต ํํ") |
|
|
|
if isinstance(result, str) and len(result) > 100: |
|
logger.info("๐ HTML ๋ฌธ์์ด๋ก ์ถ์ ๋๋ ์๋ต - Export ๋ฐ์ดํฐ ์์ฑ (๋๋ฏธ ๋ฐ์ดํฐ ์์ด)") |
|
enhanced_export_data = create_export_data_from_html( |
|
analysis_keyword=analysis_keyword, |
|
main_keyword=base_keyword, |
|
analysis_html=result, |
|
step1_data=keywords_data |
|
) |
|
return result, enhanced_export_data |
|
else: |
|
return str(result), {} |
|
|
|
except Exception as e: |
|
logger.error(f"โ ํค์๋ ์ฌ์ถฉ๋ถ์ API ํธ์ถ ์ค๋ฅ: {e}") |
|
import traceback |
|
logger.error(f"์คํ ํธ๋ ์ด์ค:\n{traceback.format_exc()}") |
|
return generate_error_response(f"์๊ฒฉ ์๋ฒ ์ฐ๊ฒฐ ์คํจ: {str(e)}"), {} |
|
|
|
|
|
def create_interface(): |
|
|
|
custom_css = """ |
|
/* ๊ธฐ์กด ๋คํฌ๋ชจ๋ ์๋ ๋ณ๊ฒฝ AI ์ํ ์์ฑ ๋ถ์ ์์คํ
CSS */ |
|
:root { |
|
--primary-color: #FB7F0D; |
|
--secondary-color: #ff9a8b; |
|
--accent-color: #FF6B6B; |
|
--background-color: #FFFFFF; |
|
--card-bg: #ffffff; |
|
--input-bg: #ffffff; |
|
--text-color: #334155; |
|
--text-secondary: #64748b; |
|
--border-color: #dddddd; |
|
--border-light: #e5e5e5; |
|
--table-even-bg: #f3f3f3; |
|
--table-hover-bg: #f0f0f0; |
|
--shadow: 0 8px 30px rgba(251, 127, 13, 0.08); |
|
--shadow-light: 0 2px 4px rgba(0, 0, 0, 0.1); |
|
--border-radius: 18px; |
|
} |
|
@media (prefers-color-scheme: dark) { |
|
:root { |
|
--background-color: #1a1a1a; |
|
--card-bg: #2d2d2d; |
|
--input-bg: #2d2d2d; |
|
--text-color: #e5e5e5; |
|
--text-secondary: #a1a1aa; |
|
--border-color: #404040; |
|
--border-light: #525252; |
|
--table-even-bg: #333333; |
|
--table-hover-bg: #404040; |
|
--shadow: 0 8px 30px rgba(0, 0, 0, 0.3); |
|
--shadow-light: 0 2px 4px rgba(0, 0, 0, 0.2); |
|
} |
|
} |
|
body { |
|
font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif; |
|
background-color: var(--background-color) !important; |
|
color: var(--text-color) !important; |
|
line-height: 1.6; |
|
margin: 0; |
|
padding: 0; |
|
transition: background-color 0.3s ease, color 0.3s ease; |
|
} |
|
.gradio-container { |
|
width: 100%; |
|
margin: 0 auto; |
|
padding: 20px; |
|
background-color: var(--background-color) !important; |
|
} |
|
.custom-frame { |
|
background-color: var(--card-bg) !important; |
|
border: 1px solid var(--border-light) !important; |
|
border-radius: var(--border-radius); |
|
padding: 20px; |
|
margin: 10px 0; |
|
box-shadow: var(--shadow) !important; |
|
color: var(--text-color) !important; |
|
} |
|
.custom-button { |
|
border-radius: 30px !important; |
|
background: var(--primary-color) !important; |
|
color: white !important; |
|
font-size: 18px !important; |
|
padding: 10px 20px !important; |
|
border: none; |
|
box-shadow: 0 4px 8px rgba(251, 127, 13, 0.25); |
|
transition: transform 0.3s ease; |
|
height: 45px !important; |
|
width: 100% !important; |
|
} |
|
.custom-button:hover { |
|
transform: translateY(-2px); |
|
box-shadow: 0 6px 12px rgba(251, 127, 13, 0.3); |
|
} |
|
.export-button { |
|
background: linear-gradient(135deg, #28a745, #20c997) !important; |
|
color: white !important; |
|
border-radius: 25px !important; |
|
height: 50px !important; |
|
font-size: 17px !important; |
|
font-weight: bold !important; |
|
width: 100% !important; |
|
margin-top: 20px !important; |
|
} |
|
.section-title { |
|
display: flex; |
|
align-items: center; |
|
font-size: 20px; |
|
font-weight: 700; |
|
color: var(--text-color) !important; |
|
margin-bottom: 10px; |
|
padding-bottom: 5px; |
|
border-bottom: 2px solid var(--primary-color); |
|
font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif; |
|
} |
|
.section-title img, .section-title i { |
|
margin-right: 10px; |
|
font-size: 20px; |
|
color: var(--primary-color); |
|
} |
|
.gr-input, .gr-text-input, .gr-sample-inputs, |
|
input[type="text"], input[type="number"], textarea, select { |
|
border-radius: var(--border-radius) !important; |
|
border: 1px solid var(--border-color) !important; |
|
padding: 12px !important; |
|
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05) !important; |
|
transition: all 0.3s ease !important; |
|
background-color: var(--input-bg) !important; |
|
color: var(--text-color) !important; |
|
} |
|
.gr-input:focus, .gr-text-input:focus, |
|
input[type="text"]:focus, textarea:focus, select:focus { |
|
border-color: var(--primary-color) !important; |
|
outline: none !important; |
|
box-shadow: 0 0 0 2px rgba(251, 127, 13, 0.2) !important; |
|
} |
|
.fade-in { |
|
animation: fadeIn 0.5s ease-out; |
|
} |
|
@keyframes fadeIn { |
|
from { opacity: 0; transform: translateY(10px); } |
|
to { opacity: 1; transform: translateY(0); } |
|
} |
|
""" |
|
|
|
with gr.Blocks( |
|
css=custom_css, |
|
title="๐ AI ์ํ ์์ฑ ๋ถ์๊ธฐ v3.2 (๋๋ฏธ ๋ฐ์ดํฐ ์ ๊ฑฐ)", |
|
theme=gr.themes.Default(primary_hue="orange", secondary_hue="orange") |
|
) as interface: |
|
|
|
|
|
gr.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"> |
|
""") |
|
|
|
|
|
keywords_data_state = gr.State() |
|
export_data_state = gr.State({}) |
|
|
|
|
|
with gr.Column(elem_classes="custom-frame fade-in"): |
|
gr.HTML('<div class="section-title"><i class="fas fa-search"></i> 1๋จ๊ณ: ๋ฉ์ธ ํค์๋ ์
๋ ฅ</div>') |
|
|
|
keyword_input = gr.Textbox( |
|
label="์ํ ๋ฉ์ธํค์๋", |
|
placeholder="์: ์ฌ๋ฆฌํผ, ๋ฌด์ ์ด์ดํฐ, ํธ๋ํฌ๋ฆผ", |
|
value="", |
|
elem_id="keyword_input" |
|
) |
|
|
|
collect_data_btn = gr.Button("1๋จ๊ณ: ์ํ ๋ฐ์ดํฐ ์์งํ๊ธฐ", elem_classes="custom-button", size="lg") |
|
|
|
with gr.Column(elem_classes="custom-frame fade-in"): |
|
gr.HTML('<div class="section-title"><i class="fas fa-database"></i> 2๋จ๊ณ: ์์ง๋ ํค์๋ ๋ชฉ๋ก</div>') |
|
keywords_result = gr.HTML() |
|
|
|
with gr.Column(elem_classes="custom-frame fade-in"): |
|
gr.HTML('<div class="section-title"><i class="fas fa-bullseye"></i> 3๋จ๊ณ: ๋ถ์ํ ํค์๋ ์ ํ</div>') |
|
|
|
analysis_keyword_input = gr.Textbox( |
|
label="๋ถ์ํ ํค์๋", |
|
placeholder="์ ๋ชฉ๋ก์์ ์ํ๋ ํค์๋๋ฅผ ์
๋ ฅํ์ธ์ (์: ํต๊ตฝ ์ฌ๋ฆฌํผ)", |
|
value="", |
|
elem_id="analysis_keyword_input" |
|
) |
|
|
|
analyze_keyword_btn = gr.Button("ํค์๋ ์ฌ์ถฉ๋ถ์ ํ๊ธฐ", elem_classes="custom-button", size="lg") |
|
|
|
with gr.Column(elem_classes="custom-frame fade-in"): |
|
gr.HTML('<div class="section-title"><i class="fas fa-chart-line"></i> ํค์๋ ์ฌ์ถฉ๋ถ์</div>') |
|
analysis_result = gr.HTML(label="ํค์๋ ์ฌ์ถฉ๋ถ์") |
|
|
|
with gr.Column(elem_classes="custom-frame fade-in"): |
|
gr.HTML('<div class="section-title"><i class="fas fa-download"></i> ๋ถ์ ๊ฒฐ๊ณผ ์ถ๋ ฅ</div>') |
|
|
|
gr.HTML(""" |
|
<div style="background: #e3f2fd; border-left: 4px solid #2196f3; padding: 15px; margin: 10px 0; border-radius: 5px;"> |
|
<h4 style="margin: 0 0 10px 0; color: #1976d2;"><i class="fas fa-info-circle"></i> ์ค์ ๋ฐ์ดํฐ ์ถ๋ ฅ ๋ฒ์ </h4> |
|
<p style="margin: 0; color: #1976d2; font-size: 14px;"> |
|
โข ๋ถ์๋ ๋ฐ์ดํฐ๋ฅผ ํ์ผ๋ก ์ถ๋ ฅ๋ฉ๋๋ค<br> |
|
</p> |
|
</div> |
|
""") |
|
|
|
export_btn = gr.Button("๐ ๋ถ์๊ฒฐ๊ณผ ์ถ๋ ฅํ๊ธฐ", elem_classes="export-button", size="lg") |
|
export_result = gr.HTML() |
|
download_file = gr.File(label="๋ค์ด๋ก๋", visible=False) |
|
|
|
|
|
def on_collect_data(keyword): |
|
if not keyword.strip(): |
|
return ("<div style='color: red; padding: 20px; text-align: center; width: 100%;'>ํค์๋๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.</div>", None) |
|
|
|
|
|
yield (create_loading_animation(), None) |
|
|
|
|
|
result_html, result_data = call_collect_data_api(keyword) |
|
|
|
yield (result_html, result_data) |
|
|
|
def on_analyze_keyword(analysis_keyword, base_keyword, keywords_data): |
|
if not analysis_keyword.strip(): |
|
return "<div style='color: red; padding: 20px; text-align: center; width: 100%;'>๋ถ์ํ ํค์๋๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.</div>", {} |
|
|
|
|
|
yield create_loading_animation(), {} |
|
|
|
|
|
html_result, enhanced_export_data = call_analyze_keyword_api_enhanced( |
|
analysis_keyword, base_keyword, keywords_data |
|
) |
|
|
|
yield html_result, enhanced_export_data |
|
|
|
def on_export_results(export_data): |
|
"""๊ฐํ๋ ๋ถ์ ๊ฒฐ๊ณผ ์ถ๋ ฅ ํธ๋ค๋ฌ (๋๋ฏธ ๋ฐ์ดํฐ ์ ๊ฑฐ)""" |
|
try: |
|
logger.info(f"๐ ์
๋ ฅ export_data: {type(export_data)}") |
|
if isinstance(export_data, dict): |
|
logger.info(f"๐ export_data ํค๋ค: {list(export_data.keys())}") |
|
|
|
|
|
zip_path, message = export_analysis_results_enhanced(export_data) |
|
|
|
if zip_path: |
|
success_html = f""" |
|
<div style="background: #d4edda; border: 1px solid #c3e6cb; padding: 20px; border-radius: 8px; margin: 10px 0;"> |
|
<h4 style="color: #155724; margin: 0 0 15px 0;"><i class="fas fa-check-circle"></i> ์ถ๋ ฅ ์๋ฃ!</h4> |
|
<p style="color: #155724; margin: 0; line-height: 1.6;"> |
|
{message}<br> |
|
<strong>๋ฐ์ดํฐ์ถ๋ ฅ:</strong><br> |
|
<br> |
|
<i class="fas fa-download"></i> ์๋ ๋ค์ด๋ก๋ ๋ฒํผ์ ํด๋ฆญํ์ฌ ํ์ผ์ ์ ์ฅํ์ธ์.<br> |
|
<small style="color: #666;">โฐ ํ๊ตญ์๊ฐ ๊ธฐ์ค์ผ๋ก ํ์ผ๋ช
์ด ์์ฑ๋ฉ๋๋ค.</small> |
|
</p> |
|
</div> |
|
""" |
|
return success_html, gr.update(value=zip_path, visible=True) |
|
else: |
|
error_html = f""" |
|
<div style="background: #f8d7da; border: 1px solid #f5c6cb; padding: 20px; border-radius: 8px; margin: 10px 0;"> |
|
<h4 style="color: #721c24; margin: 0 0 10px 0;"><i class="fas fa-exclamation-triangle"></i> ์ถ๋ ฅ ์คํจ</h4> |
|
<p style="color: #721c24; margin: 0;">{message}</p> |
|
<div style="margin-top: 15px; padding: 15px; background: white; border-radius: 5px;"> |
|
<h5 style="color: #721c24; margin: 0 0 10px 0;">๐ ๋๋ฒ๊น
์ ๋ณด:</h5> |
|
<ul style="color: #721c24; margin: 0; padding-left: 20px;"> |
|
<li>Export ๋ฐ์ดํฐ ํ์
: {type(export_data)}</li> |
|
<li>Export ๋ฐ์ดํฐ ์ ํจ์ฑ: {'์ ํจ' if export_data else '๋ฌดํจ'}</li> |
|
<li>ํค์๋ ์ฌ์ถฉ๋ถ์ ์ํ: {'์๋ฃ' if export_data.get('analysis_completed') else '๋ฏธ์๋ฃ'}</li> |
|
</ul> |
|
</div> |
|
</div> |
|
""" |
|
logger.error("โ ๊ฐํ๋ ์ถ๋ ฅ ์คํจ") |
|
return error_html, gr.update(visible=False) |
|
|
|
except Exception as e: |
|
logger.error(f"โ ๊ฐํ๋ ์ถ๋ ฅ ํธ๋ค๋ฌ ์ค๋ฅ: {e}") |
|
import traceback |
|
logger.error(f"์คํ ํธ๋ ์ด์ค:\n{traceback.format_exc()}") |
|
|
|
error_html = f""" |
|
<div style="background: #f8d7da; border: 1px solid #f5c6cb; padding: 20px; border-radius: 8px; margin: 10px 0;"> |
|
<h4 style="color: #721c24; margin: 0 0 10px 0;"><i class="fas fa-exclamation-triangle"></i> ์์คํ
์ค๋ฅ</h4> |
|
<p style="color: #721c24; margin: 0;">๊ฐํ๋ ์ถ๋ ฅ ์ค ์์คํ
์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค:</p> |
|
<code style="display: block; margin: 10px 0; padding: 10px; background: #f8f9fa; border-radius: 3px; color: #721c24;"> |
|
{type(e).__name__}: {str(e)} |
|
</code> |
|
<div style="margin-top: 15px; padding: 10px; background: #fff3cd; border-radius: 5px;"> |
|
<p style="margin: 0; color: #856404; font-size: 14px;"> |
|
๐ก ์ค์ ๋ถ์ ๊ฒฐ๊ณผ๊ฐ ์์ด์ผ๋ง ํ์ผ์ด ์์ฑ๋ฉ๋๋ค. |
|
</p> |
|
</div> |
|
</div> |
|
""" |
|
return error_html, gr.update(visible=False) |
|
|
|
|
|
collect_data_btn.click( |
|
fn=on_collect_data, |
|
inputs=[keyword_input], |
|
outputs=[keywords_result, keywords_data_state], |
|
api_name="on_collect_data" |
|
) |
|
|
|
analyze_keyword_btn.click( |
|
fn=on_analyze_keyword, |
|
inputs=[analysis_keyword_input, keyword_input, keywords_data_state], |
|
outputs=[analysis_result, export_data_state], |
|
api_name="on_analyze_keyword" |
|
) |
|
|
|
export_btn.click( |
|
fn=on_export_results, |
|
inputs=[export_data_state], |
|
outputs=[export_result, download_file], |
|
api_name="on_export_results" |
|
) |
|
|
|
return interface |
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
try: |
|
import pytz |
|
logger.info("โ
pytz ๋ชจ๋ ๋ก๋ ์ฑ๊ณต - ํ๊ตญ์๊ฐ ์ง์") |
|
except ImportError: |
|
logger.info("์์คํ
์๊ฐ์ ์ฌ์ฉํฉ๋๋ค.") |
|
|
|
|
|
app = create_interface() |
|
app.launch(server_name="0.0.0.0", server_port=7860, share=True) |