ssboost commited on
Commit
f00f5cb
ยท
verified ยท
1 Parent(s): 7567d30

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +178 -468
app.py CHANGED
@@ -5,7 +5,6 @@ import time
5
  import threading
6
  import tempfile
7
  import logging
8
- import random
9
  import uuid
10
  import shutil
11
  import glob
@@ -19,7 +18,7 @@ logging.basicConfig(
19
  format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
20
  handlers=[
21
  logging.StreamHandler(),
22
- logging.FileHandler('main_keyword_app.log', mode='a')
23
  ]
24
  )
25
 
@@ -152,8 +151,10 @@ def cleanup_huggingface_temp_folders():
152
  # ๊ธฐ์กด ์„ธ์…˜ ํŒŒ์ผ๋“ค ์ •๋ฆฌ
153
  session_files = glob.glob(os.path.join(temp_dir, "session_*.xlsx"))
154
  session_files.extend(glob.glob(os.path.join(temp_dir, "session_*.csv")))
155
- session_files.extend(glob.glob(os.path.join(temp_dir, "*keyword*.xlsx")))
156
- session_files.extend(glob.glob(os.path.join(temp_dir, "*keyword*.csv")))
 
 
157
  session_files.extend(glob.glob(os.path.join(temp_dir, "tmp*.xlsx")))
158
  session_files.extend(glob.glob(os.path.join(temp_dir, "tmp*.csv")))
159
 
@@ -191,13 +192,13 @@ def setup_clean_temp_environment():
191
  cleanup_huggingface_temp_folders()
192
 
193
  # 2. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์šฉ ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ
194
- app_temp_dir = os.path.join(tempfile.gettempdir(), "keyword_app")
195
  if os.path.exists(app_temp_dir):
196
  shutil.rmtree(app_temp_dir, ignore_errors=True)
197
  os.makedirs(app_temp_dir, exist_ok=True)
198
 
199
  # 3. ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ • (์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์ง€์ •)
200
- os.environ['KEYWORD_APP_TEMP'] = app_temp_dir
201
 
202
  logger.info(f"โœ… ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์šฉ ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์„ค์ •: {app_temp_dir}")
203
 
@@ -209,7 +210,7 @@ def setup_clean_temp_environment():
209
 
210
  def get_app_temp_dir():
211
  """์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์šฉ ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ๋ฐ˜ํ™˜"""
212
- return os.environ.get('KEYWORD_APP_TEMP', tempfile.gettempdir())
213
 
214
  def get_session_id():
215
  """์„ธ์…˜ ID ์ƒ์„ฑ"""
@@ -274,7 +275,7 @@ def update_session_activity(session_id):
274
  def create_session_temp_file(session_id, suffix='.xlsx'):
275
  """์„ธ์…˜๋ณ„ ์ž„์‹œ ํŒŒ์ผ ์ƒ์„ฑ (์ „์šฉ ๋””๋ ‰ํ† ๋ฆฌ ์‚ฌ์šฉ)"""
276
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
277
- random_suffix = str(random.randint(1000, 9999))
278
 
279
  # ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์šฉ ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์‚ฌ์šฉ
280
  temp_dir = get_app_temp_dir()
@@ -288,152 +289,119 @@ def create_session_temp_file(session_id, suffix='.xlsx'):
288
  register_session_file(session_id, temp_file_path)
289
  return temp_file_path
290
 
291
- def wrapper_modified(keyword, korean_only, apply_main_keyword_option, exclude_zero_volume, session_id):
292
- """ํ‚ค์›Œ๋“œ ๊ฒ€์ƒ‰ ๋ฐ ์ฒ˜๋ฆฌ ๋ž˜ํผ ํ•จ์ˆ˜ (์„ธ์…˜ ID ์ถ”๊ฐ€)"""
293
  update_session_activity(session_id)
294
 
295
- # ํ˜„์žฌ ํ‚ค์›Œ๋“œ ์‚ฌ์šฉ (์„ธ์…˜๋ณ„๋กœ ๊ด€๋ฆฌ)
296
- current_keyword = keyword
297
 
298
- # ํ‚ค์›Œ๋“œ๊ฐ€ ๋น„์–ด์žˆ๋Š” ๊ฒฝ์šฐ ์ฒ˜๋ฆฌ
299
- if not keyword:
300
- return (gr.update(value=""), gr.update(choices=["์ „์ฒด ๋ณด๊ธฐ"]), gr.update(choices=["์ „์ฒด"]),
301
- None, gr.update(choices=["์ „์ฒด ๋ณด๊ธฐ"], value="์ „์ฒด ๋ณด๊ธฐ"), None,
302
- gr.update(visible=False), gr.update(visible=False), current_keyword)
303
 
304
- # ๋„ค์ด๋ฒ„ ์‡ผํ•‘ API ๊ฒ€์ƒ‰ ์ˆ˜ํ–‰
305
- search_results = product_search.fetch_naver_shopping_data(keyword, korean_only, apply_main_keyword_option == "๋ฉ”์ธํ‚ค์›Œ๋“œ ์ ์šฉ")
 
306
 
307
- # ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ
308
- if not search_results.get("product_list"):
309
- return (gr.update(value="<p>๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ํ‚ค์›Œ๋“œ๋กœ ์‹œ๋„ํ•ด๋ณด์„ธ์š”.</p>"),
310
- gr.update(choices=["์ „์ฒด ๋ณด๊ธฐ"]), gr.update(choices=["์ „์ฒด"]),
311
- None, gr.update(choices=["์ „์ฒด ๋ณด๊ธฐ"], value="์ „์ฒด ๋ณด๊ธฐ"), None,
312
- gr.update(visible=False), gr.update(visible=False), current_keyword)
313
 
314
- # ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ - ํ‚ค์›Œ๋“œ ์ „๋‹ฌ ๋ฐ ๊ฒ€์ƒ‰๋Ÿ‰ 0 ํ‚ค์›Œ๋“œ ์ œ์™ธ ์˜ต์…˜ ์ „๋‹ฌ
315
- result = keyword_processor.process_search_results(search_results, current_keyword, exclude_zero_volume)
316
 
317
- df_products = result["products_df"]
318
- df_keywords = result["keywords_df"]
319
- category_list = result["categories"]
320
-
321
- if df_keywords.empty:
322
- return (gr.update(value="<p>์ถ”์ถœ๋œ ํ‚ค์›Œ๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์˜ต์…˜์œผ๋กœ ์‹œ๋„ํ•ด๋ณด์„ธ์š”.</p>"),
323
- gr.update(choices=["์ „์ฒด ๋ณด๊ธฐ"]), gr.update(choices=["์ „์ฒด"]),
324
- df_keywords, gr.update(choices=["์ „์ฒด ๋ณด๊ธฐ"], value="์ „์ฒด ๋ณด๊ธฐ"), None,
325
- gr.update(visible=False), gr.update(visible=False), current_keyword)
326
-
327
- # HTML ํ…Œ์ด๋ธ” ์ƒ์„ฑ
328
- html = export_utils.create_table_without_checkboxes(df_keywords)
329
-
330
- # ํ•„ํ„ฐ๋ง์„ ์œ„ํ•œ ๊ณ ์œ  ๊ฐ’ ๋ฆฌ์ŠคํŠธ ์ƒ์„ฑ
331
- volume_range_choices = ["์ „์ฒด"] + sorted(df_keywords["๊ฒ€์ƒ‰๋Ÿ‰๊ตฌ๊ฐ„"].unique().tolist())
332
-
333
- # ๋ถ„์„ํ•  ์นดํ…Œ๊ณ ๋ฆฌ ๋“œ๋กญ๋‹ค์šด๋„ ๊ฐ™์€ ์„ ํƒ์ง€๋กœ ์—…๋ฐ์ดํŠธ
334
- first_category = category_list[0] if category_list else "์ „์ฒด ๋ณด๊ธฐ"
335
 
336
- # ์„ธ์…˜๋ณ„ ์—‘์…€ ํŒŒ์ผ ์ƒ์„ฑ
337
- excel_path = create_session_excel_file(df_keywords, session_id)
 
338
 
339
- # ๋ถ„์„ ์„น์…˜ ํ‘œ์‹œ
340
- return (gr.update(value=html), gr.update(choices=category_list), gr.update(choices=volume_range_choices),
341
- df_keywords, gr.update(choices=category_list, value=first_category), excel_path,
342
- gr.update(visible=True), gr.update(visible=True), current_keyword)
343
-
344
- def create_session_excel_file(df, session_id):
345
- """์„ธ์…˜๋ณ„ ์—‘์…€ ํŒŒ์ผ ์ƒ์„ฑ"""
346
  try:
347
- excel_path = create_session_temp_file(session_id, '.xlsx')
348
- df.to_excel(excel_path, index=False, engine='openpyxl')
349
- logger.info(f"์„ธ์…˜ {session_id[:8]}... ์—‘์…€ ํŒŒ์ผ ์ƒ์„ฑ: {excel_path}")
350
- return excel_path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
  except Exception as e:
352
- logger.error(f"์„ธ์…˜๋ณ„ ์—‘์…€ ํŒŒ์ผ ์ƒ์„ฑ ์˜ค๋ฅ˜: {e}")
 
 
353
  return None
354
 
355
- def analyze_with_auto_download(analysis_keywords, selected_category, state_df, session_id):
356
- """์นดํ…Œ๊ณ ๋ฆฌ ์ผ์น˜ ๋ถ„์„ ์‹คํ–‰ ๋ฐ ์ž๋™ ๋‹ค์šด๋กœ๋“œ (์„ธ์…˜ ID ์ถ”๊ฐ€)"""
357
- update_session_activity(session_id)
358
-
359
- # ๋ถ„์„ํ•  ํ‚ค์›Œ๋“œ๋‚˜ ์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ
360
- if not analysis_keywords or not selected_category:
361
- return "ํ‚ค์›Œ๋“œ์™€ ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ๋ชจ๋‘ ์„ ํƒํ•ด์ฃผ์„ธ์š”.", None, gr.update(visible=False)
362
-
363
- # ๋ถ„์„ ์‹คํ–‰ - ๋™์  ๋กœ๋”ฉ๋œ category_analysis ๋ชจ๋“ˆ ์‚ฌ์šฉ
364
- analysis_result = category_analysis.analyze_keywords_by_category(analysis_keywords, selected_category, state_df)
365
-
366
- # ์„ธ์…˜๋ณ„ ์—‘์…€ ํŒŒ์ผ ์ƒ์„ฑ
367
- excel_path = create_session_excel_file(state_df, session_id)
368
-
369
- # ๋ถ„์„ ๊ฒฐ๊ณผ ์ถœ๋ ฅ ์„น์…˜ ํ‘œ์‹œ
370
- return analysis_result, excel_path, gr.update(visible=True)
371
-
372
- def filter_and_sort_table(df, selected_cat, keyword_sort, total_volume_sort, usage_count_sort, selected_volume_range, exclude_zero_volume, session_id):
373
- """ํ…Œ์ด๋ธ” ํ•„ํ„ฐ๋ง ๋ฐ ์ •๋ ฌ ํ•จ์ˆ˜ (์„ธ์…˜ ID ์ถ”๊ฐ€)"""
374
- update_session_activity(session_id)
375
-
376
- if df is None or df.empty:
377
- return ""
378
-
379
- # ํ•„ํ„ฐ๋ง ์ ์šฉ
380
- filtered_df = df.copy()
381
-
382
- # ์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ ์ ์šฉ
383
- if selected_cat and selected_cat != "์ „์ฒด ๋ณด๊ธฐ":
384
- cat_name_to_filter = selected_cat.rsplit(" (", 1)[0]
385
- filtered_df = filtered_df[filtered_df["๊ด€๋ จ ์นดํ…Œ๊ณ ๋ฆฌ"].astype(str).str.contains(cat_name_to_filter, case=False, na=False)]
386
-
387
- def get_filtered_category_display(current_categories_str):
388
- if pd.isna(current_categories_str):
389
- return ""
390
-
391
- categories = str(current_categories_str).split('\n')
392
- matched_categories = [cat for cat in categories if cat_name_to_filter.lower() in cat.lower()]
393
- if matched_categories:
394
- return "\n".join(matched_categories)
395
-
396
- return current_categories_str
397
-
398
- filtered_df['๊ด€๋ จ ์นดํ…Œ๊ณ ๋ฆฌ'] = filtered_df['๊ด€๋ จ ์นดํ…Œ๊ณ ๋ฆฌ'].apply(get_filtered_category_display)
399
-
400
- # ๊ฒ€์ƒ‰๋Ÿ‰ ๊ตฌ๊ฐ„ ํ•„ํ„ฐ ์ ์šฉ
401
- if selected_volume_range and selected_volume_range != "์ „์ฒด":
402
- filtered_df = filtered_df[filtered_df["๊ฒ€์ƒ‰๋Ÿ‰๊ตฌ๊ฐ„"] == selected_volume_range]
403
-
404
- # ๊ฒ€์ƒ‰๋Ÿ‰ 0 ์ œ์™ธ ํ•„ํ„ฐ ์ ์šฉ
405
- if exclude_zero_volume:
406
- filtered_df = filtered_df[filtered_df["์ด๊ฒ€์ƒ‰๋Ÿ‰"] > 0]
407
- logger.info(f"์„ธ์…˜ {session_id[:8]}... ๊ฒ€์ƒ‰๋Ÿ‰ 0 ์ œ์™ธ ํ•„ํ„ฐ ์ ์šฉ - ๋‚จ์€ ํ‚ค์›Œ๋“œ ์ˆ˜: {len(filtered_df)}")
408
-
409
- # ์ •๋ ฌ ์ ์šฉ
410
- if keyword_sort != "์ •๋ ฌ ์—†์Œ":
411
- is_ascending = keyword_sort == "์˜ค๋ฆ„์ฐจ์ˆœ"
412
- filtered_df = filtered_df.sort_values(by="์กฐํ•ฉ ํ‚ค์›Œ๋“œ", ascending=is_ascending)
413
-
414
- if total_volume_sort != "์ •๋ ฌ ์—†์Œ":
415
- is_ascending = total_volume_sort == "์˜ค๋ฆ„์ฐจ์ˆœ"
416
- filtered_df = filtered_df.sort_values(by="์ด๊ฒ€์ƒ‰๋Ÿ‰", ascending=is_ascending)
417
-
418
- # ํ‚ค์›Œ๋“œ ์‚ฌ์šฉํšŸ์ˆ˜ ์ •๋ ฌ ์ ์šฉ
419
- if usage_count_sort != "์ •๋ ฌ ์—†์Œ":
420
- is_ascending = usage_count_sort == "์˜ค๋ฆ„์ฐจ์ˆœ"
421
- filtered_df = filtered_df.sort_values(by="ํ‚ค์›Œ๋“œ ์‚ฌ์šฉํšŸ์ˆ˜", ascending=is_ascending)
422
-
423
- # ์ˆœ๋ฒˆ์„ 1๋ถ€ํ„ฐ ์ˆœ์ฐจ์ ์œผ๋กœ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ํ–‰ ์ธ๋ฑ์Šค ์žฌ์„ค์ •
424
- filtered_df = filtered_df.reset_index(drop=True)
425
-
426
- # ์ˆœ๋ฒˆ์„ ํฌํ•จํ•œ HTML ํ…Œ์ด๋ธ” ์ƒ์„ฑ
427
- html = export_utils.create_table_without_checkboxes(filtered_df)
428
-
429
- return html
430
-
431
- def update_category_selection(selected_cat, session_id):
432
- """์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ ์„ ํƒ ์‹œ ๋ถ„์„ํ•  ์นดํ…Œ๊ณ ๋ฆฌ๋„ ๊ฐ™์€ ๊ฐ’์œผ๋กœ ์—…๏ฟฝ๏ฟฝ์ดํŠธ"""
433
- update_session_activity(session_id)
434
- logger.debug(f"์„ธ์…˜ {session_id[:8]}... ์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ ๋ณ€๊ฒฝ: {selected_cat}")
435
- return gr.update(value=selected_cat)
436
-
437
  def reset_interface(session_id):
438
  """์ธํ„ฐํŽ˜์ด์Šค ๋ฆฌ์…‹ ํ•จ์ˆ˜ - ์„ธ์…˜๋ณ„ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™”"""
439
  update_session_activity(session_id)
@@ -450,69 +418,24 @@ def reset_interface(session_id):
450
  session_temp_files[session_id] = []
451
 
452
  return (
453
- "", # ๊ฒ€์ƒ‰ ํ‚ค์›Œ๋“œ
454
- True, # ํ•œ๊ธ€๋งŒ ์ถ”์ถœ
455
- False, # ๊ฒ€์ƒ‰๋Ÿ‰ 0 ํ‚ค์›Œ๋“œ ์ œ์™ธ
456
- "๋ฉ”์ธํ‚ค์›Œ๋“œ ์ ์šฉ", # ์กฐํ•ฉ ๋ฐฉ์‹
457
- "", # HTML ํ…Œ์ด๋ธ”
458
- ["์ „์ฒด ๋ณด๊ธฐ"], # ์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ
459
- "์ „์ฒด ๋ณด๊ธฐ", # ์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ ์„ ํƒ
460
- ["์ „์ฒด"], # ๊ฒ€์ƒ‰๋Ÿ‰ ๊ตฌ๊ฐ„ ํ•„ํ„ฐ
461
- "์ „์ฒด", # ๊ฒ€์ƒ‰๋Ÿ‰ ๊ตฌ๊ฐ„ ์„ ํƒ
462
- "์ •๋ ฌ ์—†์Œ", # ์ด๊ฒ€์ƒ‰๋Ÿ‰ ์ •๋ ฌ
463
- "์ •๋ ฌ ์—†์Œ", # ํ‚ค์›Œ๋“œ ์‚ฌ์šฉํšŸ์ˆ˜ ์ •๋ ฌ
464
- None, # ์ƒํƒœ DataFrame
465
- ["์ „์ฒด ๋ณด๊ธฐ"], # ๋ถ„์„ํ•  ์นดํ…Œ๊ณ ๋ฆฌ
466
- "์ „์ฒด ๋ณด๊ธฐ", # ๋ถ„์„ํ•  ์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ
467
- "", # ํ‚ค์›Œ๋“œ ์ž…๋ ฅ
468
- "", # ๋ถ„์„ ๊ฒฐ๊ณผ
469
- None, # ๋‹ค์šด๋กœ๋“œ ํŒŒ์ผ
470
- gr.update(visible=False), # ํ‚ค์›Œ๋“œ ๋ถ„์„ ์„น์…˜
471
- gr.update(visible=False), # ๋ถ„์„ ๊ฒฐ๊ณผ ์ถœ๋ ฅ ์„น์…˜
472
- "" # ํ‚ค์›Œ๋“œ ์ƒํƒœ
473
- )
474
-
475
- # ๋ž˜ํผ ํ•จ์ˆ˜๋“ค๋„ ์„ธ์…˜ ID ์ถ”๊ฐ€
476
- def search_with_loading(keyword, korean_only, apply_main_keyword, exclude_zero_volume, session_id):
477
- update_session_activity(session_id)
478
- return (
479
- gr.update(visible=True),
480
- gr.update(visible=False)
481
- )
482
-
483
- def process_search_results(keyword, korean_only, apply_main_keyword, exclude_zero_volume, session_id):
484
- update_session_activity(session_id)
485
-
486
- result = wrapper_modified(keyword, korean_only, apply_main_keyword, exclude_zero_volume, session_id)
487
-
488
- table_html, cat_choices, vol_choices, df, selected_cat, excel, keyword_section_vis, cat_section_vis, new_keyword_state = result
489
-
490
- if not isinstance(df, type(None)) and not df.empty:
491
- empty_placeholder_vis = False
492
- keyword_section_visibility = True
493
- execution_section_visibility = True
494
- else:
495
- empty_placeholder_vis = True
496
- keyword_section_visibility = False
497
- execution_section_visibility = False
498
-
499
- return (
500
- table_html, cat_choices, vol_choices, df, selected_cat, excel,
501
- gr.update(visible=keyword_section_visibility),
502
- gr.update(visible=cat_section_vis),
503
- gr.update(visible=False),
504
- gr.update(visible=empty_placeholder_vis),
505
- gr.update(visible=execution_section_visibility),
506
- new_keyword_state
507
  )
508
 
509
- def analyze_with_loading(analysis_keywords, selected_category, state_df, session_id):
 
510
  update_session_activity(session_id)
511
  return gr.update(visible=True)
512
 
513
- def process_analyze_results(analysis_keywords, selected_category, state_df, session_id):
 
514
  update_session_activity(session_id)
515
- results = analyze_with_auto_download(analysis_keywords, selected_category, state_df, session_id)
 
516
  return results + (gr.update(visible=False),)
517
 
518
  # ์„ธ์…˜ ์ •๋ฆฌ ์Šค์ผ€์ค„๋Ÿฌ
@@ -529,7 +452,7 @@ def start_session_cleanup_scheduler():
529
 
530
  def cleanup_on_startup():
531
  """์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์‹œ ์ „์ฒด ์ •๋ฆฌ"""
532
- logger.info("๐Ÿงน ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ - ์ดˆ๊ธฐ ์ •๋ฆฌ ์ž‘์—… ์‹œ์ž‘...")
533
 
534
  # 1. ํ—ˆ๊น…ํŽ˜์ด์Šค ์ž„์‹œ ํด๋” ์ •๋ฆฌ
535
  cleanup_huggingface_temp_folders()
@@ -548,6 +471,7 @@ def cleanup_on_startup():
548
 
549
  # Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ
550
  def create_app():
 
551
  fontawesome_html = """
552
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
553
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css">
@@ -559,6 +483,7 @@ def create_app():
559
  with open('style.css', 'r', encoding='utf-8') as f:
560
  custom_css = f.read()
561
  except:
 
562
  custom_css = """
563
  :root {
564
  --primary-color: #FB7F0D;
@@ -631,30 +556,6 @@ def create_app():
631
  50% { width: 70%; }
632
  100% { width: 10%; }
633
  }
634
- .empty-table {
635
- width: 100%;
636
- border-collapse: collapse;
637
- font-size: 14px;
638
- margin-top: 20px;
639
- }
640
- .empty-table th {
641
- background-color: #FB7F0D;
642
- color: white;
643
- text-align: left;
644
- padding: 12px;
645
- border: 1px solid #ddd;
646
- }
647
- .empty-table td {
648
- padding: 10px;
649
- border: 1px solid #ddd;
650
- text-align: center;
651
- color: #999;
652
- }
653
- .button-container {
654
- margin-top: 20px;
655
- display: flex;
656
- gap: 15px;
657
- }
658
  .execution-section {
659
  margin-top: 20px;
660
  background-color: #f9f9f9;
@@ -683,56 +584,47 @@ def create_app():
683
  # ์„ธ์…˜ ID ์ƒํƒœ (๊ฐ ์‚ฌ์šฉ์ž๋ณ„๋กœ ๊ณ ์œ )
684
  session_id = gr.State(get_session_id)
685
 
686
- # ํ‚ค์›Œ๋“œ ์ƒํƒœ ๊ด€๋ฆฌ
687
- keyword_state = gr.State("")
688
-
689
  # ์ž…๋ ฅ ์„น์…˜
690
  with gr.Column(elem_classes="custom-frame fade-in"):
691
- gr.HTML('<div class="section-title"><i class="fas fa-search"></i> ๊ฒ€์ƒ‰ ์ž…๋ ฅ</div>')
692
 
 
693
  with gr.Row():
694
  with gr.Column(scale=1):
695
- keyword = gr.Textbox(
696
  label="๋ฉ”์ธ ํ‚ค์›Œ๋“œ",
697
  placeholder="์˜ˆ: ์˜ค์ง•์–ด"
698
  )
699
  with gr.Column(scale=1):
700
- search_btn = gr.Button(
701
- "๋ฉ”์ธํ‚ค์›Œ๋“œ ๋ถ„์„",
702
- elem_classes="custom-button"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
703
  )
704
-
705
- with gr.Accordion("์˜ต์…˜ ์„ค์ •", open=False):
706
- with gr.Row():
707
- with gr.Column(scale=1):
708
- korean_only = gr.Checkbox(
709
- label="ํ•œ๊ธ€๋งŒ ์ถ”์ถœ",
710
- value=True
711
- )
712
- with gr.Column(scale=1):
713
- exclude_zero_volume = gr.Checkbox(
714
- label="๊ฒ€์ƒ‰๋Ÿ‰ 0 ํ‚ค์›Œ๋“œ ์ œ์™ธ",
715
- value=False
716
- )
717
-
718
- with gr.Row():
719
- with gr.Column(scale=1):
720
- apply_main_keyword = gr.Radio(
721
- ["๋ฉ”์ธํ‚ค์›Œ๋“œ ์ ์šฉ", "๋ฉ”์ธํ‚ค์›Œ๋“œ ๋ฏธ์ ์šฉ"],
722
- label="์กฐํ•ฉ ๋ฐฉ์‹",
723
- value="๋ฉ”์ธํ‚ค์›Œ๋“œ ์ ์šฉ"
724
- )
725
- with gr.Column(scale=1):
726
- gr.HTML("")
727
 
728
- # ์ง„ํ–‰ ์ƒํƒœ ํ‘œ์‹œ ์„น์…˜
729
  with gr.Column(elem_classes="custom-frame fade-in", visible=False) as progress_section:
730
  gr.HTML('<div class="section-title"><i class="fas fa-spinner"></i> ๋ถ„์„ ์ง„ํ–‰ ์ƒํƒœ</div>')
 
731
  progress_html = gr.HTML("""
732
  <div style="padding: 15px; background-color: #f9f9f9; border-radius: 5px; margin: 10px 0; border: 1px solid #ddd;">
733
  <div style="margin-bottom: 10px; display: flex; align-items: center;">
734
  <i class="fas fa-spinner fa-spin" style="color: #FB7F0D; margin-right: 10px;"></i>
735
- <span>ํ‚ค์›Œ๋“œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„์ค‘์ž…๋‹ˆ๋‹ค. ์ž ์‹œ๋งŒ ๊ธฐ๋‹ค๋ ค์ฃผ์„ธ์š”...</span>
736
  </div>
737
  <div style="background-color: #e9ecef; height: 10px; border-radius: 5px; overflow: hidden;">
738
  <div class="progress-bar"></div>
@@ -740,219 +632,43 @@ def create_app():
740
  </div>
741
  """)
742
 
743
- # ๋ฉ”์ธํ‚ค์›Œ๋“œ ๋ถ„์„ ๊ฒฐ๊ณผ ์„น์…˜
744
- with gr.Column(elem_classes="custom-frame fade-in") as main_keyword_section:
745
- gr.HTML('<div class="section-title"><i class="fas fa-table"></i> ๋ฉ”์ธํ‚ค์›Œ๋“œ ๋ถ„์„ ๊ฒฐ๊ณผ</div>')
746
-
747
- empty_table_html = gr.HTML("""
748
- <table class="empty-table">
749
- <thead>
750
- <tr>
751
- <th>์ˆœ๋ฒˆ</th>
752
- <th>์กฐํ•ฉ ํ‚ค์›Œ๋“œ</th>
753
- <th>PC๊ฒ€์ƒ‰๋Ÿ‰</th>
754
- <th>๋ชจ๋ฐ”์ผ๊ฒ€์ƒ‰๋Ÿ‰</th>
755
- <th>์ด๊ฒ€์ƒ‰๋Ÿ‰</th>
756
- <th>๊ฒ€์ƒ‰๋Ÿ‰๊ตฌ๊ฐ„</th>
757
- <th>ํ‚ค์›Œ๋“œ ์‚ฌ์šฉ์ž์ˆœ์œ„</th>
758
- <th>ํ‚ค์›Œ๋“œ ์‚ฌ์šฉํšŸ์ˆ˜</th>
759
- <th>์ƒํ’ˆ ๋“ฑ๋ก ์นดํ…Œ๊ณ ๋ฆฌ</th>
760
- </tr>
761
- </thead>
762
- <tbody>
763
- <tr>
764
- <td colspan="9" style="padding: 30px; text-align: center;">
765
- ๊ฒ€์ƒ‰์„ ์‹คํ–‰ํ•˜๋ฉด ์—ฌ๊ธฐ์— ๊ฒฐ๊ณผ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค
766
- </td>
767
- </tr>
768
- </tbody>
769
- </table>
770
- """)
771
-
772
- with gr.Column(visible=False) as keyword_analysis_section:
773
- with gr.Row():
774
- with gr.Column(scale=1):
775
- category_filter = gr.Dropdown(
776
- choices=["์ „์ฒด ๋ณด๊ธฐ"],
777
- label="์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ",
778
- value="์ „์ฒด ๋ณด๊ธฐ",
779
- interactive=True
780
- )
781
- with gr.Column(scale=1):
782
- total_volume_sort = gr.Dropdown(
783
- choices=["์ •๋ ฌ ์—†์Œ", "์˜ค๋ฆ„์ฐจ์ˆœ", "๋‚ด๋ฆผ์ฐจ์ˆœ"],
784
- label="์ด๊ฒ€์ƒ‰๋Ÿ‰ ์ •๋ ฌ",
785
- value="์ •๋ ฌ ์—†์Œ",
786
- interactive=True
787
- )
788
-
789
- with gr.Row():
790
- with gr.Column(scale=1):
791
- search_volume_filter = gr.Dropdown(
792
- choices=["์ „์ฒด"],
793
- label="๊ฒ€์ƒ‰๋Ÿ‰ ๊ตฌ๊ฐ„ ํ•„ํ„ฐ",
794
- value="์ „์ฒด",
795
- interactive=True
796
- )
797
- with gr.Column(scale=1):
798
- usage_count_sort = gr.Dropdown(
799
- choices=["์ •๋ ฌ ์—†์Œ", "์˜ค๋ฆ„์ฐจ์ˆœ", "๋‚ด๋ฆผ์ฐจ์ˆœ"],
800
- label="ํ‚ค์›Œ๋“œ ์‚ฌ์šฉํšŸ์ˆ˜ ์ •๋ ฌ",
801
- value="์ •๋ ฌ ์—†์Œ",
802
- interactive=True
803
- )
804
-
805
- gr.HTML("<div class='data-container' id='table_container'></div>")
806
- table_output = gr.HTML(elem_classes="fade-in")
807
-
808
- # ์นดํ…Œ๊ณ ๋ฆฌ ๋ถ„์„ ์„น์…˜
809
- with gr.Column(elem_classes="custom-frame fade-in", visible=False) as category_analysis_section:
810
- gr.HTML('<div class="section-title"><i class="fas fa-chart-bar"></i> ํ‚ค์›Œ๋“œ ๋ถ„์„</div>')
811
-
812
- with gr.Row():
813
- with gr.Column(scale=1):
814
- analysis_keywords = gr.Textbox(
815
- label="ํ‚ค์›Œ๋“œ ์ž…๋ ฅ (์ตœ๋Œ€ 20๊ฐœ, ์‰ผํ‘œ ๋˜๋Š” ์—”ํ„ฐ๋กœ ๊ตฌ๋ถ„)",
816
- placeholder="์˜ˆ: ์˜ค์ง•์–ด๋ณถ์Œ, ์˜ค์ง•์–ด ์†์งˆ, ์˜ค์ง•์–ด ์š”๋ฆฌ...",
817
- lines=5
818
- )
819
-
820
- with gr.Column(scale=1):
821
- selected_category = gr.Dropdown(
822
- label="๋ถ„์„ํ•  ์นดํ…Œ๊ณ ๋ฆฌ(๋ถ„์„ ์ „ ๋ฐ˜๋“œ์‹œ ์„ ํƒํ•ด์ฃผ์„ธ์š”)",
823
- choices=["์ „์ฒด ๋ณด๊ธฐ"],
824
- value="์ „์ฒด ๋ณด๊ธฐ",
825
- interactive=True
826
- )
827
-
828
- # ์‹คํ–‰ ์„น์…˜
829
- with gr.Column(elem_classes="execution-section", visible=False) as execution_section:
830
- gr.HTML('<div class="section-title"><i class="fas fa-play-circle"></i> ์‹คํ–‰</div>')
831
- with gr.Row():
832
- with gr.Column(scale=1):
833
- analyze_btn = gr.Button(
834
- "์นดํ…Œ๊ณ ๋ฆฌ ์ผ์น˜ ๋ถ„์„",
835
- elem_classes=["execution-button", "primary-button"]
836
- )
837
- with gr.Column(scale=1):
838
- reset_btn = gr.Button(
839
- "๋ชจ๋“  ์ž…๋ ฅ ์ดˆ๊ธฐํ™”",
840
- elem_classes=["execution-button", "secondary-button"]
841
- )
842
-
843
- # ๋ถ„์„ ๊ฒฐ๊ณผ ์ถœ๋ ฅ ์„น์…˜
844
- with gr.Column(elem_classes="custom-frame fade-in", visible=False) as analysis_output_section:
845
- gr.HTML('<div class="section-title"><i class="fas fa-list-ul"></i> ๋ถ„์„ ๊ฒฐ๊ณผ ์š”์•ฝ</div>')
846
 
847
- analysis_result = gr.HTML(elem_classes="fade-in")
 
848
 
849
- with gr.Row():
850
- download_output = gr.File(
851
- label="ํ‚ค์›Œ๋“œ ๋ชฉ๋ก ๋‹ค์šด๋กœ๋“œ",
852
- visible=True
853
- )
854
-
855
- # ์ƒํƒœ ์ €์žฅ์šฉ ๋ณ€์ˆ˜
856
- state_df = gr.State()
857
-
858
- # ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ - ๋ชจ๋“  ํ•จ์ˆ˜์— session_id ์ถ”๊ฐ€
859
- search_btn.click(
860
- fn=search_with_loading,
861
- inputs=[keyword, korean_only, apply_main_keyword, exclude_zero_volume, session_id],
862
- outputs=[progress_section, empty_table_html]
863
  ).then(
864
- fn=process_search_results,
865
- inputs=[keyword, korean_only, apply_main_keyword, exclude_zero_volume, session_id],
866
  outputs=[
867
- table_output, category_filter, search_volume_filter,
868
- state_df, selected_category, download_output,
869
- keyword_analysis_section, category_analysis_section,
870
- progress_section, empty_table_html, execution_section,
871
- keyword_state
872
  ]
873
  )
874
 
875
- # ํ•„ํ„ฐ ๋ฐ ์ •๋ ฌ ๋ณ€๊ฒฝ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ - session_id ์ถ”๊ฐ€
876
- category_filter.change(
877
- fn=filter_and_sort_table,
878
- inputs=[
879
- state_df, category_filter, gr.Textbox(value="์ •๋ ฌ ์—†์Œ", visible=False),
880
- total_volume_sort, usage_count_sort,
881
- search_volume_filter, exclude_zero_volume, session_id
882
- ],
883
- outputs=[table_output]
884
- )
885
-
886
- category_filter.change(
887
- fn=update_category_selection,
888
- inputs=[category_filter, session_id],
889
- outputs=[selected_category]
890
- )
891
-
892
- total_volume_sort.change(
893
- fn=filter_and_sort_table,
894
- inputs=[
895
- state_df, category_filter, gr.Textbox(value="์ •๋ ฌ ์—†์Œ", visible=False),
896
- total_volume_sort, usage_count_sort,
897
- search_volume_filter, exclude_zero_volume, session_id
898
- ],
899
- outputs=[table_output]
900
- )
901
-
902
- usage_count_sort.change(
903
- fn=filter_and_sort_table,
904
- inputs=[
905
- state_df, category_filter, gr.Textbox(value="์ •๋ ฌ ์—†์Œ", visible=False),
906
- total_volume_sort, usage_count_sort,
907
- search_volume_filter, exclude_zero_volume, session_id
908
- ],
909
- outputs=[table_output]
910
- )
911
-
912
- search_volume_filter.change(
913
- fn=filter_and_sort_table,
914
- inputs=[
915
- state_df, category_filter, gr.Textbox(value="์ •๋ ฌ ์—†์Œ", visible=False),
916
- total_volume_sort, usage_count_sort,
917
- search_volume_filter, exclude_zero_volume, session_id
918
- ],
919
- outputs=[table_output]
920
- )
921
-
922
- exclude_zero_volume.change(
923
- fn=filter_and_sort_table,
924
- inputs=[
925
- state_df, category_filter, gr.Textbox(value="์ •๋ ฌ ์—†์Œ", visible=False),
926
- total_volume_sort, usage_count_sort,
927
- search_volume_filter, exclude_zero_volume, session_id
928
- ],
929
- outputs=[table_output]
930
- )
931
-
932
- # ์นดํ…Œ๊ณ ๋ฆฌ ๋ถ„์„ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ - session_id ์ถ”๊ฐ€
933
- analyze_btn.click(
934
- fn=analyze_with_loading,
935
- inputs=[analysis_keywords, selected_category, state_df, session_id],
936
- outputs=[progress_section]
937
- ).then(
938
- fn=process_analyze_results,
939
- inputs=[analysis_keywords, selected_category, state_df, session_id],
940
- outputs=[analysis_result, download_output, analysis_output_section, progress_section]
941
- )
942
-
943
- # ๋ฆฌ์…‹ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ - session_id ์ถ”๊ฐ€
944
  reset_btn.click(
945
  fn=reset_interface,
946
  inputs=[session_id],
947
  outputs=[
948
- keyword, korean_only, exclude_zero_volume, apply_main_keyword,
949
- table_output, category_filter, category_filter,
950
- search_volume_filter, search_volume_filter,
951
- total_volume_sort, usage_count_sort,
952
- state_df, selected_category, selected_category,
953
- analysis_keywords, analysis_result, download_output,
954
- keyword_analysis_section, analysis_output_section,
955
- keyword_state
956
  ]
957
  )
958
 
@@ -960,7 +676,7 @@ def create_app():
960
 
961
  if __name__ == "__main__":
962
  # ========== ์‹œ์ž‘ ์‹œ ์ „์ฒด ์ดˆ๊ธฐํ™” ==========
963
- logger.info("๐Ÿš€ ๋ฉ”์ธํ‚ค์›Œ๋“œ ๋ถ„์„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘...")
964
 
965
  # 1. ์ฒซ ๋ฒˆ์งธ: ํ—ˆ๊น…ํŽ˜์ด์Šค ์ž„์‹œ ํด๋” ์ •๋ฆฌ ๋ฐ ํ™˜๊ฒฝ ์„ค์ •
966
  app_temp_dir = cleanup_on_startup()
@@ -969,18 +685,12 @@ if __name__ == "__main__":
969
  start_session_cleanup_scheduler()
970
 
971
  # 3. API ์„ค์ • ์ดˆ๊ธฐํ™”
972
- try:
973
- api_utils.initialize_api_configs()
974
- except Exception as e:
975
- logger.warning(f"API ์„ค์ • ์ดˆ๊ธฐํ™” ์ค‘ ์˜ค๋ฅ˜ (๊ณ„์† ์ง„ํ–‰): {e}")
976
 
977
  # 4. Gemini ๋ชจ๋ธ ์ดˆ๊ธฐํ™”
978
- try:
979
- gemini_model = text_utils.get_gemini_model()
980
- except Exception as e:
981
- logger.warning(f"Gemini ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ์ค‘ ์˜ค๋ฅ˜ (๊ณ„์† ์ง„ํ–‰): {e}")
982
 
983
- logger.info("===== ๋ฉ€ํ‹ฐ์œ ์ € ๋ฉ”์ธํ‚ค์›Œ๋“œ ๋ถ„์„ Application Startup at %s =====", time.strftime("%Y-%m-%d %H:%M:%S"))
984
  logger.info(f"๐Ÿ“ ์ž„์‹œ ํŒŒ์ผ ์ €์žฅ ์œ„์น˜: {app_temp_dir}")
985
 
986
  # ========== ์•ฑ ์‹คํ–‰ ==========
 
5
  import threading
6
  import tempfile
7
  import logging
 
8
  import uuid
9
  import shutil
10
  import glob
 
18
  format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
19
  handlers=[
20
  logging.StreamHandler(),
21
+ logging.FileHandler('category_analysis_app.log', mode='a')
22
  ]
23
  )
24
 
 
151
  # ๊ธฐ์กด ์„ธ์…˜ ํŒŒ์ผ๋“ค ์ •๋ฆฌ
152
  session_files = glob.glob(os.path.join(temp_dir, "session_*.xlsx"))
153
  session_files.extend(glob.glob(os.path.join(temp_dir, "session_*.csv")))
154
+ session_files.extend(glob.glob(os.path.join(temp_dir, "*category*.xlsx")))
155
+ session_files.extend(glob.glob(os.path.join(temp_dir, "*category*.csv")))
156
+ session_files.extend(glob.glob(os.path.join(temp_dir, "*analysis*.xlsx")))
157
+ session_files.extend(glob.glob(os.path.join(temp_dir, "*analysis*.csv")))
158
  session_files.extend(glob.glob(os.path.join(temp_dir, "tmp*.xlsx")))
159
  session_files.extend(glob.glob(os.path.join(temp_dir, "tmp*.csv")))
160
 
 
192
  cleanup_huggingface_temp_folders()
193
 
194
  # 2. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์šฉ ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ
195
+ app_temp_dir = os.path.join(tempfile.gettempdir(), "category_analysis_app")
196
  if os.path.exists(app_temp_dir):
197
  shutil.rmtree(app_temp_dir, ignore_errors=True)
198
  os.makedirs(app_temp_dir, exist_ok=True)
199
 
200
  # 3. ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ • (์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์ง€์ •)
201
+ os.environ['CATEGORY_APP_TEMP'] = app_temp_dir
202
 
203
  logger.info(f"โœ… ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์šฉ ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์„ค์ •: {app_temp_dir}")
204
 
 
210
 
211
  def get_app_temp_dir():
212
  """์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์šฉ ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ๋ฐ˜ํ™˜"""
213
+ return os.environ.get('CATEGORY_APP_TEMP', tempfile.gettempdir())
214
 
215
  def get_session_id():
216
  """์„ธ์…˜ ID ์ƒ์„ฑ"""
 
275
  def create_session_temp_file(session_id, suffix='.xlsx'):
276
  """์„ธ์…˜๋ณ„ ์ž„์‹œ ํŒŒ์ผ ์ƒ์„ฑ (์ „์šฉ ๋””๋ ‰ํ† ๋ฆฌ ์‚ฌ์šฉ)"""
277
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
278
+ random_suffix = str(time.time_ns())[-4:]
279
 
280
  # ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์šฉ ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์‚ฌ์šฉ
281
  temp_dir = get_app_temp_dir()
 
289
  register_session_file(session_id, temp_file_path)
290
  return temp_file_path
291
 
292
+ def analyze_product_terms_wrapper(product_name, main_keyword, current_state, session_id):
293
+ """์ƒํ’ˆ๋ช… ํ‚ค์›Œ๋“œ ๋ถ„์„ ๋ž˜ํผ ํ•จ์ˆ˜ - ์„ธ์…˜ ID ์ถ”๊ฐ€"""
294
  update_session_activity(session_id)
295
 
296
+ if not product_name:
297
+ return "์ƒํ’ˆ๋ช…์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.", current_state, None, gr.update(visible=False)
298
 
299
+ # ๋ถ„์„ ์ˆ˜ํ–‰ - HTML ๊ฒฐ๊ณผ์™€ ํ‚ค์›Œ๋“œ ๋ถ„์„ ๊ฒฐ๊ณผ ํ•จ๊ป˜ ๋ฐ›๊ธฐ
300
+ result_html, keyword_results = category_analysis.analyze_product_terms(product_name, main_keyword)
 
 
 
301
 
302
+ # ์ƒˆ๋กœ์šด ์ƒํƒœ ์ƒ์„ฑ
303
+ if current_state is None or not isinstance(current_state, dict):
304
+ current_state = {}
305
 
306
+ # ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ์ƒํƒœ์— ์ถ”๊ฐ€
307
+ current_state["keyword_analysis_results"] = keyword_results
308
+ current_state["product_name"] = product_name
309
+ current_state["main_keyword"] = main_keyword
 
 
310
 
311
+ # ์„ธ์…˜๋ณ„ ์—‘์…€ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ - ์ž๋™ ๋‹ค์šด๋กœ๋“œ
312
+ excel_path = download_analysis(current_state, session_id)
313
 
314
+ # ์ถœ๋ ฅ ์„น์…˜ ํ‘œ์‹œ
315
+ return result_html, current_state, excel_path, gr.update(visible=True)
316
+
317
+ def download_analysis(result, session_id):
318
+ """์นดํ…Œ๊ณ ๋ฆฌ ๋ถ„์„ ๊ฒฐ๊ณผ ๋‹ค์šด๋กœ๋“œ (์„ธ์…˜๋ณ„)"""
319
+ update_session_activity(session_id)
 
 
 
 
 
 
 
 
 
 
 
 
320
 
321
+ if not result or not isinstance(result, dict):
322
+ logger.warning(f"์„ธ์…˜ {session_id[:8]}... ๋ถ„์„ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
323
+ return None
324
 
 
 
 
 
 
 
 
325
  try:
326
+ # ์ƒํ’ˆ๋ช… ๋ถ„์„ ๊ฒฐ๊ณผ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ
327
+ if "keyword_analysis_results" in result:
328
+ logger.info(f"์„ธ์…˜ {session_id[:8]}... ํ‚ค์›Œ๋“œ ๋ถ„์„ ๊ฒฐ๊ณผ ํฌํ•จํ•˜์—ฌ ๋‹ค์šด๋กœ๋“œ: {len(result['keyword_analysis_results'])}๊ฐœ ํ‚ค์›Œ๋“œ")
329
+
330
+ # ์„ธ์…˜๋ณ„ ์ž„์‹œ ํŒŒ์ผ ์ƒ์„ฑ
331
+ temp_filename = create_session_temp_file(session_id, '.xlsx')
332
+
333
+ # ๋ฐ์ดํ„ฐํ”„๋ ˆ์ž„ ์ƒ์„ฑ
334
+ keywords = []
335
+ pc_volumes = []
336
+ mobile_volumes = []
337
+ total_volumes = []
338
+ ranges = []
339
+ category_items = []
340
+
341
+ for kw_result in result["keyword_analysis_results"]:
342
+ keywords.append(kw_result.get("ํ‚ค์›Œ๋“œ", ""))
343
+ pc_volumes.append(kw_result.get("PC๊ฒ€์ƒ‰๋Ÿ‰", 0))
344
+ mobile_volumes.append(kw_result.get("๋ชจ๋ฐ”์ผ๊ฒ€์ƒ‰๋Ÿ‰", 0))
345
+ total_volumes.append(kw_result.get("์ด๊ฒ€์ƒ‰๋Ÿ‰", 0))
346
+ ranges.append(kw_result.get("๊ฒ€์ƒ‰๋Ÿ‰๊ตฌ๊ฐ„", ""))
347
+ category_items.append(kw_result.get("์นดํ…Œ๊ณ ๋ฆฌํ•ญ๋ชฉ", ""))
348
+
349
+ # ๋ฐ์ดํ„ฐํ”„๋ ˆ์ž„์œผ๋กœ ๋ณ€ํ™˜
350
+ df = pd.DataFrame({
351
+ "ํ‚ค์›Œ๋“œ": keywords,
352
+ "PC๊ฒ€์ƒ‰๋Ÿ‰": pc_volumes,
353
+ "๋ชจ๋ฐ”์ผ๊ฒ€์ƒ‰๋Ÿ‰": mobile_volumes,
354
+ "์ด๊ฒ€์ƒ‰๋Ÿ‰": total_volumes,
355
+ "๊ฒ€์ƒ‰๋Ÿ‰๊ตฌ๊ฐ„": ranges,
356
+ "์นดํ…Œ๊ณ ๋ฆฌํ•ญ๋ชฉ": category_items
357
+ })
358
+
359
+ with pd.ExcelWriter(temp_filename, engine="xlsxwriter") as writer:
360
+ df.to_excel(writer, sheet_name="์ƒํ’ˆ๋ช… ๊ฒ€์ฆ ๊ฒฐ๊ณผ", index=False)
361
+
362
+ ws = writer.sheets["์ƒํ’ˆ๋ช… ๊ฒ€์ฆ ๊ฒฐ๊ณผ"]
363
+
364
+ # ์ค„๋ฐ”๊ฟˆ + ์œ„์ชฝ ์ •๋ ฌ ์„œ์‹
365
+ wrap_fmt = writer.book.add_format({
366
+ "text_wrap": True,
367
+ "valign": "top"
368
+ })
369
+
370
+ # F์—ด('์นดํ…Œ๊ณ ๋ฆฌํ•ญ๋ชฉ') ์ „์ฒด์— ์„œ์‹ ์ ์šฉ + ์—ด ๋„ˆ๋น„
371
+ ws.set_column("F:F", 40, wrap_fmt)
372
+
373
+ # ์—ด ๋„ˆ๋น„ ์„ค์ •
374
+ worksheet = writer.sheets['์ƒํ’ˆ๋ช… ๊ฒ€์ฆ ๊ฒฐ๊ณผ']
375
+ worksheet.set_column('A:A', 20) # ํ‚ค์›Œ๋“œ
376
+ worksheet.set_column('B:B', 12) # PC๊ฒ€์ƒ‰๋Ÿ‰
377
+ worksheet.set_column('C:C', 12) # ๋ชจ๋ฐ”์ผ๊ฒ€์ƒ‰๋Ÿ‰
378
+ worksheet.set_column('D:D', 12) # ์ด๊ฒ€์ƒ‰๋Ÿ‰
379
+ worksheet.set_column('E:E', 12) # ๊ฒ€์ƒ‰๋Ÿ‰๊ตฌ๊ฐ„
380
+ worksheet.set_column('F:F', 40) # ์นดํ…Œ๊ณ ๋ฆฌํ•ญ๋ชฉ
381
+
382
+ # ํ—ค๋” ์„œ์‹ ์ง€์ •
383
+ header_format = writer.book.add_format({
384
+ 'bold': True,
385
+ 'bg_color': '#FB7F0D',
386
+ 'color': 'white',
387
+ 'border': 1
388
+ })
389
+
390
+ # ํ—ค๋”์— ์„œ์‹ ์ ์šฉ
391
+ for col_num, value in enumerate(df.columns.values):
392
+ worksheet.write(0, col_num, value, header_format)
393
+
394
+ logger.info(f"์„ธ์…˜ {session_id[:8]}... ์—‘์…€ ํŒŒ์ผ ์ €์žฅ ์™„๋ฃŒ: {temp_filename}")
395
+ return temp_filename
396
+ else:
397
+ logger.warning(f"์„ธ์…˜ {session_id[:8]}... ํ‚ค์›Œ๋“œ ๋ถ„์„ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
398
+ return None
399
  except Exception as e:
400
+ logger.error(f"์„ธ์…˜ {session_id[:8]}... ๋‹ค์šด๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")
401
+ import traceback
402
+ logger.error(traceback.format_exc())
403
  return None
404
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
405
  def reset_interface(session_id):
406
  """์ธํ„ฐํŽ˜์ด์Šค ๋ฆฌ์…‹ ํ•จ์ˆ˜ - ์„ธ์…˜๋ณ„ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™”"""
407
  update_session_activity(session_id)
 
418
  session_temp_files[session_id] = []
419
 
420
  return (
421
+ "", # ๋ฉ”์ธ ํ‚ค์›Œ๋“œ ์ž…๋ ฅ
422
+ "", # ์ƒํ’ˆ๋ช… ์ž…๋ ฅ
423
+ "", # ๋ถ„์„ ๊ฒฐ๊ณผ ์ถœ๋ ฅ
424
+ None, # ๋‹ค์šด๋กœ๋“œ ํŒŒ์ผ
425
+ None, # ์ƒํƒœ ๋ณ€์ˆ˜
426
+ gr.update(visible=False) # ๋ถ„์„ ๊ฒฐ๊ณผ ์„น์…˜
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
427
  )
428
 
429
+ def product_analyze_with_loading(product_name, main_keyword, current_state, session_id):
430
+ """๋กœ๋”ฉ ํ‘œ์‹œ ํ•จ์ˆ˜"""
431
  update_session_activity(session_id)
432
  return gr.update(visible=True)
433
 
434
+ def process_product_analyze(product_name, main_keyword, current_state, session_id):
435
+ """์‹ค์ œ ๋ถ„์„ ์ˆ˜ํ–‰"""
436
  update_session_activity(session_id)
437
+ results = analyze_product_terms_wrapper(product_name, main_keyword, current_state, session_id)
438
+ # ๋กœ๋”ฉ ์ธ๋””์ผ€์ดํ„ฐ ์ˆจ๊ธฐ๊ธฐ
439
  return results + (gr.update(visible=False),)
440
 
441
  # ์„ธ์…˜ ์ •๋ฆฌ ์Šค์ผ€์ค„๋Ÿฌ
 
452
 
453
  def cleanup_on_startup():
454
  """์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์‹œ ์ „์ฒด ์ •๋ฆฌ"""
455
+ logger.info("๐Ÿงน ์นดํ…Œ๊ณ ๋ฆฌ ๋ถ„์„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ - ์ดˆ๊ธฐ ์ •๋ฆฌ ์ž‘์—… ์‹œ์ž‘...")
456
 
457
  # 1. ํ—ˆ๊น…ํŽ˜์ด์Šค ์ž„์‹œ ํด๋” ์ •๋ฆฌ
458
  cleanup_huggingface_temp_folders()
 
471
 
472
  # Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ
473
  def create_app():
474
+ # FontAwesome ์•„์ด์ฝ˜ ํฌํ•จ
475
  fontawesome_html = """
476
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
477
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css">
 
483
  with open('style.css', 'r', encoding='utf-8') as f:
484
  custom_css = f.read()
485
  except:
486
+ # CSS ํŒŒ์ผ์ด ์—†๋Š” ๊ฒฝ์šฐ ๊ธฐ๋ณธ ์Šคํƒ€์ผ ์‚ฌ์šฉ
487
  custom_css = """
488
  :root {
489
  --primary-color: #FB7F0D;
 
556
  50% { width: 70%; }
557
  100% { width: 10%; }
558
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
559
  .execution-section {
560
  margin-top: 20px;
561
  background-color: #f9f9f9;
 
584
  # ์„ธ์…˜ ID ์ƒํƒœ (๊ฐ ์‚ฌ์šฉ์ž๋ณ„๋กœ ๊ณ ์œ )
585
  session_id = gr.State(get_session_id)
586
 
 
 
 
587
  # ์ž…๋ ฅ ์„น์…˜
588
  with gr.Column(elem_classes="custom-frame fade-in"):
589
+ gr.HTML('<div class="section-title"><i class="fas fa-tag"></i> ์ƒํ’ˆ๋ช… ๋ถ„์„ ์ž…๋ ฅ</div>')
590
 
591
+ # ๋ฉ”์ธ ํ‚ค์›Œ๋“œ์™€ ์ƒํ’ˆ๋ช… ์ž…๋ ฅ์„ ํ•œ ์ค„์— ๋ฐฐ์น˜
592
  with gr.Row():
593
  with gr.Column(scale=1):
594
+ main_keyword = gr.Textbox(
595
  label="๋ฉ”์ธ ํ‚ค์›Œ๋“œ",
596
  placeholder="์˜ˆ: ์˜ค์ง•์–ด"
597
  )
598
  with gr.Column(scale=1):
599
+ product_name = gr.Textbox(
600
+ label="์ƒํ’ˆ๋ช…",
601
+ placeholder="์˜ˆ: ์†์งˆ ์˜ค์ง•์–ด ์ด‰์ด‰ํ•œ ์ง„๋ฏธ์ฑ„"
602
+ )
603
+
604
+ # ์‹คํ–‰ ์„น์…˜ - ๋ฒ„ํŠผ ํ†ตํ•ฉ
605
+ with gr.Column(elem_classes="execution-section"):
606
+ gr.HTML('<div class="section-title"><i class="fas fa-play-circle"></i> ์‹คํ–‰</div>')
607
+ with gr.Row():
608
+ with gr.Column(scale=1):
609
+ analyze_product_btn = gr.Button(
610
+ "์ƒํ’ˆ๋ช… ๋ถ„์„",
611
+ elem_classes=["execution-button", "primary-button"]
612
+ )
613
+ with gr.Column(scale=1):
614
+ reset_btn = gr.Button(
615
+ "๋ชจ๋“  ์ž…๋ ฅ ์ดˆ๊ธฐํ™”",
616
+ elem_classes=["execution-button", "secondary-button"]
617
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
618
 
619
+ # ์ง„ํ–‰ ์ƒํƒœ ํ‘œ์‹œ ์„น์…˜ (์ดˆ๊ธฐ์—๋Š” ์ˆจ๊น€)
620
  with gr.Column(elem_classes="custom-frame fade-in", visible=False) as progress_section:
621
  gr.HTML('<div class="section-title"><i class="fas fa-spinner"></i> ๋ถ„์„ ์ง„ํ–‰ ์ƒํƒœ</div>')
622
+ # ์‚ฌ์šฉ์ž ์นœํ™”์ ์ธ ์ง„ํ–‰ ์ƒํƒœ ํ‘œ์‹œ
623
  progress_html = gr.HTML("""
624
  <div style="padding: 15px; background-color: #f9f9f9; border-radius: 5px; margin: 10px 0; border: 1px solid #ddd;">
625
  <div style="margin-bottom: 10px; display: flex; align-items: center;">
626
  <i class="fas fa-spinner fa-spin" style="color: #FB7F0D; margin-right: 10px;"></i>
627
+ <span>์ƒํ’ˆ๋ช… ๋ถ„์„์ค‘์ž…๋‹ˆ๋‹ค. ์ž ์‹œ๋งŒ ๊ธฐ๋‹ค๋ ค์ฃผ์„ธ์š”...</span>
628
  </div>
629
  <div style="background-color: #e9ecef; height: 10px; border-radius: 5px; overflow: hidden;">
630
  <div class="progress-bar"></div>
 
632
  </div>
633
  """)
634
 
635
+ # ์ƒํ’ˆ๋ช… ํ‚ค์›Œ๋“œ ๋ถ„์„ ๊ฒฐ๊ณผ ์„น์…˜ (์ดˆ๊ธฐ์—๋Š” ์ˆจ๊น€)
636
+ with gr.Column(elem_classes="custom-frame fade-in", visible=False) as product_analysis_section:
637
+ gr.HTML('<div class="section-title"><i class="fas fa-table"></i> ์ƒํ’ˆ๋ช… ํ‚ค์›Œ๋“œ ๋ถ„์„ ๊ฒฐ๊ณผ</div>')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
638
 
639
+ # ์ƒํ’ˆ๋ช… ๋ถ„์„ ๊ฒฐ๊ณผ
640
+ product_analysis_result = gr.HTML(elem_classes="fade-in")
641
 
642
+ # ์—‘์…€ ๋‹ค์šด๋กœ๋“œ ํŒŒ์ผ
643
+ download_file = gr.File(
644
+ label="๋ถ„์„ ๊ฒฐ๊ณผ ๋‹ค์šด๋กœ๋“œ",
645
+ visible=True
646
+ )
647
+
648
+ # ์ƒํƒœ ์ €์žฅ์šฉ ๋ณ€์ˆ˜ - ๋ถ„์„ ๊ฒฐ๊ณผ ์ €์žฅ
649
+ analysis_result_state = gr.State()
650
+
651
+ # ์ƒํ’ˆ๋ช… ๋ถ„์„ ๋ฒ„ํŠผ ์—ฐ๊ฒฐ - ๋กœ๋”ฉ ํ‘œ์‹œ ํ›„ ์ž๋™ ๋‹ค์šด๋กœ๋“œ (์„ธ์…˜ ID ์ถ”๊ฐ€)
652
+ analyze_product_btn.click(
653
+ fn=product_analyze_with_loading,
654
+ inputs=[product_name, main_keyword, analysis_result_state, session_id],
655
+ outputs=[progress_section]
656
  ).then(
657
+ fn=process_product_analyze,
658
+ inputs=[product_name, main_keyword, analysis_result_state, session_id],
659
  outputs=[
660
+ product_analysis_result, analysis_result_state,
661
+ download_file, product_analysis_section, progress_section
 
 
 
662
  ]
663
  )
664
 
665
+ # ๋ฆฌ์…‹ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ (์„ธ์…˜ ID ์ถ”๊ฐ€)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
666
  reset_btn.click(
667
  fn=reset_interface,
668
  inputs=[session_id],
669
  outputs=[
670
+ main_keyword, product_name, product_analysis_result,
671
+ download_file, analysis_result_state, product_analysis_section
 
 
 
 
 
 
672
  ]
673
  )
674
 
 
676
 
677
  if __name__ == "__main__":
678
  # ========== ์‹œ์ž‘ ์‹œ ์ „์ฒด ์ดˆ๊ธฐํ™” ==========
679
+ logger.info("๐Ÿš€ ์นดํ…Œ๊ณ ๋ฆฌ ๋ถ„์„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘...")
680
 
681
  # 1. ์ฒซ ๋ฒˆ์งธ: ํ—ˆ๊น…ํŽ˜์ด์Šค ์ž„์‹œ ํด๋” ์ •๋ฆฌ ๋ฐ ํ™˜๊ฒฝ ์„ค์ •
682
  app_temp_dir = cleanup_on_startup()
 
685
  start_session_cleanup_scheduler()
686
 
687
  # 3. API ์„ค์ • ์ดˆ๊ธฐํ™”
688
+ api_utils.initialize_api_configs()
 
 
 
689
 
690
  # 4. Gemini ๋ชจ๋ธ ์ดˆ๊ธฐํ™”
691
+ gemini_model = text_utils.get_gemini_model()
 
 
 
692
 
693
+ logger.info("===== ๋ฉ€ํ‹ฐ์œ ์ € ์นดํ…Œ๊ณ ๋ฆฌ ๋ถ„์„ Application Startup at %s =====", time.strftime("%Y-%m-%d %H:%M:%S"))
694
  logger.info(f"๐Ÿ“ ์ž„์‹œ ํŒŒ์ผ ์ €์žฅ ์œ„์น˜: {app_temp_dir}")
695
 
696
  # ========== ์•ฑ ์‹คํ–‰ ==========