ssboost commited on
Commit
304e207
ยท
verified ยท
1 Parent(s): b5cd001

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +394 -487
app.py CHANGED
@@ -10,7 +10,8 @@ import uuid
10
  import shutil
11
  import glob
12
  from datetime import datetime
13
- from gradio_client import Client
 
14
 
15
  # ๋กœ๊น… ์„ค์ •
16
  logging.basicConfig(
@@ -18,34 +19,112 @@ logging.basicConfig(
18
  format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
19
  handlers=[
20
  logging.StreamHandler(),
21
- logging.FileHandler('control_tower_app.log', mode='a')
22
  ]
23
  )
24
 
25
  logger = logging.getLogger(__name__)
26
 
27
- # API ํด๋ผ์ด์–ธํŠธ ์„ค์ •
28
- def get_client():
29
- # ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ API ์—”๋“œํฌ์ธํŠธ ์ฝ๊ธฐ
30
- endpoint = os.environ.get('API_ENDPOINT', '')
31
-
32
- # ํ™˜๊ฒฝ๋ณ€์ˆ˜์— ํŒŒ์ผ ๋‚ด์šฉ์ด๋‚˜ ๋ถˆํ•„์š”ํ•œ ํ…์ŠคํŠธ๊ฐ€ ๋“ค์–ด์˜จ ๊ฒฝ์šฐ ์ •๋ฆฌ
33
- if endpoint:
34
- # ์ค„๋ฐ”๊ฟˆ์œผ๋กœ ๋ถ„๋ฆฌํ•ด์„œ ์ฒซ ๋ฒˆ์งธ ์œ ํšจํ•œ ๋ผ์ธ ์ฐพ๊ธฐ
35
- lines = endpoint.split('\n')
36
- for line in lines:
37
- line = line.strip()
38
- # ์ฃผ์„์ด๋‚˜ ๋น„์–ด์žˆ๋Š” ๋ผ์ธ ์ œ์™ธ
39
- if line and not line.startswith('#') and '/' in line:
40
- # API_ENDPOINT= ๊ฐ™์€ ํ‚ค๊ฐ’ ์ œ๊ฑฐ
41
- if '=' in line:
42
- line = line.split('=', 1)[1].strip()
43
- # ๋”ฐ์˜ดํ‘œ ์ œ๊ฑฐ
44
- line = line.strip('"\'')
45
- if line and '/' in line and len(line) < 50:
46
- return Client(line)
47
-
48
- raise ValueError("์˜ฌ๋ฐ”๋ฅธ API_ENDPOINT๋ฅผ ์„ค์ •ํ•ด์ฃผ์„ธ์š” (์˜ˆ: username/repo-name)")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
  # ์„ธ์…˜๋ณ„ ์ž„์‹œ ํŒŒ์ผ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ๋”•์…”๋„ˆ๋ฆฌ
51
  session_temp_files = {}
@@ -54,6 +133,7 @@ session_data = {}
54
  def cleanup_huggingface_temp_folders():
55
  """ํ—ˆ๊น…ํŽ˜์ด์Šค ์ž„์‹œ ํด๋” ์ดˆ๊ธฐ ์ •๋ฆฌ"""
56
  try:
 
57
  temp_dirs = [
58
  tempfile.gettempdir(),
59
  "/tmp",
@@ -69,6 +149,7 @@ def cleanup_huggingface_temp_folders():
69
  for temp_dir in temp_dirs:
70
  if os.path.exists(temp_dir):
71
  try:
 
72
  session_files = glob.glob(os.path.join(temp_dir, "session_*.xlsx"))
73
  session_files.extend(glob.glob(os.path.join(temp_dir, "session_*.csv")))
74
  session_files.extend(glob.glob(os.path.join(temp_dir, "*keyword*.xlsx")))
@@ -78,6 +159,7 @@ def cleanup_huggingface_temp_folders():
78
 
79
  for file_path in session_files:
80
  try:
 
81
  if os.path.getmtime(file_path) < time.time() - 3600:
82
  os.remove(file_path)
83
  cleanup_count += 1
@@ -90,6 +172,7 @@ def cleanup_huggingface_temp_folders():
90
 
91
  logger.info(f"โœ… ํ—ˆ๊น…ํŽ˜์ด์Šค ์ž„์‹œ ํด๋” ์ดˆ๊ธฐ ์ •๋ฆฌ ์™„๋ฃŒ - {cleanup_count}๊ฐœ ํŒŒ์ผ ์‚ญ์ œ")
92
 
 
93
  try:
94
  gradio_temp_dir = os.path.join(os.getcwd(), "gradio_cached_examples")
95
  if os.path.exists(gradio_temp_dir):
@@ -104,13 +187,16 @@ def cleanup_huggingface_temp_folders():
104
  def setup_clean_temp_environment():
105
  """๊นจ๋—ํ•œ ์ž„์‹œ ํ™˜๊ฒฝ ์„ค์ •"""
106
  try:
 
107
  cleanup_huggingface_temp_folders()
108
 
 
109
  app_temp_dir = os.path.join(tempfile.gettempdir(), "keyword_app")
110
  if os.path.exists(app_temp_dir):
111
  shutil.rmtree(app_temp_dir, ignore_errors=True)
112
  os.makedirs(app_temp_dir, exist_ok=True)
113
 
 
114
  os.environ['KEYWORD_APP_TEMP'] = app_temp_dir
115
 
116
  logger.info(f"โœ… ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์šฉ ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์„ค์ •: {app_temp_dir}")
@@ -159,10 +245,11 @@ def cleanup_old_sessions():
159
  sessions_to_remove = []
160
 
161
  for session_id, data in session_data.items():
162
- if current_time - data.get('last_activity', 0) > 3600:
163
  sessions_to_remove.append(session_id)
164
 
165
  for session_id in sessions_to_remove:
 
166
  if session_id in session_temp_files:
167
  for file_path in session_temp_files[session_id]:
168
  try:
@@ -173,6 +260,7 @@ def cleanup_old_sessions():
173
  logger.error(f"์˜ค๋ž˜๋œ ์„ธ์…˜ ํŒŒ์ผ ์‚ญ์ œ ์˜ค๋ฅ˜: {e}")
174
  del session_temp_files[session_id]
175
 
 
176
  if session_id in session_data:
177
  del session_data[session_id]
178
  logger.info(f"์˜ค๋ž˜๋œ ์„ธ์…˜ ๋ฐ์ดํ„ฐ ์‚ญ์ œ: {session_id[:8]}...")
@@ -188,10 +276,12 @@ def create_session_temp_file(session_id, suffix='.xlsx'):
188
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
189
  random_suffix = str(random.randint(1000, 9999))
190
 
 
191
  temp_dir = get_app_temp_dir()
192
  filename = f"session_{session_id[:8]}_{timestamp}_{random_suffix}{suffix}"
193
  temp_file_path = os.path.join(temp_dir, filename)
194
 
 
195
  with open(temp_file_path, 'w') as f:
196
  pass
197
 
@@ -199,176 +289,156 @@ def create_session_temp_file(session_id, suffix='.xlsx'):
199
  return temp_file_path
200
 
201
  def wrapper_modified(keyword, korean_only, apply_main_keyword_option, exclude_zero_volume, session_id):
202
- """ํ‚ค์›Œ๋“œ ๊ฒ€์ƒ‰ ๋ฐ ์ฒ˜๋ฆฌ ๋ž˜ํผ ํ•จ์ˆ˜ (API ์‚ฌ์šฉ)"""
203
  update_session_activity(session_id)
204
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  try:
206
- client = get_client()
207
- result = client.predict(
208
- keyword=keyword,
209
- korean_only=korean_only,
210
- apply_main_keyword=apply_main_keyword_option,
211
- exclude_zero_volume=exclude_zero_volume,
212
- api_name="/process_search_results"
213
- )
214
-
215
- # API ์‘๋‹ต ํ™•์ธ ๋ฐ ์ฒ˜๋ฆฌ (6๊ฐœ ๊ฐ’)
216
- logger.info(f"API ์‘๋‹ต ํƒ€์ž…: {type(result)}, ๊ธธ์ด: {len(result) if isinstance(result, (list, tuple)) else 'N/A'}")
217
- logger.info(f"API ์‘๋‹ต ๋‚ด์šฉ: {result}")
218
-
219
- if isinstance(result, (list, tuple)) and len(result) >= 6:
220
- table_html, cat_choices, vol_choices, selected_cat, download_file, extra = result[:6]
221
- logger.info(f"table_html ํƒ€์ž…: {type(table_html)}")
222
- logger.info(f"cat_choices: {cat_choices}")
223
- logger.info(f"vol_choices: {vol_choices}")
224
- logger.info(f"selected_cat: {selected_cat}")
225
- logger.info(f"download_file: {download_file}")
226
- elif isinstance(result, (list, tuple)) and len(result) >= 5:
227
- table_html, cat_choices, vol_choices, selected_cat, download_file = result[:5]
228
- extra = None
229
- else:
230
- # ์‘๋‹ต์ด ์˜ˆ์ƒ๊ณผ ๋‹ค๋ฅธ ๊ฒฝ์šฐ ๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ
231
- logger.warning(f"์˜ˆ์ƒ๊ณผ ๋‹ค๋ฅธ API ์‘๋‹ต: {result}")
232
- table_html = "<p>๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.</p>"
233
- cat_choices = ["์ „์ฒด ๋ณด๊ธฐ"]
234
- vol_choices = ["์ „์ฒด"]
235
- selected_cat = "์ „์ฒด ๋ณด๊ธฐ"
236
- download_file = None
237
-
238
- # table_html ์ฒ˜๋ฆฌ (dict์ธ ๊ฒฝ์šฐ value ์ถ”์ถœ)
239
- if isinstance(table_html, dict) and 'value' in table_html:
240
- table_html = table_html['value']
241
- elif isinstance(table_html, dict):
242
- table_html = str(table_html) # dict๋ฅผ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜
243
-
244
- # choices ํ˜•์‹ ์ฒ˜๋ฆฌ (์ค‘์ฒฉ ๋ฆฌ์ŠคํŠธ์ธ ๊ฒฝ์šฐ ์ฒซ ๋ฒˆ์งธ ๊ฐ’๋งŒ ์‚ฌ์šฉ)
245
- if isinstance(cat_choices, dict) and 'choices' in cat_choices:
246
- cat_choices = [choice[0] if isinstance(choice, list) else choice for choice in cat_choices['choices']]
247
- elif isinstance(cat_choices, list) and cat_choices and isinstance(cat_choices[0], list):
248
- cat_choices = [choice[0] for choice in cat_choices]
249
-
250
- if isinstance(vol_choices, dict) and 'choices' in vol_choices:
251
- vol_choices = [choice[0] if isinstance(choice, list) else choice for choice in vol_choices['choices']]
252
- elif isinstance(vol_choices, list) and vol_choices and isinstance(vol_choices[0], list):
253
- vol_choices = [choice[0] for choice in vol_choices]
254
-
255
- # selected_cat ์ฒ˜๋ฆฌ
256
- if isinstance(selected_cat, dict) and 'value' in selected_cat:
257
- selected_cat = selected_cat['value']
258
- elif isinstance(selected_cat, list):
259
- selected_cat = selected_cat[0] if selected_cat else "์ „์ฒด ๋ณด๊ธฐ"
260
-
261
- logger.info(f"์ฒ˜๋ฆฌ๋œ cat_choices: {cat_choices}")
262
- logger.info(f"์ฒ˜๋ฆฌ๋œ vol_choices: {vol_choices}")
263
- logger.info(f"์ฒ˜๋ฆฌ๋œ selected_cat: {selected_cat}")
264
-
265
- local_file = None
266
- if download_file:
267
- try:
268
- local_file = create_session_temp_file(session_id, '.xlsx')
269
- shutil.copy(download_file, local_file)
270
- logger.info(f"ํŒŒ์ผ ๋ณต์‚ฌ ์™„๋ฃŒ: {local_file}")
271
- except Exception as file_error:
272
- logger.error(f"ํŒŒ์ผ ๋ณต์‚ฌ ์˜ค๋ฅ˜: {file_error}")
273
-
274
- return (
275
- table_html,
276
- gr.update(choices=cat_choices),
277
- gr.update(choices=vol_choices),
278
- None,
279
- gr.update(choices=cat_choices, value=selected_cat), # ๋ถ„์„์šฉ ์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ์ง€์™€ ๊ฐ’ ์—…๋ฐ์ดํŠธ
280
- local_file,
281
- gr.update(visible=True),
282
- gr.update(visible=True),
283
- keyword
284
- )
285
-
286
  except Exception as e:
287
- logger.error(f"API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
288
- import traceback
289
- logger.error(f"์ƒ์„ธ ์˜ค๋ฅ˜: {traceback.format_exc()}")
290
- return (
291
- gr.update(value="<p>๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ํ‚ค์›Œ๋“œ๋กœ ์‹œ๋„ํ•ด๋ณด์„ธ์š”.</p>"),
292
- gr.update(choices=["์ „์ฒด ๋ณด๊ธฐ"]),
293
- gr.update(choices=["์ „์ฒด"]),
294
- None,
295
- gr.update(choices=["์ „์ฒด ๋ณด๊ธฐ"], value="์ „์ฒด ๋ณด๊ธฐ"),
296
- None,
297
- gr.update(visible=False),
298
- gr.update(visible=False),
299
- keyword
300
- )
301
 
302
  def analyze_with_auto_download(analysis_keywords, selected_category, state_df, session_id):
303
- """์นดํ…Œ๊ณ ๋ฆฌ ์ผ์น˜ ๋ถ„์„ ์‹คํ–‰ ๋ฐ ์ž๋™ ๋‹ค์šด๋กœ๋“œ (API ์‚ฌ์šฉ)"""
304
  update_session_activity(session_id)
305
 
306
- try:
307
- client = get_client()
308
- result = client.predict(
309
- analysis_keywords=analysis_keywords,
310
- selected_category=selected_category,
311
- api_name="/process_analyze_results"
312
- )
313
-
314
- # API ์‘๋‹ต ํ™•์ธ ๋ฐ ์ฒ˜๋ฆฌ
315
- logger.info(f"๋ถ„์„ API ์‘๋‹ต ํƒ€์ž…: {type(result)}, ๊ธธ์ด: {len(result) if isinstance(result, (list, tuple)) else 'N/A'}")
316
-
317
- if isinstance(result, (list, tuple)) and len(result) >= 2:
318
- analysis_result, download_file = result[:2]
319
- elif isinstance(result, str):
320
- analysis_result = result
321
- download_file = None
322
- else:
323
- logger.warning(f"์˜ˆ์ƒ๊ณผ ๋‹ค๋ฅธ ๋ถ„์„ API ์‘๋‹ต: {result}")
324
- analysis_result = "๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."
325
- download_file = None
326
-
327
- local_file = None
328
- if download_file:
329
- local_file = create_session_temp_file(session_id, '.xlsx')
330
- shutil.copy(download_file, local_file)
331
-
332
- return analysis_result, local_file, gr.update(visible=True)
333
-
334
- except Exception as e:
335
- logger.error(f"๋ถ„์„ API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
336
- return "๋ถ„์„ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.", None, gr.update(visible=False)
337
 
338
  def filter_and_sort_table(df, selected_cat, keyword_sort, total_volume_sort, usage_count_sort, selected_volume_range, exclude_zero_volume, session_id):
339
- """ํ…Œ์ด๋ธ” ํ•„ํ„ฐ๋ง ๋ฐ ์ •๋ ฌ ํ•จ์ˆ˜ (API ์‚ฌ์šฉ)"""
340
  update_session_activity(session_id)
341
 
342
- try:
343
- client = get_client()
344
- result = client.predict(
345
- selected_cat=selected_cat,
346
- keyword_sort=keyword_sort,
347
- total_volume_sort=total_volume_sort,
348
- usage_count_sort=usage_count_sort,
349
- selected_volume_range=selected_volume_range,
350
- exclude_zero_volume=exclude_zero_volume,
351
- api_name="/filter_and_sort_table"
352
- )
353
-
354
- return result
355
-
356
- except Exception as e:
357
- logger.error(f"ํ•„ํ„ฐ๋ง API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
358
  return ""
359
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
360
  def update_category_selection(selected_cat, session_id):
361
  """์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ ์„ ํƒ ์‹œ ๋ถ„์„ํ•  ์นดํ…Œ๊ณ ๋ฆฌ๋„ ๊ฐ™์€ ๊ฐ’์œผ๋กœ ์—…๋ฐ์ดํŠธ"""
362
  update_session_activity(session_id)
363
- logger.info(f"์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ ๋ณ€๊ฒฝ: {selected_cat}")
364
-
365
- # ๋กœ์ปฌ์—์„œ ์ง์ ‘ ์ฒ˜๋ฆฌ (API ํ˜ธ์ถœ ์—†์ด)
366
  return gr.update(value=selected_cat)
367
 
368
  def reset_interface(session_id):
369
  """์ธํ„ฐํŽ˜์ด์Šค ๋ฆฌ์…‹ ํ•จ์ˆ˜ - ์„ธ์…˜๋ณ„ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™”"""
370
  update_session_activity(session_id)
371
 
 
372
  if session_id in session_temp_files:
373
  for file_path in session_temp_files[session_id]:
374
  try:
@@ -379,23 +449,36 @@ def reset_interface(session_id):
379
  logger.error(f"์„ธ์…˜ {session_id[:8]}... ๋ฆฌ์…‹ ์‹œ ํŒŒ์ผ ์‚ญ์ œ ์˜ค๋ฅ˜: {e}")
380
  session_temp_files[session_id] = []
381
 
382
- try:
383
- client = get_client()
384
- result = client.predict(api_name="/reset_interface")
385
- return result
386
-
387
- except Exception as e:
388
- logger.error(f"๋ฆฌ์…‹ API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
389
- return (
390
- "", True, False, "๋ฉ”์ธํ‚ค์›Œ๋“œ ์ ์šฉ", "", ["์ „์ฒด ๋ณด๊ธฐ"], "์ „์ฒด ๋ณด๊ธฐ",
391
- ["์ „์ฒด"], "์ „์ฒด", "์ •๋ ฌ ์—†์Œ", "์ •๋ ฌ ์—†์Œ", None, ["์ „์ฒด ๋ณด๊ธฐ"],
392
- "์ „์ฒด ๋ณด๊ธฐ", "", "", None, gr.update(visible=False),
393
- gr.update(visible=False), ""
394
- )
 
 
 
 
 
 
 
 
 
395
 
 
396
  def search_with_loading(keyword, korean_only, apply_main_keyword, exclude_zero_volume, session_id):
397
  update_session_activity(session_id)
398
- return (gr.update(visible=True), gr.update(visible=False))
 
 
 
399
 
400
  def process_search_results(keyword, korean_only, apply_main_keyword, exclude_zero_volume, session_id):
401
  update_session_activity(session_id)
@@ -404,23 +487,20 @@ def process_search_results(keyword, korean_only, apply_main_keyword, exclude_zer
404
 
405
  table_html, cat_choices, vol_choices, df, selected_cat, excel, keyword_section_vis, cat_section_vis, new_keyword_state = result
406
 
407
- # ํ…Œ์ด๋ธ”์ด ์žˆ์œผ๋ฉด ์„น์…˜๋“ค์„ ํ‘œ์‹œ
408
- if table_html and table_html.get('value') if isinstance(table_html, dict) else table_html:
409
  empty_placeholder_vis = False
410
  keyword_section_visibility = True
411
  execution_section_visibility = True
412
- logger.info("ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์Œ - ์„น์…˜๋“ค์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค")
413
  else:
414
  empty_placeholder_vis = True
415
  keyword_section_visibility = False
416
  execution_section_visibility = False
417
- logger.info("ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Œ - ๊ธฐ๋ณธ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค")
418
 
419
  return (
420
  table_html, cat_choices, vol_choices, df, selected_cat, excel,
421
  gr.update(visible=keyword_section_visibility),
422
- gr.update(visible=True), # ์นดํ…Œ๊ณ ๋ฆฌ ๋ถ„์„ ์„น์…˜์€ ํ•ญ์ƒ ํ‘œ์‹œ
423
- gr.update(visible=False), # ์ง„ํ–‰ ์ƒํƒœ๋Š” ์ˆจ๊น€
424
  gr.update(visible=empty_placeholder_vis),
425
  gr.update(visible=execution_section_visibility),
426
  new_keyword_state
@@ -435,12 +515,14 @@ def process_analyze_results(analysis_keywords, selected_category, state_df, sess
435
  results = analyze_with_auto_download(analysis_keywords, selected_category, state_df, session_id)
436
  return results + (gr.update(visible=False),)
437
 
 
438
  def start_session_cleanup_scheduler():
439
  """์„ธ์…˜ ์ •๋ฆฌ ์Šค์ผ€์ค„๋Ÿฌ ์‹œ์ž‘"""
440
  def cleanup_scheduler():
441
  while True:
442
- time.sleep(600)
443
  cleanup_old_sessions()
 
444
  cleanup_huggingface_temp_folders()
445
 
446
  threading.Thread(target=cleanup_scheduler, daemon=True).start()
@@ -449,9 +531,13 @@ def cleanup_on_startup():
449
  """์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์‹œ ์ „์ฒด ์ •๋ฆฌ"""
450
  logger.info("๐Ÿงน ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ - ์ดˆ๊ธฐ ์ •๋ฆฌ ์ž‘์—… ์‹œ์ž‘...")
451
 
 
452
  cleanup_huggingface_temp_folders()
 
 
453
  app_temp_dir = setup_clean_temp_environment()
454
 
 
455
  global session_temp_files, session_data
456
  session_temp_files.clear()
457
  session_data.clear()
@@ -460,6 +546,7 @@ def cleanup_on_startup():
460
 
461
  return app_temp_dir
462
 
 
463
  def create_app():
464
  fontawesome_html = """
465
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
@@ -467,301 +554,121 @@ def create_app():
467
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap">
468
  """
469
 
 
470
  try:
471
  with open('style.css', 'r', encoding='utf-8') as f:
472
  custom_css = f.read()
473
  except:
474
- custom_css = ""
475
-
476
- with gr.Blocks(css=custom_css, theme=gr.themes.Default(
477
- primary_hue="orange",
478
- secondary_hue="orange",
479
- font=[gr.themes.GoogleFont("Noto Sans KR"), "ui-sans-serif", "system-ui"]
480
- )) as demo:
481
- gr.HTML(fontawesome_html)
482
-
483
- session_id = gr.State(get_session_id)
484
- keyword_state = gr.State("")
485
-
486
- with gr.Column(elem_classes="custom-frame fade-in"):
487
- gr.HTML('<div class="section-title"><i class="fas fa-search"></i> ๊ฒ€์ƒ‰ ์ž…๋ ฅ</div>')
488
-
489
- with gr.Row():
490
- with gr.Column(scale=1):
491
- keyword = gr.Textbox(label="๋ฉ”์ธ ํ‚ค์›Œ๋“œ", placeholder="์˜ˆ: ์˜ค์ง•์–ด")
492
- with gr.Column(scale=1):
493
- search_btn = gr.Button("๋ฉ”์ธํ‚ค์›Œ๋“œ ๋ถ„์„", elem_classes="custom-button")
494
-
495
- with gr.Accordion("์˜ต์…˜ ์„ค์ •", open=False):
496
- with gr.Row():
497
- with gr.Column(scale=1):
498
- korean_only = gr.Checkbox(label="ํ•œ๊ธ€๋งŒ ์ถ”์ถœ", value=True)
499
- with gr.Column(scale=1):
500
- exclude_zero_volume = gr.Checkbox(label="๊ฒ€์ƒ‰๋Ÿ‰ 0 ํ‚ค์›Œ๋“œ ์ œ์™ธ", value=False)
501
-
502
- with gr.Row():
503
- with gr.Column(scale=1):
504
- apply_main_keyword = gr.Radio(
505
- ["๋ฉ”์ธํ‚ค์›Œ๋“œ ์ ์šฉ", "๋ฉ”์ธํ‚ค์›Œ๋“œ ๋ฏธ์ ์šฉ"],
506
- label="์กฐํ•ฉ ๋ฐฉ์‹",
507
- value="๋ฉ”์ธํ‚ค์›Œ๋“œ ์ ์šฉ"
508
- )
509
- with gr.Column(scale=1):
510
- gr.HTML("")
511
-
512
- with gr.Column(elem_classes="custom-frame fade-in", visible=False) as progress_section:
513
- gr.HTML('<div class="section-title"><i class="fas fa-spinner"></i> ๋ถ„์„ ์ง„ํ–‰ ์ƒํƒœ</div>')
514
- progress_html = gr.HTML("""
515
- <div style="padding: 15px; background-color: #f9f9f9; border-radius: 5px; margin: 10px 0; border: 1px solid #ddd;">
516
- <div style="margin-bottom: 10px; display: flex; align-items: center;">
517
- <i class="fas fa-spinner fa-spin" style="color: #FB7F0D; margin-right: 10px;"></i>
518
- <span>ํ‚ค์›Œ๋“œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„์ค‘์ž…๋‹ˆ๋‹ค. ์ž ์‹œ๋งŒ ๊ธฐ๋‹ค๋ ค์ฃผ์„ธ์š”...</span>
519
- </div>
520
- <div style="background-color: #e9ecef; height: 10px; border-radius: 5px; overflow: hidden;">
521
- <div class="progress-bar"></div>
522
- </div>
523
- </div>
524
- """)
525
-
526
- with gr.Column(elem_classes="custom-frame fade-in") as main_keyword_section:
527
- gr.HTML('<div class="section-title"><i class="fas fa-table"></i> ๋ฉ”์ธํ‚ค์›Œ๋“œ ๋ถ„์„ ๊ฒฐ๊ณผ</div>')
528
-
529
- empty_table_html = gr.HTML("""
530
- <table class="empty-table">
531
- <thead>
532
- <tr>
533
- <th>์ˆœ๋ฒˆ</th>
534
- <th>์กฐํ•ฉ ํ‚ค์›Œ๋“œ</th>
535
- <th>PC๊ฒ€์ƒ‰๋Ÿ‰</th>
536
- <th>๋ชจ๋ฐ”์ผ๊ฒ€์ƒ‰๋Ÿ‰</th>
537
- <th>์ด๊ฒ€์ƒ‰๋Ÿ‰</th>
538
- <th>๊ฒ€์ƒ‰๋Ÿ‰๊ตฌ๊ฐ„</th>
539
- <th>ํ‚ค์›Œ๋“œ ์‚ฌ์šฉ์ž์ˆœ์œ„</th>
540
- <th>ํ‚ค์›Œ๋“œ ์‚ฌ์šฉํšŸ์ˆ˜</th>
541
- <th>์ƒํ’ˆ ๋“ฑ๋ก ์นดํ…Œ๊ณ ๋ฆฌ</th>
542
- </tr>
543
- </thead>
544
- <tbody>
545
- <tr>
546
- <td colspan="9" style="padding: 30px; text-align: center;">
547
- ๊ฒ€์ƒ‰์„ ์‹คํ–‰ํ•˜๋ฉด ์—ฌ๊ธฐ์— ๊ฒฐ๊ณผ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค
548
- </td>
549
- </tr>
550
- </tbody>
551
- </table>
552
- """)
553
-
554
- with gr.Column(visible=False) as keyword_analysis_section:
555
- with gr.Row():
556
- with gr.Column(scale=1):
557
- category_filter = gr.Dropdown(
558
- choices=["์ „์ฒด ๋ณด๊ธฐ"],
559
- label="์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ",
560
- value="์ „์ฒด ๋ณด๊ธฐ",
561
- interactive=True,
562
- allow_custom_value=True # ์‚ฌ์šฉ์ž ์ •์˜ ๊ฐ’ ํ—ˆ์šฉ
563
- )
564
- with gr.Column(scale=1):
565
- total_volume_sort = gr.Dropdown(
566
- choices=["์ •๋ ฌ ์—†์Œ", "์˜ค๋ฆ„์ฐจ์ˆœ", "๋‚ด๋ฆผ์ฐจ์ˆœ"],
567
- label="์ด๊ฒ€์ƒ‰๋Ÿ‰ ์ •๋ ฌ",
568
- value="์ •๋ ฌ ์—†์Œ",
569
- interactive=True
570
- )
571
-
572
- with gr.Row():
573
- with gr.Column(scale=1):
574
- search_volume_filter = gr.Dropdown(
575
- choices=["์ „์ฒด"],
576
- label="๊ฒ€์ƒ‰๋Ÿ‰ ๊ตฌ๊ฐ„ ํ•„ํ„ฐ",
577
- value="์ „์ฒด",
578
- interactive=True
579
- )
580
- with gr.Column(scale=1):
581
- usage_count_sort = gr.Dropdown(
582
- choices=["์ •๋ ฌ ์—†์Œ", "์˜ค๋ฆ„์ฐจ์ˆœ", "๋‚ด๋ฆผ์ฐจ์ˆœ"],
583
- label="ํ‚ค์›Œ๋“œ ์‚ฌ์šฉํšŸ์ˆ˜ ์ •๋ ฌ",
584
- value="์ •๋ ฌ ์—†์Œ",
585
- interactive=True
586
- )
587
-
588
- gr.HTML("<div class='data-container' id='table_container'></div>")
589
- table_output = gr.HTML(elem_classes="fade-in")
590
-
591
- with gr.Column(elem_classes="custom-frame fade-in", visible=False) as category_analysis_section:
592
- gr.HTML('<div class="section-title"><i class="fas fa-chart-bar"></i> ํ‚ค์›Œ๋“œ ๋ถ„์„</div>')
593
-
594
- with gr.Row():
595
- with gr.Column(scale=1):
596
- analysis_keywords = gr.Textbox(
597
- label="ํ‚ค์›Œ๋“œ ์ž…๋ ฅ (์ตœ๋Œ€ 20๊ฐœ, ์‰ผํ‘œ ๋˜๋Š” ์—”ํ„ฐ๋กœ ๊ตฌ๋ถ„)",
598
- placeholder="์˜ˆ: ์˜ค์ง•์–ด๋ณถ์Œ, ์˜ค์ง•์–ด ์†์งˆ, ์˜ค์ง•์–ด ์š”๋ฆฌ...",
599
- lines=5
600
- )
601
-
602
- with gr.Column(scale=1):
603
- selected_category = gr.Dropdown(
604
- label="๋ถ„์„ํ•  ์นดํ…Œ๊ณ ๋ฆฌ(๋ถ„์„ ์ „ ๋ฐ˜๋“œ์‹œ ์„ ํƒํ•ด์ฃผ์„ธ์š”)",
605
- choices=["์ „์ฒด ๋ณด๊ธฐ"],
606
- value="์ „์ฒด ๋ณด๊ธฐ",
607
- interactive=True,
608
- allow_custom_value=True # ์‚ฌ์šฉ์ž ์ •์˜ ๊ฐ’ ํ—ˆ์šฉ
609
- )
610
-
611
- with gr.Column(elem_classes="execution-section", visible=False) as execution_section:
612
- gr.HTML('<div class="section-title"><i class="fas fa-play-circle"></i> ์‹คํ–‰</div>')
613
- with gr.Row():
614
- with gr.Column(scale=1):
615
- analyze_btn = gr.Button(
616
- "์นดํ…Œ๊ณ ๋ฆฌ ์ผ์น˜ ๋ถ„์„",
617
- elem_classes=["execution-button", "primary-button"]
618
- )
619
- with gr.Column(scale=1):
620
- reset_btn = gr.Button(
621
- "๋ชจ๋“  ์ž…๋ ฅ ์ดˆ๊ธฐํ™”",
622
- elem_classes=["execution-button", "secondary-button"]
623
- )
624
-
625
- with gr.Column(elem_classes="custom-frame fade-in", visible=False) as analysis_output_section:
626
- gr.HTML('<div class="section-title"><i class="fas fa-list-ul"></i> ๋ถ„์„ ๊ฒฐ๊ณผ ์š”์•ฝ</div>')
627
-
628
- analysis_result = gr.HTML(elem_classes="fade-in")
629
-
630
- with gr.Row():
631
- download_output = gr.File(label="ํ‚ค์›Œ๋“œ ๋ชฉ๋ก ๋‹ค์šด๋กœ๋“œ", visible=True)
632
-
633
- state_df = gr.State()
634
-
635
- search_btn.click(
636
- fn=search_with_loading,
637
- inputs=[keyword, korean_only, apply_main_keyword, exclude_zero_volume, session_id],
638
- outputs=[progress_section, empty_table_html]
639
- ).then(
640
- fn=process_search_results,
641
- inputs=[keyword, korean_only, apply_main_keyword, exclude_zero_volume, session_id],
642
- outputs=[
643
- table_output, category_filter, search_volume_filter,
644
- state_df, selected_category, download_output, # selected_category๊ฐ€ ๋ถ„์„์šฉ ์นดํ…Œ๊ณ ๋ฆฌ
645
- keyword_analysis_section, category_analysis_section,
646
- progress_section, empty_table_html, execution_section,
647
- keyword_state
648
- ]
649
- )
650
-
651
- category_filter.change(
652
- fn=filter_and_sort_table,
653
- inputs=[
654
- state_df, category_filter, gr.Textbox(value="์ •๋ ฌ ์—†์Œ", visible=False),
655
- total_volume_sort, usage_count_sort,
656
- search_volume_filter, exclude_zero_volume, session_id
657
- ],
658
- outputs=[table_output]
659
- )
660
-
661
- category_filter.change(
662
- fn=update_category_selection,
663
- inputs=[category_filter, session_id],
664
- outputs=[selected_category]
665
- )
666
-
667
- total_volume_sort.change(
668
- fn=filter_and_sort_table,
669
- inputs=[
670
- state_df, category_filter, gr.Textbox(value="์ •๋ ฌ ์—†์Œ", visible=False),
671
- total_volume_sort, usage_count_sort,
672
- search_volume_filter, exclude_zero_volume, session_id
673
- ],
674
- outputs=[table_output]
675
- )
676
-
677
- usage_count_sort.change(
678
- fn=filter_and_sort_table,
679
- inputs=[
680
- state_df, category_filter, gr.Textbox(value="์ •๋ ฌ ์—†์Œ", visible=False),
681
- total_volume_sort, usage_count_sort,
682
- search_volume_filter, exclude_zero_volume, session_id
683
- ],
684
- outputs=[table_output]
685
- )
686
-
687
- search_volume_filter.change(
688
- fn=filter_and_sort_table,
689
- inputs=[
690
- state_df, category_filter, gr.Textbox(value="์ •๋ ฌ ์—†์Œ", visible=False),
691
- total_volume_sort, usage_count_sort,
692
- search_volume_filter, exclude_zero_volume, session_id
693
- ],
694
- outputs=[table_output]
695
- )
696
-
697
- exclude_zero_volume.change(
698
- fn=filter_and_sort_table,
699
- inputs=[
700
- state_df, category_filter, gr.Textbox(value="์ •๋ ฌ ์—†์Œ", visible=False),
701
- total_volume_sort, usage_count_sort,
702
- search_volume_filter, exclude_zero_volume, session_id
703
- ],
704
- outputs=[table_output]
705
- )
706
-
707
- analyze_btn.click(
708
- fn=analyze_with_loading,
709
- inputs=[analysis_keywords, selected_category, state_df, session_id],
710
- outputs=[progress_section]
711
- ).then(
712
- fn=process_analyze_results,
713
- inputs=[analysis_keywords, selected_category, state_df, session_id],
714
- outputs=[analysis_result, download_output, analysis_output_section, progress_section]
715
- )
716
-
717
- reset_btn.click(
718
- fn=reset_interface,
719
- inputs=[session_id],
720
- outputs=[
721
- keyword, korean_only, exclude_zero_volume, apply_main_keyword,
722
- table_output, category_filter, category_filter,
723
- search_volume_filter, search_volume_filter,
724
- total_volume_sort, usage_count_sort,
725
- state_df, selected_category, selected_category,
726
- analysis_keywords, analysis_result, download_output,
727
- keyword_analysis_section, analysis_output_section,
728
- keyword_state
729
- ]
730
- )
731
-
732
- return demo
733
-
734
- if __name__ == "__main__":
735
- logger.info("๐Ÿš€ ๋ฉ”์ธํ‚ค์›Œ๋“œ ๋ถ„์„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘...")
736
-
737
- app_temp_dir = cleanup_on_startup()
738
- start_session_cleanup_scheduler()
739
-
740
- logger.info("===== ๋ฉ€ํ‹ฐ์œ ์ € ๋ฉ”์ธํ‚ค์›Œ๋“œ ๋ถ„์„ Application Startup at %s =====", time.strftime("%Y-%m-%d %H:%M:%S"))
741
- logger.info(f"๐Ÿ“ ์ž„์‹œ ํŒŒ์ผ ์ €์žฅ ์œ„์น˜: {app_temp_dir}")
742
-
743
- try:
744
- app = create_app()
745
- app.launch(
746
- share=False,
747
- server_name="0.0.0.0",
748
- server_port=7860,
749
- max_threads=40,
750
- auth=None,
751
- show_error=True,
752
- quiet=False,
753
- favicon_path=None,
754
- ssl_verify=False
755
- )
756
- except Exception as e:
757
- logger.error(f"์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ์‹คํŒจ: {e}")
758
- raise
759
- finally:
760
- logger.info("๐Ÿงน ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ข…๋ฃŒ - ์ตœ์ข… ์ •๋ฆฌ ์ž‘์—…...")
761
- try:
762
- cleanup_huggingface_temp_folders()
763
- if os.path.exists(app_temp_dir):
764
- shutil.rmtree(app_temp_dir, ignore_errors=True)
765
- logger.info("โœ… ์ตœ์ข… ์ •๋ฆฌ ์™„๋ฃŒ")
766
- except Exception as e:
767
- logger.error(f"์ตœ์ข… ์ •๋ฆฌ ์ค‘ ์˜ค๋ฅ˜: {e}")
 
10
  import shutil
11
  import glob
12
  from datetime import datetime
13
+ import sys
14
+ import types
15
 
16
  # ๋กœ๊น… ์„ค์ •
17
  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
 
26
  logger = logging.getLogger(__name__)
27
 
28
+ # ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ ๋ชจ๋“ˆ ์ฝ”๋“œ ๋กœ๋“œ ๋ฐ ๋™์  ์ƒ์„ฑ
29
+ def load_module_from_env(module_name, env_var_name):
30
+ """ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ ๋ชจ๋“ˆ ์ฝ”๋“œ๋ฅผ ๋กœ๋“œํ•˜์—ฌ ๋™์ ์œผ๋กœ ๋ชจ๋“ˆ ์ƒ์„ฑ"""
31
+ try:
32
+ module_code = os.getenv(env_var_name)
33
+ if not module_code:
34
+ raise ValueError(f"ํ™˜๊ฒฝ๋ณ€์ˆ˜ {env_var_name}๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
35
+
36
+ # ์ƒˆ ๋ชจ๋“ˆ ์ƒ์„ฑ
37
+ module = types.ModuleType(module_name)
38
+
39
+ # ๋ชจ๋“ˆ์— ํ•„์š”ํ•œ ๊ธฐ๋ณธ ์ž„ํฌํŠธ๋“ค ์ถ”๊ฐ€
40
+ module.__dict__.update({
41
+ 'os': __import__('os'),
42
+ 'time': __import__('time'),
43
+ 'logging': __import__('logging'),
44
+ 'pandas': __import__('pandas'),
45
+ 'requests': __import__('requests'),
46
+ 'tempfile': __import__('tempfile'),
47
+ 'threading': __import__('threading'),
48
+ 're': __import__('re'),
49
+ 'random': __import__('random'),
50
+ 'uuid': __import__('uuid'),
51
+ 'shutil': __import__('shutil'),
52
+ 'glob': __import__('glob'),
53
+ 'datetime': __import__('datetime'),
54
+ 'types': __import__('types'),
55
+ 'collections': __import__('collections'),
56
+ 'Counter': __import__('collections').Counter,
57
+ 'defaultdict': __import__('collections').defaultdict,
58
+ 'hmac': __import__('hmac'),
59
+ 'hashlib': __import__('hashlib'),
60
+ 'base64': __import__('base64'),
61
+ })
62
+
63
+ # ์ฝ”๋“œ ์‹คํ–‰
64
+ exec(module_code, module.__dict__)
65
+
66
+ # ์‹œ์Šคํ…œ ๋ชจ๋“ˆ์— ๋“ฑ๋ก
67
+ sys.modules[module_name] = module
68
+
69
+ logger.info(f"โœ… ๋ชจ๋“ˆ {module_name} ๋กœ๋“œ ์™„๋ฃŒ")
70
+ return module
71
+
72
+ except Exception as e:
73
+ logger.error(f"โŒ ๋ชจ๋“ˆ {module_name} ๋กœ๋“œ ์‹คํŒจ: {e}")
74
+ raise
75
+
76
+ # ํ•„์š”ํ•œ ๋ชจ๋“ˆ๋“ค์„ ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ ๋กœ๋“œ
77
+ logger.info("๐Ÿ”„ ๋ชจ๋“ˆ ๋กœ๋“œ ์‹œ์ž‘...")
78
+
79
+ try:
80
+ # 1. api_utils ๋ชจ๋“ˆ ๋กœ๋“œ
81
+ api_utils = load_module_from_env('api_utils', 'API_UTILS_CODE')
82
+
83
+ # 2. text_utils ๋ชจ๋“ˆ ๋กœ๋“œ (๋‹ค๋ฅธ ๋ชจ๋“ˆ๋“ค์ด ์˜์กดํ•˜๋ฏ€๋กœ ๋จผ์ € ๋กœ๋“œ)
84
+ text_utils = load_module_from_env('text_utils', 'TEXT_UTILS_CODE')
85
+
86
+ # 3. keyword_search ๋ชจ๋“ˆ ๋กœ๋“œ
87
+ keyword_search = load_module_from_env('keyword_search', 'KEYWORD_SEARCH_CODE')
88
+
89
+ # 4. product_search ๋ชจ๋“ˆ ๋กœ๋“œ (text_utils, keyword_search ์˜์กด)
90
+ product_search_module = load_module_from_env('product_search', 'PRODUCT_SEARCH_CODE')
91
+ # product_search ๋ชจ๋“ˆ์— ์˜์กด์„ฑ ์ฃผ์ž…
92
+ product_search_module.api_utils = api_utils
93
+ product_search_module.text_utils = text_utils
94
+ product_search = product_search_module
95
+
96
+ # 5. keyword_processor ๋ชจ๋“ˆ ๋กœ๋“œ
97
+ keyword_processor_module = load_module_from_env('keyword_processor', 'KEYWORD_PROCESSOR_CODE')
98
+ # keyword_processor ๋ชจ๋“ˆ์— ์˜์กด์„ฑ ์ฃผ์ž…
99
+ keyword_processor_module.text_utils = text_utils
100
+ keyword_processor_module.keyword_search = keyword_search
101
+ keyword_processor_module.product_search = product_search
102
+ keyword_processor = keyword_processor_module
103
+
104
+ # 6. export_utils ๋ชจ๋“ˆ ๋กœ๋“œ
105
+ export_utils = load_module_from_env('export_utils', 'EXPORT_UTILS_CODE')
106
+
107
+ # 7. category_analysis ๋ชจ๋“ˆ ๋กœ๋“œ (๋ชจ๋“  ๋ชจ๋“ˆ ์˜์กด)
108
+ category_analysis_module = load_module_from_env('category_analysis', 'CATEGORY_ANALYSIS_CODE')
109
+ # category_analysis ๋ชจ๋“ˆ์— ์˜์กด์„ฑ ์ฃผ์ž…
110
+ category_analysis_module.text_utils = text_utils
111
+ category_analysis_module.product_search = product_search
112
+ category_analysis_module.keyword_search = keyword_search
113
+ category_analysis = category_analysis_module
114
+
115
+ logger.info("โœ… ๋ชจ๋“  ๋ชจ๋“ˆ ๋กœ๋“œ ์™„๋ฃŒ")
116
+
117
+ except Exception as e:
118
+ logger.error(f"โŒ ๋ชจ๋“ˆ ๋กœ๋“œ ์ค‘ ์น˜๋ช…์  ์˜ค๋ฅ˜: {e}")
119
+ logger.error("ํ•„์š”ํ•œ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋“ค์ด ์„ค์ •๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”:")
120
+ logger.error("- API_UTILS_CODE")
121
+ logger.error("- TEXT_UTILS_CODE")
122
+ logger.error("- KEYWORD_SEARCH_CODE")
123
+ logger.error("- PRODUCT_SEARCH_CODE")
124
+ logger.error("- KEYWORD_PROCESSOR_CODE")
125
+ logger.error("- EXPORT_UTILS_CODE")
126
+ logger.error("- CATEGORY_ANALYSIS_CODE")
127
+ raise
128
 
129
  # ์„ธ์…˜๋ณ„ ์ž„์‹œ ํŒŒ์ผ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ๋”•์…”๋„ˆ๋ฆฌ
130
  session_temp_files = {}
 
133
  def cleanup_huggingface_temp_folders():
134
  """ํ—ˆ๊น…ํŽ˜์ด์Šค ์ž„์‹œ ํด๋” ์ดˆ๊ธฐ ์ •๋ฆฌ"""
135
  try:
136
+ # ์ผ๋ฐ˜์ ์ธ ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ๋“ค
137
  temp_dirs = [
138
  tempfile.gettempdir(),
139
  "/tmp",
 
149
  for temp_dir in temp_dirs:
150
  if os.path.exists(temp_dir):
151
  try:
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")))
 
159
 
160
  for file_path in session_files:
161
  try:
162
+ # ํŒŒ์ผ์ด 1์‹œ๊ฐ„ ์ด์ƒ ์˜ค๋ž˜๋œ ๊ฒฝ์šฐ๋งŒ ์‚ญ์ œ
163
  if os.path.getmtime(file_path) < time.time() - 3600:
164
  os.remove(file_path)
165
  cleanup_count += 1
 
172
 
173
  logger.info(f"โœ… ํ—ˆ๊น…ํŽ˜์ด์Šค ์ž„์‹œ ํด๋” ์ดˆ๊ธฐ ์ •๋ฆฌ ์™„๋ฃŒ - {cleanup_count}๊ฐœ ํŒŒ์ผ ์‚ญ์ œ")
174
 
175
+ # Gradio ์บ์‹œ ํด๋”๋„ ์ •๋ฆฌ
176
  try:
177
  gradio_temp_dir = os.path.join(os.getcwd(), "gradio_cached_examples")
178
  if os.path.exists(gradio_temp_dir):
 
187
  def setup_clean_temp_environment():
188
  """๊นจ๋—ํ•œ ์ž„์‹œ ํ™˜๊ฒฝ ์„ค์ •"""
189
  try:
190
+ # 1. ๊ธฐ์กด ์ž„์‹œ ํŒŒ์ผ๋“ค ์ •๋ฆฌ
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}")
 
245
  sessions_to_remove = []
246
 
247
  for session_id, data in session_data.items():
248
+ if current_time - data.get('last_activity', 0) > 3600: # 1์‹œ๊ฐ„ ์ดˆ๊ณผ
249
  sessions_to_remove.append(session_id)
250
 
251
  for session_id in sessions_to_remove:
252
+ # ํŒŒ์ผ ์ •๋ฆฌ
253
  if session_id in session_temp_files:
254
  for file_path in session_temp_files[session_id]:
255
  try:
 
260
  logger.error(f"์˜ค๋ž˜๋œ ์„ธ์…˜ ํŒŒ์ผ ์‚ญ์ œ ์˜ค๋ฅ˜: {e}")
261
  del session_temp_files[session_id]
262
 
263
+ # ์„ธ์…˜ ๋ฐ์ดํ„ฐ ์ •๋ฆฌ
264
  if session_id in session_data:
265
  del session_data[session_id]
266
  logger.info(f"์˜ค๋ž˜๋œ ์„ธ์…˜ ๋ฐ์ดํ„ฐ ์‚ญ์ œ: {session_id[:8]}...")
 
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()
281
  filename = f"session_{session_id[:8]}_{timestamp}_{random_suffix}{suffix}"
282
  temp_file_path = os.path.join(temp_dir, filename)
283
 
284
+ # ๋นˆ ํŒŒ์ผ ์ƒ์„ฑ
285
  with open(temp_file_path, 'w') as f:
286
  pass
287
 
 
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
+ # ๋ถ„์„ ์‹คํ–‰
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)
440
 
441
+ # ์„ธ์…˜๋ณ„ ์ž„์‹œ ํŒŒ์ผ ์ •๋ฆฌ
442
  if session_id in session_temp_files:
443
  for file_path in session_temp_files[session_id]:
444
  try:
 
449
  logger.error(f"์„ธ์…˜ {session_id[:8]}... ๋ฆฌ์…‹ ์‹œ ํŒŒ์ผ ์‚ญ์ œ ์˜ค๋ฅ˜: {e}")
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)
 
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
 
515
  results = analyze_with_auto_download(analysis_keywords, selected_category, state_df, session_id)
516
  return results + (gr.update(visible=False),)
517
 
518
+ # ์„ธ์…˜ ์ •๋ฆฌ ์Šค์ผ€์ค„๋Ÿฌ
519
  def start_session_cleanup_scheduler():
520
  """์„ธ์…˜ ์ •๋ฆฌ ์Šค์ผ€์ค„๋Ÿฌ ์‹œ์ž‘"""
521
  def cleanup_scheduler():
522
  while True:
523
+ time.sleep(600) # 10๋ถ„๋งˆ๋‹ค ์‹คํ–‰
524
  cleanup_old_sessions()
525
+ # ์ถ”๊ฐ€๋กœ ํ—ˆ๊น…ํŽ˜์ด์Šค ์ž„์‹œ ํด๋”๋„ ์ฃผ๊ธฐ์  ์ •๋ฆฌ
526
  cleanup_huggingface_temp_folders()
527
 
528
  threading.Thread(target=cleanup_scheduler, daemon=True).start()
 
531
  """์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์‹œ ์ „์ฒด ์ •๋ฆฌ"""
532
  logger.info("๐Ÿงน ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ - ์ดˆ๊ธฐ ์ •๋ฆฌ ์ž‘์—… ์‹œ์ž‘...")
533
 
534
+ # 1. ํ—ˆ๊น…ํŽ˜์ด์Šค ์ž„์‹œ ํด๋” ์ •๋ฆฌ
535
  cleanup_huggingface_temp_folders()
536
+
537
+ # 2. ๊นจ๋—ํ•œ ์ž„์‹œ ํ™˜๊ฒฝ ์„ค์ •
538
  app_temp_dir = setup_clean_temp_environment()
539
 
540
+ # 3. ์ „์—ญ ๋ณ€์ˆ˜ ์ดˆ๊ธฐํ™”
541
  global session_temp_files, session_data
542
  session_temp_files.clear()
543
  session_data.clear()
 
546
 
547
  return app_temp_dir
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">
 
554
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap">
555
  """
556
 
557
+ # CSS ํŒŒ์ผ ๋กœ๋“œ
558
  try:
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;
565
+ --secondary-color: #ff9a8b;
566
+ }
567
+ .custom-button {
568
+ background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important;
569
+ color: white !important;
570
+ border-radius: 30px !important;
571
+ height: 45px !important;
572
+ font-size: 16px !important;
573
+ font-weight: bold !important;
574
+ width: 100% !important;
575
+ text-align: center !important;
576
+ display: flex !important;
577
+ align-items: center !important;
578
+ justify-content: center !important;
579
+ }
580
+ .reset-button {
581
+ background: linear-gradient(135deg, #6c757d, #495057) !important;
582
+ color: white !important;
583
+ border-radius: 30px !important;
584
+ height: 45px !important;
585
+ font-size: 16px !important;
586
+ font-weight: bold !important;
587
+ width: 100% !important;
588
+ text-align: center !important;
589
+ display: flex !important;
590
+ align-items: center !important;
591
+ justify-content: center !important;
592
+ }
593
+ .section-title {
594
+ border-bottom: 2px solid #FB7F0D;
595
+ font-weight: bold;
596
+ padding-bottom: 5px;
597
+ margin-bottom: 15px;
598
+ }
599
+ .loading-indicator {
600
+ display: flex;
601
+ align-items: center;
602
+ justify-content: center;
603
+ padding: 15px;
604
+ background-color: #f8f9fa;
605
+ border-radius: 5px;
606
+ margin: 10px 0;
607
+ border: 1px solid #ddd;
608
+ }
609
+ .loading-spinner {
610
+ border: 4px solid rgba(0, 0, 0, 0.1);
611
+ width: 24px;
612
+ height: 24px;
613
+ border-radius: 50%;
614
+ border-left-color: #FB7F0D;
615
+ animation: spin 1s linear infinite;
616
+ margin-right: 10px;
617
+ }
618
+ @keyframes spin {
619
+ 0% { transform: rotate(0deg); }
620
+ 100% { transform: rotate(360deg); }
621
+ }
622
+ .progress-bar {
623
+ height: 10px;
624
+ background-color: #FB7F0D;
625
+ border-radius: 5px;
626
+ width: 0%;
627
+ animation: progressAnim 2s ease-in-out infinite;
628
+ }
629
+ @keyframes progressAnim {
630
+ 0% { width: 10%; }
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;
661
+ border-radius: 8px;
662
+ padding: 15px;
663
+ border: 1px solid #e5e5e5;
664
+ }
665
+ .session-info {
666
+ background-color: #e8f4f8;
667
+ padding: 8px 12px;
668
+ border-radius: 4px;
669
+ font-size: 12px;
670
+ color: #0c5460;
671
+ margin-bottom: 10px;
672
+ text-align: center;
673
+ }
674
+ """