parkkyujin commited on
Commit
06c6c1e
ยท
verified ยท
1 Parent(s): 37ceaca

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +55 -55
src/streamlit_app.py CHANGED
@@ -36,7 +36,7 @@ st.sidebar.header("๐ŸŽ›๏ธ ์นดํ”ผ ์ƒ์„ฑ ์„ค์ •")
36
  default_api_key = os.getenv("GEMINI_API_KEY", "")
37
 
38
  api_key = st.sidebar.text_input(
39
- "๐Ÿ”‘ Gemini API ํ‚ค",
40
  value=default_api_key,
41
  type="password",
42
  help="ํ™˜๊ฒฝ๋ณ€์ˆ˜์— GEMINI_API_KEY๋กœ ์„ค์ •ํ•˜๋ฉด ์ž๋™ ์ž…๋ ฅ๋ฉ๋‹ˆ๋‹ค"
@@ -51,14 +51,14 @@ if not api_key:
51
  @st.cache_resource(show_spinner=False)
52
  def load_system():
53
  """์‹œ์Šคํ…œ ์ปดํฌ๋„ŒํŠธ ๋กœ๋”ฉ - ์ž„๋ฒ ๋”ฉ ๊ธฐ๋ฐ˜ RAG ์‹œ์Šคํ…œ"""
54
-
55
  progress_container = st.container()
56
-
57
  with progress_container:
58
  # ์ „์ฒด ์ง„ํ–‰๋ฅ 
59
  total_progress = st.progress(0)
60
  status_text = st.empty()
61
-
62
  # 1๋‹จ๊ณ„: API ์„ค์ • (10%)
63
  status_text.text("๐Ÿ”‘ Gemini API ์ดˆ๊ธฐํ™” ์ค‘...")
64
  try:
@@ -69,42 +69,42 @@ def load_system():
69
  except Exception as e:
70
  st.error(f"โŒ Gemini API ์„ค์ • ์‹คํŒจ: {e}")
71
  return None, None, None, None
72
-
73
  # 2๋‹จ๊ณ„: ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ ๋กœ๋“œ (40%)
74
  status_text.text("๐Ÿค– ํ•œ๊ตญ์–ด ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ ๋กœ๋”ฉ ์ค‘... (1-2๋ถ„ ์†Œ์š”)")
75
  embedding_model = None
76
-
77
  # ์•ˆ์ •์ ์ธ ๋ชจ๋ธ ๋กœ๋”ฉ ์ „๋žต
78
  try:
79
  # ๋จผ์ € ์บ์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ
80
  os.makedirs('/tmp/sentence_transformers', exist_ok=True)
81
  os.makedirs('/tmp/transformers', exist_ok=True)
82
-
83
  # sentence-transformers ์ž„ํฌํŠธ๋ฅผ ํ•จ์ˆ˜ ๋‚ด์—์„œ
84
  from sentence_transformers import SentenceTransformer
85
  from sklearn.metrics.pairwise import cosine_similarity
86
-
87
  # ํ•œ๊ตญ์–ด ๋ชจ๋ธ ๋กœ๋”ฉ ์‹œ๋„
88
- embedding_model = SentenceTransformer('jhgan/ko-sbert-nli',
89
  cache_folder='/tmp/sentence_transformers')
90
  total_progress.progress(40)
91
  st.success("โœ… ํ•œ๊ตญ์–ด ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ ๋กœ๋”ฉ ์™„๋ฃŒ")
92
-
93
  except Exception as e:
94
  st.error(f"โŒ ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ ๋กœ๋”ฉ ์‹คํŒจ: {e}")
95
  st.error("๐Ÿšจ ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ ์—†์ด๋Š” RAG ์‹œ์Šคํ…œ์ด ์ž‘๋™ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!")
96
  return None, None, None, None
97
-
98
  # 3๋‹จ๊ณ„: ๋ฐ์ดํ„ฐ ๋กœ๋“œ (60%)
99
  status_text.text("๐Ÿ“Š ์นดํ”ผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋กœ๋”ฉ ์ค‘...")
100
  try:
101
- df = pd.read_excel('๊ด‘๊ณ ์นดํ”ผ๋ฐ์ดํ„ฐ_๋ธŒ๋žœ๋“œ์ถ”์ถœ์™„๋ฃŒ.xlsx')
102
  total_progress.progress(60)
103
  st.success(f"โœ… ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์™„๋ฃŒ: {len(df):,}๊ฐœ ์นดํ”ผ")
104
  except Exception as e:
105
  st.error(f"โŒ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์‹คํŒจ: {e}")
106
  return None, None, None, None
107
-
108
  # 4๋‹จ๊ณ„: ์ž„๋ฒ ๋”ฉ ๋ฐ์ดํ„ฐ ๋กœ๋“œ (90%) - ์ด๊ฒŒ ํ•ต์‹ฌ!
109
  status_text.text("๐Ÿ” ๋ฒกํ„ฐ ์ž„๋ฒ ๋”ฉ ๋กœ๋”ฉ ์ค‘... (RAG ์‹œ์Šคํ…œ ํ•ต์‹ฌ)")
110
  try:
@@ -117,13 +117,13 @@ def load_system():
117
  st.error(f"โŒ ์ž„๋ฒ ๋”ฉ ๋กœ๋”ฉ ์‹คํŒจ: {e}")
118
  st.error("๐Ÿšจ ์ž„๋ฒ ๋”ฉ ์—†์ด๋Š” ์˜๋ฏธ์  ๊ฒ€์ƒ‰์ด ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค!")
119
  return None, None, None, None
120
-
121
  # 5๋‹จ๊ณ„: ์ตœ์ข… ๊ฒ€์ฆ (100%)
122
  status_text.text("โœจ ์‹œ์Šคํ…œ ๊ฒ€์ฆ ์ค‘...")
123
  if model and embedding_model and df is not None and embeddings is not None:
124
  total_progress.progress(100)
125
  status_text.text("๐ŸŽ‰ RAG ์‹œ์Šคํ…œ ๋กœ๋”ฉ ์™„๋ฃŒ!")
126
-
127
  # ์„ฑ๊ณต ๋ฉ”์‹œ์ง€
128
  success_col1, success_col2, success_col3 = st.columns(3)
129
  with success_col1:
@@ -132,12 +132,12 @@ def load_system():
132
  st.metric("์ž„๋ฒ ๋”ฉ ์ฐจ์›", f"{embeddings.shape[1]}D")
133
  with success_col3:
134
  st.metric("๊ฒ€์ƒ‰ ์—”์ง„", "Korean SBERT")
135
-
136
  # ์ง„ํ–‰๋ฅ  ๋ฐ” ์ œ๊ฑฐ
137
  time.sleep(1)
138
  total_progress.empty()
139
  status_text.empty()
140
-
141
  return model, embedding_model, df, embeddings
142
  else:
143
  st.error("โŒ ์‹œ์Šคํ…œ ๋กœ๋”ฉ ์‹คํŒจ: ํ•„์ˆ˜ ๊ตฌ์„ฑ์š”์†Œ ๋ˆ„๋ฝ")
@@ -204,14 +204,14 @@ else:
204
  # ํ…œํ”Œ๋ฆฟ ์„ ํƒ
205
  templates = {
206
  "์‹ ์ œํ’ˆ ๋Ÿฐ์นญ": "๋Œ€์ƒ {์นดํ…Œ๊ณ ๋ฆฌ} ์‹ ์ œํ’ˆ ๋Ÿฐ์นญ ์นดํ”ผ",
207
- "ํ• ์ธ ์ด๋ฒคํŠธ": "{์นดํ…Œ๊ณ ๋ฆฌ} ํ• ์ธ ์ด๋ฒคํŠธ ํ”„๋กœ๋ชจ์…˜ ์นดํ”ผ",
208
  "๋ธŒ๋žœ๋“œ ์Šฌ๋กœ๊ฑด": "{์นดํ…Œ๊ณ ๋ฆฌ} ๋ธŒ๋žœ๋“œ์˜ ๋Œ€ํ‘œ ์Šฌ๋กœ๊ฑด",
209
  "์•ฑ/์„œ๋น„์Šค ๋ฆฌ๋‰ด์–ผ": "{์„œ๋น„์Šค๋ช…} ์ƒˆ ๋ฒ„์ „ ์ถœ์‹œ ์นดํ”ผ",
210
  "์‹œ์ฆŒ ํ•œ์ •": "{์‹œ์ฆŒ} ํ•œ์ • {์นดํ…Œ๊ณ ๋ฆฌ} ํŠน๋ณ„ ์—๋””์…˜ ์นดํ”ผ"
211
  }
212
-
213
  selected_template = st.selectbox("ํ…œํ”Œ๋ฆฟ ์„ ํƒ:", list(templates.keys()))
214
-
215
  col1, col2 = st.columns(2)
216
  with col1:
217
  template_category = st.text_input("์ œํ’ˆ/์„œ๋น„์Šค:", value="")
@@ -224,7 +224,7 @@ else:
224
  user_request = templates[selected_template].format(์‹œ์ฆŒ=season, ์นดํ…Œ๊ณ ๋ฆฌ=template_category)
225
  else:
226
  user_request = templates[selected_template].format(์นดํ…Œ๊ณ ๋ฆฌ=template_category)
227
-
228
  st.text_area("์ƒ์„ฑ๋œ ์š”์ฒญ:", value=user_request, height=80, disabled=True)
229
 
230
  # ๊ณ ๊ธ‰ ์˜ต์…˜
@@ -240,41 +240,41 @@ with st.expander("๐Ÿ”ง ๊ณ ๊ธ‰ ์˜ต์…˜"):
240
  # RAG ์นดํ”ผ ์ƒ์„ฑ ํ•จ์ˆ˜ (์ž„๋ฒ ๋”ฉ ๊ธฐ๋ฐ˜ ํ•„์ˆ˜!)
241
  def generate_copy_with_rag(user_request, category, target, tone, creative, num_concepts):
242
  """RAG ๊ธฐ๋ฐ˜ ์นดํ”ผ ์ƒ์„ฑ - ์ž„๋ฒ ๋”ฉ ํ•„์ˆ˜ ์‚ฌ์šฉ"""
243
-
244
  if not user_request.strip():
245
  st.error("โŒ ์นดํ”ผ ์š”์ฒญ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”")
246
  return None
247
-
248
  # ์ง„ํ–‰ ์ƒํ™ฉ ํ‘œ์‹œ
249
  progress_bar = st.progress(0)
250
  status_text = st.empty()
251
-
252
  # 1๋‹จ๊ณ„: ์˜๋ฏธ์  ๊ฒ€์ƒ‰ (์ž„๋ฒ ๋”ฉ ๊ธฐ๋ฐ˜)
253
  status_text.text("๐Ÿ” ์˜๋ฏธ์  ๊ฒ€์ƒ‰ ์ค‘... (RAG ํ•ต์‹ฌ ๊ธฐ๋Šฅ)")
254
  progress_bar.progress(20)
255
-
256
  try:
257
  # ๊ฒ€์ƒ‰ ์ฟผ๋ฆฌ ์ƒ์„ฑ ๋ฐ ์ž„๋ฒ ๋”ฉ
258
  search_query = f"{user_request} {target} ๊ด‘๊ณ  ์นดํ”ผ"
259
  from sklearn.metrics.pairwise import cosine_similarity
260
  query_embedding = embedding_model.encode([search_query])
261
-
262
  # ์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ๋ง
263
  if category != '์ „์ฒด':
264
  filtered_df = df[df['์นดํ…Œ๊ณ ๋ฆฌ'] == category]
265
  else:
266
  filtered_df = df
267
-
268
  progress_bar.progress(40)
269
-
270
  # ์œ ์‚ฌ๋„ ๊ณ„์‚ฐ (์ž„๋ฒ ๋”ฉ์˜ ํ•ต์‹ฌ!)
271
  filtered_indices = filtered_df.index.tolist()
272
  filtered_embeddings = embeddings[filtered_indices]
273
  similarities = cosine_similarity(query_embedding, filtered_embeddings)[0]
274
-
275
  # ์ƒ์œ„ ์ฐธ๊ณ  ์นดํ”ผ ์„ ๋ณ„
276
  top_indices = np.argsort(similarities)[::-1][:num_references]
277
-
278
  reference_copies = []
279
  for idx in top_indices:
280
  original_idx = filtered_indices[idx]
@@ -285,31 +285,31 @@ def generate_copy_with_rag(user_request, category, target, tone, creative, num_c
285
  'brand': row['๋ธŒ๋žœ๋“œ'],
286
  'similarity': similarities[idx]
287
  })
288
-
289
  progress_bar.progress(60)
290
-
291
  if not reference_copies:
292
  st.warning(f"โš ๏ธ ์œ ์‚ฌ๋„ {min_similarity} ์ด์ƒ์ธ ์ฐธ๊ณ  ์นดํ”ผ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์œ ์‚ฌ๋„๋ฅผ ๋‚ฎ์ถฐ๋ณด์„ธ์š”.")
293
  progress_bar.empty()
294
  status_text.empty()
295
  return None
296
-
297
  # 2๋‹จ๊ณ„: AI ์นดํ”ผ ์ƒ์„ฑ
298
  status_text.text("๐Ÿค– AI ์นดํ”ผ ์ƒ์„ฑ ์ค‘...")
299
  progress_bar.progress(80)
300
-
301
  # ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ
302
  references_text = "\n".join([
303
  f"{i}. \"{ref['copy']}\" - {ref['brand']} (์œ ์‚ฌ๋„: {ref['similarity']:.3f})"
304
  for i, ref in enumerate(reference_copies, 1)
305
  ])
306
-
307
  creativity_guidance = {
308
  "๋ณด์ˆ˜์ ": "์•ˆ์ „ํ•˜๊ณ  ๊ฒ€์ฆ๋œ ํ‘œํ˜„์„ ์‚ฌ์šฉํ•˜์—ฌ",
309
- "๊ท ํ˜•": "์ฐฝ์˜์ ์ด๋ฉด์„œ๋„ ์ ์ ˆํ•œ ์ˆ˜์ค€์—์„œ",
310
  "์ฐฝ์˜์ ": "๋…์ฐฝ์ ์ด๊ณ  ํ˜์‹ ์ ์ธ ํ‘œํ˜„์œผ๋กœ"
311
  }
312
-
313
  prompt = f"""
314
  ๋‹น์‹ ์€ ํ•œ๊ตญ์˜ ์ „๋ฌธ ๊ด‘๊ณ  ์นดํ”ผ๋ผ์ดํ„ฐ์ž…๋‹ˆ๋‹ค.
315
 
@@ -332,7 +332,7 @@ def generate_copy_with_rag(user_request, category, target, tone, creative, num_c
332
  1. [์นดํ”ผ1]
333
  - ์„ค๋ช…: ์™œ ์ด ์นดํ”ผ๊ฐ€ ํšจ๊ณผ์ ์ธ์ง€ ๊ฐ„๋‹จํžˆ ์„ค๋ช…
334
 
335
- 2. [์นดํ”ผ2]
336
  - ์„ค๋ช…: ์™œ ์ด ์นดํ”ผ๊ฐ€ ํšจ๊ณผ์ ์ธ์ง€ ๊ฐ„๋‹จํžˆ ์„ค๋ช…
337
 
338
  3. [์นดํ”ผ3]
@@ -340,15 +340,15 @@ def generate_copy_with_rag(user_request, category, target, tone, creative, num_c
340
 
341
  **์ถ”์ฒœ ์นดํ”ผ:** ์œ„ ์ค‘ ๊ฐ€์žฅ ์ถ”์ฒœํ•˜๋Š” ์นดํ”ผ์™€ ์ด์œ 
342
  """
343
-
344
  response = model.generate_content(prompt)
345
  progress_bar.progress(100)
346
  status_text.text("โœ… ์™„๋ฃŒ!")
347
-
348
  time.sleep(0.5)
349
  progress_bar.empty()
350
  status_text.empty()
351
-
352
  return {
353
  'references': reference_copies,
354
  'generated_content': response.text,
@@ -364,7 +364,7 @@ def generate_copy_with_rag(user_request, category, target, tone, creative, num_c
364
  'creative': creative
365
  }
366
  }
367
-
368
  except Exception as e:
369
  st.error(f"โŒ ์นดํ”ผ ์ƒ์„ฑ ์‹คํŒจ: {e}")
370
  progress_bar.empty()
@@ -373,7 +373,7 @@ def generate_copy_with_rag(user_request, category, target, tone, creative, num_c
373
 
374
  # ์ƒ์„ฑ ๋ฒ„ํŠผ
375
  if st.button("๐Ÿš€ ์นดํ”ผ ์ƒ์„ฑํ•˜๊ธฐ", type="primary", use_container_width=True):
376
-
377
  if not user_request or not user_request.strip():
378
  st.error("โŒ ์นดํ”ผ ์š”์ฒญ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”")
379
  else:
@@ -386,29 +386,29 @@ if st.button("๐Ÿš€ ์นดํ”ผ ์ƒ์„ฑํ•˜๊ธฐ", type="primary", use_container_width=Tru
386
  creative=creative_level,
387
  num_concepts=num_concepts
388
  )
389
-
390
  if result:
391
  # ๊ฒฐ๊ณผ ํ‘œ์‹œ
392
  st.markdown("## ๐ŸŽ‰ ์ƒ์„ฑ๋œ ์นด๏ฟฝ๏ฟฝ๏ฟฝ")
393
  st.markdown("---")
394
-
395
  # ๊ฒ€์ƒ‰ ์ •๋ณด ํ‘œ์‹œ
396
  st.info(f"๐Ÿ” **๊ฒ€์ƒ‰ ์ •๋ณด**: {result['search_info']['total_candidates']:,}๊ฐœ ํ›„๋ณด์—์„œ "
397
  f"{result['search_info']['selected_references']}๊ฐœ ์ฐธ๊ณ  ์นดํ”ผ ์„ ๋ณ„")
398
-
399
  # ์ฐธ๊ณ  ์นดํ”ผ ํ‘œ์‹œ
400
  if show_references and result['references']:
401
  with st.expander("๐Ÿ“š ์ฐธ๊ณ ํ•œ ์นดํ”ผ๋“ค (์˜๋ฏธ์  ์œ ์‚ฌ๋„ ๊ธฐ๋ฐ˜ ์„ ๋ณ„)"):
402
  for i, ref in enumerate(result['references'], 1):
403
  st.markdown(f"**{i}.** \"{ref['copy']}\"")
404
- st.markdown(f" - ๋ธŒ๋žœ๋“œ: {ref['brand']}")
405
  st.markdown(f" - ์œ ์‚ฌ๋„: {ref['similarity']:.3f}")
406
  st.markdown("")
407
-
408
  # ์ƒ์„ฑ๋œ ์นดํ”ผ ํ‘œ์‹œ
409
  st.markdown("### โœจ AI๊ฐ€ ์ƒ์„ฑํ•œ ์นดํ”ผ:")
410
  st.markdown(result['generated_content'])
411
-
412
  # ๊ฒฐ๊ณผ ๋‹ค์šด๋กœ๋“œ
413
  result_json = json.dumps({
414
  'timestamp': datetime.now().isoformat(),
@@ -417,7 +417,7 @@ if st.button("๐Ÿš€ ์นดํ”ผ ์ƒ์„ฑํ•˜๊ธฐ", type="primary", use_container_width=Tru
417
  'search_info': result['search_info'],
418
  'generated_content': result['generated_content']
419
  }, ensure_ascii=False, indent=2)
420
-
421
  st.download_button(
422
  label="๐Ÿ’พ ๊ฒฐ๊ณผ ๋‹ค์šด๋กœ๋“œ (JSON)",
423
  data=result_json,
@@ -440,21 +440,21 @@ if df is not None and embeddings is not None:
440
  with st.expander("๐Ÿ’ก RAG ์‹œ์Šคํ…œ ์‚ฌ์šฉ๋ฒ• ๊ฐ€์ด๋“œ"):
441
  st.markdown("""
442
  ### ๐ŸŽฏ ํšจ๊ณผ์ ์ธ ์‚ฌ์šฉ๋ฒ•
443
-
444
  **1. ๊ตฌ์ฒด์ ์ธ ์š”์ฒญํ•˜๊ธฐ:**
445
  - โŒ "์นดํ”ผ ์จ์ค˜"
446
  - โœ… "30๋Œ€ ์ง์žฅ ์—ฌ์„ฑ์šฉ ํ”„๋ฆฌ๋ฏธ์—„ ์Šคํ‚จ์ผ€์–ด ์‹ ์ œํ’ˆ ๋Ÿฐ์นญ ์นดํ”ผ"
447
-
448
  **2. RAG ์‹œ์Šคํ…œ์˜ ์žฅ์ :**
449
  - ๐Ÿง  **์˜๋ฏธ์  ๊ฒ€์ƒ‰**: ํ‚ค์›Œ๋“œ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์˜๋ฏธ๊นŒ์ง€ ์ดํ•ด
450
  - ๐ŸŽฏ **๋ฌธ๋งฅ ๋งค์นญ**: ํƒ€๊ฒŸ๊ณผ ์ƒํ™ฉ์— ๋งž๋Š” ์นดํ”ผ ์ž๋™ ์„ ๋ณ„
451
  - ๐Ÿ“Š **๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜**: 37,671๊ฐœ ์‹ค์ œ ์นดํ”ผ์—์„œ ํ•™์Šตํ•œ ํŒจํ„ด
452
-
453
  **3. ์ฐฝ์˜์„ฑ ์กฐ์ ˆ:**
454
  - **๋ณด์ˆ˜์ **: ์•ˆ์ „ํ•œ ํด๋ผ์ด์–ธํŠธ, ๊ฒ€์ฆ๋œ ์ ‘๊ทผ
455
  - **๊ท ํ˜•**: ์ผ๋ฐ˜์ ์ธ ํ”„๋กœ์ ํŠธ (์ถ”์ฒœ!)
456
  - **์ฐฝ์˜์ **: ํ˜์‹ ์  ๋ธŒ๋žœ๋“œ, ํŒŒ๊ฒฉ์  ์บ ํŽ˜์ธ
457
-
458
  **4. ์ฐธ๊ณ  ์นดํ”ผ ํ™œ์šฉ:**
459
  - ์ƒ์„ฑ๋œ ์นดํ”ผ์™€ ์ฐธ๊ณ  ์นดํ”ผ๋ฅผ ๋น„๊ต ๋ถ„์„
460
  - ํŠธ๋ Œ๋“œ์™€ ํŒจํ„ด ํŒŒ์•… ๊ฐ€๋Šฅ
@@ -473,4 +473,4 @@ if os.getenv("DEBUG_MODE"):
473
  st.sidebar.markdown("### ๐Ÿ”ง ๋””๋ฒ„๊ทธ ์ •๋ณด")
474
  if 'embeddings' in locals():
475
  st.sidebar.write(f"์ž„๋ฒ ๋”ฉ ๋ฉ”๋ชจ๋ฆฌ: {embeddings.nbytes / (1024*1024):.1f}MB")
476
- st.sidebar.write(f"Streamlit ๋ฒ„์ „: {st.__version__}")
 
36
  default_api_key = os.getenv("GEMINI_API_KEY", "")
37
 
38
  api_key = st.sidebar.text_input(
39
+ "๐Ÿ”‘ Gemini API ํ‚ค",
40
  value=default_api_key,
41
  type="password",
42
  help="ํ™˜๊ฒฝ๋ณ€์ˆ˜์— GEMINI_API_KEY๋กœ ์„ค์ •ํ•˜๋ฉด ์ž๋™ ์ž…๋ ฅ๋ฉ๋‹ˆ๋‹ค"
 
51
  @st.cache_resource(show_spinner=False)
52
  def load_system():
53
  """์‹œ์Šคํ…œ ์ปดํฌ๋„ŒํŠธ ๋กœ๋”ฉ - ์ž„๋ฒ ๋”ฉ ๊ธฐ๋ฐ˜ RAG ์‹œ์Šคํ…œ"""
54
+
55
  progress_container = st.container()
56
+
57
  with progress_container:
58
  # ์ „์ฒด ์ง„ํ–‰๋ฅ 
59
  total_progress = st.progress(0)
60
  status_text = st.empty()
61
+
62
  # 1๋‹จ๊ณ„: API ์„ค์ • (10%)
63
  status_text.text("๐Ÿ”‘ Gemini API ์ดˆ๊ธฐํ™” ์ค‘...")
64
  try:
 
69
  except Exception as e:
70
  st.error(f"โŒ Gemini API ์„ค์ • ์‹คํŒจ: {e}")
71
  return None, None, None, None
72
+
73
  # 2๋‹จ๊ณ„: ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ ๋กœ๋“œ (40%)
74
  status_text.text("๐Ÿค– ํ•œ๊ตญ์–ด ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ ๋กœ๋”ฉ ์ค‘... (1-2๋ถ„ ์†Œ์š”)")
75
  embedding_model = None
76
+
77
  # ์•ˆ์ •์ ์ธ ๋ชจ๋ธ ๋กœ๋”ฉ ์ „๋žต
78
  try:
79
  # ๋จผ์ € ์บ์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ
80
  os.makedirs('/tmp/sentence_transformers', exist_ok=True)
81
  os.makedirs('/tmp/transformers', exist_ok=True)
82
+
83
  # sentence-transformers ์ž„ํฌํŠธ๋ฅผ ํ•จ์ˆ˜ ๋‚ด์—์„œ
84
  from sentence_transformers import SentenceTransformer
85
  from sklearn.metrics.pairwise import cosine_similarity
86
+
87
  # ํ•œ๊ตญ์–ด ๋ชจ๋ธ ๋กœ๋”ฉ ์‹œ๋„
88
+ embedding_model = SentenceTransformer('jhgan/ko-sbert-nli',
89
  cache_folder='/tmp/sentence_transformers')
90
  total_progress.progress(40)
91
  st.success("โœ… ํ•œ๊ตญ์–ด ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ ๋กœ๋”ฉ ์™„๋ฃŒ")
92
+
93
  except Exception as e:
94
  st.error(f"โŒ ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ ๋กœ๋”ฉ ์‹คํŒจ: {e}")
95
  st.error("๐Ÿšจ ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ ์—†์ด๋Š” RAG ์‹œ์Šคํ…œ์ด ์ž‘๋™ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!")
96
  return None, None, None, None
97
+
98
  # 3๋‹จ๊ณ„: ๋ฐ์ดํ„ฐ ๋กœ๋“œ (60%)
99
  status_text.text("๐Ÿ“Š ์นดํ”ผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋กœ๋”ฉ ์ค‘...")
100
  try:
101
+ df = pd.read_excel('../๊ด‘๊ณ ์นดํ”ผ๋ฐ์ดํ„ฐ_๋ธŒ๋žœ๋“œ์ถ”์ถœ์™„๋ฃŒ.xlsx')
102
  total_progress.progress(60)
103
  st.success(f"โœ… ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์™„๋ฃŒ: {len(df):,}๊ฐœ ์นดํ”ผ")
104
  except Exception as e:
105
  st.error(f"โŒ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์‹คํŒจ: {e}")
106
  return None, None, None, None
107
+
108
  # 4๋‹จ๊ณ„: ์ž„๋ฒ ๋”ฉ ๋ฐ์ดํ„ฐ ๋กœ๋“œ (90%) - ์ด๊ฒŒ ํ•ต์‹ฌ!
109
  status_text.text("๐Ÿ” ๋ฒกํ„ฐ ์ž„๋ฒ ๋”ฉ ๋กœ๋”ฉ ์ค‘... (RAG ์‹œ์Šคํ…œ ํ•ต์‹ฌ)")
110
  try:
 
117
  st.error(f"โŒ ์ž„๋ฒ ๋”ฉ ๋กœ๋”ฉ ์‹คํŒจ: {e}")
118
  st.error("๐Ÿšจ ์ž„๋ฒ ๋”ฉ ์—†์ด๋Š” ์˜๋ฏธ์  ๊ฒ€์ƒ‰์ด ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค!")
119
  return None, None, None, None
120
+
121
  # 5๋‹จ๊ณ„: ์ตœ์ข… ๊ฒ€์ฆ (100%)
122
  status_text.text("โœจ ์‹œ์Šคํ…œ ๊ฒ€์ฆ ์ค‘...")
123
  if model and embedding_model and df is not None and embeddings is not None:
124
  total_progress.progress(100)
125
  status_text.text("๐ŸŽ‰ RAG ์‹œ์Šคํ…œ ๋กœ๋”ฉ ์™„๋ฃŒ!")
126
+
127
  # ์„ฑ๊ณต ๋ฉ”์‹œ์ง€
128
  success_col1, success_col2, success_col3 = st.columns(3)
129
  with success_col1:
 
132
  st.metric("์ž„๋ฒ ๋”ฉ ์ฐจ์›", f"{embeddings.shape[1]}D")
133
  with success_col3:
134
  st.metric("๊ฒ€์ƒ‰ ์—”์ง„", "Korean SBERT")
135
+
136
  # ์ง„ํ–‰๋ฅ  ๋ฐ” ์ œ๊ฑฐ
137
  time.sleep(1)
138
  total_progress.empty()
139
  status_text.empty()
140
+
141
  return model, embedding_model, df, embeddings
142
  else:
143
  st.error("โŒ ์‹œ์Šคํ…œ ๋กœ๋”ฉ ์‹คํŒจ: ํ•„์ˆ˜ ๊ตฌ์„ฑ์š”์†Œ ๋ˆ„๋ฝ")
 
204
  # ํ…œํ”Œ๋ฆฟ ์„ ํƒ
205
  templates = {
206
  "์‹ ์ œํ’ˆ ๋Ÿฐ์นญ": "๋Œ€์ƒ {์นดํ…Œ๊ณ ๋ฆฌ} ์‹ ์ œํ’ˆ ๋Ÿฐ์นญ ์นดํ”ผ",
207
+ "ํ• ์ธ ์ด๋ฒคํŠธ": "{์นดํ…Œ๊ณ ๋ฆฌ} ํ• ์ธ ์ด๋ฒคํŠธ ํ”„๋กœ๋ชจ์…˜ ์นดํ”ผ",
208
  "๋ธŒ๋žœ๋“œ ์Šฌ๋กœ๊ฑด": "{์นดํ…Œ๊ณ ๋ฆฌ} ๋ธŒ๋žœ๋“œ์˜ ๋Œ€ํ‘œ ์Šฌ๋กœ๊ฑด",
209
  "์•ฑ/์„œ๋น„์Šค ๋ฆฌ๋‰ด์–ผ": "{์„œ๋น„์Šค๋ช…} ์ƒˆ ๋ฒ„์ „ ์ถœ์‹œ ์นดํ”ผ",
210
  "์‹œ์ฆŒ ํ•œ์ •": "{์‹œ์ฆŒ} ํ•œ์ • {์นดํ…Œ๊ณ ๋ฆฌ} ํŠน๋ณ„ ์—๋””์…˜ ์นดํ”ผ"
211
  }
212
+
213
  selected_template = st.selectbox("ํ…œํ”Œ๋ฆฟ ์„ ํƒ:", list(templates.keys()))
214
+
215
  col1, col2 = st.columns(2)
216
  with col1:
217
  template_category = st.text_input("์ œํ’ˆ/์„œ๋น„์Šค:", value="")
 
224
  user_request = templates[selected_template].format(์‹œ์ฆŒ=season, ์นดํ…Œ๊ณ ๋ฆฌ=template_category)
225
  else:
226
  user_request = templates[selected_template].format(์นดํ…Œ๊ณ ๋ฆฌ=template_category)
227
+
228
  st.text_area("์ƒ์„ฑ๋œ ์š”์ฒญ:", value=user_request, height=80, disabled=True)
229
 
230
  # ๊ณ ๊ธ‰ ์˜ต์…˜
 
240
  # RAG ์นดํ”ผ ์ƒ์„ฑ ํ•จ์ˆ˜ (์ž„๋ฒ ๋”ฉ ๊ธฐ๋ฐ˜ ํ•„์ˆ˜!)
241
  def generate_copy_with_rag(user_request, category, target, tone, creative, num_concepts):
242
  """RAG ๊ธฐ๋ฐ˜ ์นดํ”ผ ์ƒ์„ฑ - ์ž„๋ฒ ๋”ฉ ํ•„์ˆ˜ ์‚ฌ์šฉ"""
243
+
244
  if not user_request.strip():
245
  st.error("โŒ ์นดํ”ผ ์š”์ฒญ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”")
246
  return None
247
+
248
  # ์ง„ํ–‰ ์ƒํ™ฉ ํ‘œ์‹œ
249
  progress_bar = st.progress(0)
250
  status_text = st.empty()
251
+
252
  # 1๋‹จ๊ณ„: ์˜๋ฏธ์  ๊ฒ€์ƒ‰ (์ž„๋ฒ ๋”ฉ ๊ธฐ๋ฐ˜)
253
  status_text.text("๐Ÿ” ์˜๋ฏธ์  ๊ฒ€์ƒ‰ ์ค‘... (RAG ํ•ต์‹ฌ ๊ธฐ๋Šฅ)")
254
  progress_bar.progress(20)
255
+
256
  try:
257
  # ๊ฒ€์ƒ‰ ์ฟผ๋ฆฌ ์ƒ์„ฑ ๋ฐ ์ž„๋ฒ ๋”ฉ
258
  search_query = f"{user_request} {target} ๊ด‘๊ณ  ์นดํ”ผ"
259
  from sklearn.metrics.pairwise import cosine_similarity
260
  query_embedding = embedding_model.encode([search_query])
261
+
262
  # ์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ๋ง
263
  if category != '์ „์ฒด':
264
  filtered_df = df[df['์นดํ…Œ๊ณ ๋ฆฌ'] == category]
265
  else:
266
  filtered_df = df
267
+
268
  progress_bar.progress(40)
269
+
270
  # ์œ ์‚ฌ๋„ ๊ณ„์‚ฐ (์ž„๋ฒ ๋”ฉ์˜ ํ•ต์‹ฌ!)
271
  filtered_indices = filtered_df.index.tolist()
272
  filtered_embeddings = embeddings[filtered_indices]
273
  similarities = cosine_similarity(query_embedding, filtered_embeddings)[0]
274
+
275
  # ์ƒ์œ„ ์ฐธ๊ณ  ์นดํ”ผ ์„ ๋ณ„
276
  top_indices = np.argsort(similarities)[::-1][:num_references]
277
+
278
  reference_copies = []
279
  for idx in top_indices:
280
  original_idx = filtered_indices[idx]
 
285
  'brand': row['๋ธŒ๋žœ๋“œ'],
286
  'similarity': similarities[idx]
287
  })
288
+
289
  progress_bar.progress(60)
290
+
291
  if not reference_copies:
292
  st.warning(f"โš ๏ธ ์œ ์‚ฌ๋„ {min_similarity} ์ด์ƒ์ธ ์ฐธ๊ณ  ์นดํ”ผ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์œ ์‚ฌ๋„๋ฅผ ๋‚ฎ์ถฐ๋ณด์„ธ์š”.")
293
  progress_bar.empty()
294
  status_text.empty()
295
  return None
296
+
297
  # 2๋‹จ๊ณ„: AI ์นดํ”ผ ์ƒ์„ฑ
298
  status_text.text("๐Ÿค– AI ์นดํ”ผ ์ƒ์„ฑ ์ค‘...")
299
  progress_bar.progress(80)
300
+
301
  # ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ
302
  references_text = "\n".join([
303
  f"{i}. \"{ref['copy']}\" - {ref['brand']} (์œ ์‚ฌ๋„: {ref['similarity']:.3f})"
304
  for i, ref in enumerate(reference_copies, 1)
305
  ])
306
+
307
  creativity_guidance = {
308
  "๋ณด์ˆ˜์ ": "์•ˆ์ „ํ•˜๊ณ  ๊ฒ€์ฆ๋œ ํ‘œํ˜„์„ ์‚ฌ์šฉํ•˜์—ฌ",
309
+ "๊ท ํ˜•": "์ฐฝ์˜์ ์ด๋ฉด์„œ๋„ ์ ์ ˆํ•œ ์ˆ˜์ค€์—์„œ",
310
  "์ฐฝ์˜์ ": "๋…์ฐฝ์ ์ด๊ณ  ํ˜์‹ ์ ์ธ ํ‘œํ˜„์œผ๋กœ"
311
  }
312
+
313
  prompt = f"""
314
  ๋‹น์‹ ์€ ํ•œ๊ตญ์˜ ์ „๋ฌธ ๊ด‘๊ณ  ์นดํ”ผ๋ผ์ดํ„ฐ์ž…๋‹ˆ๋‹ค.
315
 
 
332
  1. [์นดํ”ผ1]
333
  - ์„ค๋ช…: ์™œ ์ด ์นดํ”ผ๊ฐ€ ํšจ๊ณผ์ ์ธ์ง€ ๊ฐ„๋‹จํžˆ ์„ค๋ช…
334
 
335
+ 2. [์นดํ”ผ2]
336
  - ์„ค๋ช…: ์™œ ์ด ์นดํ”ผ๊ฐ€ ํšจ๊ณผ์ ์ธ์ง€ ๊ฐ„๋‹จํžˆ ์„ค๋ช…
337
 
338
  3. [์นดํ”ผ3]
 
340
 
341
  **์ถ”์ฒœ ์นดํ”ผ:** ์œ„ ์ค‘ ๊ฐ€์žฅ ์ถ”์ฒœํ•˜๋Š” ์นดํ”ผ์™€ ์ด์œ 
342
  """
343
+
344
  response = model.generate_content(prompt)
345
  progress_bar.progress(100)
346
  status_text.text("โœ… ์™„๋ฃŒ!")
347
+
348
  time.sleep(0.5)
349
  progress_bar.empty()
350
  status_text.empty()
351
+
352
  return {
353
  'references': reference_copies,
354
  'generated_content': response.text,
 
364
  'creative': creative
365
  }
366
  }
367
+
368
  except Exception as e:
369
  st.error(f"โŒ ์นดํ”ผ ์ƒ์„ฑ ์‹คํŒจ: {e}")
370
  progress_bar.empty()
 
373
 
374
  # ์ƒ์„ฑ ๋ฒ„ํŠผ
375
  if st.button("๐Ÿš€ ์นดํ”ผ ์ƒ์„ฑํ•˜๊ธฐ", type="primary", use_container_width=True):
376
+
377
  if not user_request or not user_request.strip():
378
  st.error("โŒ ์นดํ”ผ ์š”์ฒญ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”")
379
  else:
 
386
  creative=creative_level,
387
  num_concepts=num_concepts
388
  )
389
+
390
  if result:
391
  # ๊ฒฐ๊ณผ ํ‘œ์‹œ
392
  st.markdown("## ๐ŸŽ‰ ์ƒ์„ฑ๋œ ์นด๏ฟฝ๏ฟฝ๏ฟฝ")
393
  st.markdown("---")
394
+
395
  # ๊ฒ€์ƒ‰ ์ •๋ณด ํ‘œ์‹œ
396
  st.info(f"๐Ÿ” **๊ฒ€์ƒ‰ ์ •๋ณด**: {result['search_info']['total_candidates']:,}๊ฐœ ํ›„๋ณด์—์„œ "
397
  f"{result['search_info']['selected_references']}๊ฐœ ์ฐธ๊ณ  ์นดํ”ผ ์„ ๋ณ„")
398
+
399
  # ์ฐธ๊ณ  ์นดํ”ผ ํ‘œ์‹œ
400
  if show_references and result['references']:
401
  with st.expander("๐Ÿ“š ์ฐธ๊ณ ํ•œ ์นดํ”ผ๋“ค (์˜๋ฏธ์  ์œ ์‚ฌ๋„ ๊ธฐ๋ฐ˜ ์„ ๋ณ„)"):
402
  for i, ref in enumerate(result['references'], 1):
403
  st.markdown(f"**{i}.** \"{ref['copy']}\"")
404
+ st.markdown(f" - ๋ธŒ๋žœ๋“œ: {ref['brand']}")
405
  st.markdown(f" - ์œ ์‚ฌ๋„: {ref['similarity']:.3f}")
406
  st.markdown("")
407
+
408
  # ์ƒ์„ฑ๋œ ์นดํ”ผ ํ‘œ์‹œ
409
  st.markdown("### โœจ AI๊ฐ€ ์ƒ์„ฑํ•œ ์นดํ”ผ:")
410
  st.markdown(result['generated_content'])
411
+
412
  # ๊ฒฐ๊ณผ ๋‹ค์šด๋กœ๋“œ
413
  result_json = json.dumps({
414
  'timestamp': datetime.now().isoformat(),
 
417
  'search_info': result['search_info'],
418
  'generated_content': result['generated_content']
419
  }, ensure_ascii=False, indent=2)
420
+
421
  st.download_button(
422
  label="๐Ÿ’พ ๊ฒฐ๊ณผ ๋‹ค์šด๋กœ๋“œ (JSON)",
423
  data=result_json,
 
440
  with st.expander("๐Ÿ’ก RAG ์‹œ์Šคํ…œ ์‚ฌ์šฉ๋ฒ• ๊ฐ€์ด๋“œ"):
441
  st.markdown("""
442
  ### ๐ŸŽฏ ํšจ๊ณผ์ ์ธ ์‚ฌ์šฉ๋ฒ•
443
+
444
  **1. ๊ตฌ์ฒด์ ์ธ ์š”์ฒญํ•˜๊ธฐ:**
445
  - โŒ "์นดํ”ผ ์จ์ค˜"
446
  - โœ… "30๋Œ€ ์ง์žฅ ์—ฌ์„ฑ์šฉ ํ”„๋ฆฌ๋ฏธ์—„ ์Šคํ‚จ์ผ€์–ด ์‹ ์ œํ’ˆ ๋Ÿฐ์นญ ์นดํ”ผ"
447
+
448
  **2. RAG ์‹œ์Šคํ…œ์˜ ์žฅ์ :**
449
  - ๐Ÿง  **์˜๋ฏธ์  ๊ฒ€์ƒ‰**: ํ‚ค์›Œ๋“œ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์˜๋ฏธ๊นŒ์ง€ ์ดํ•ด
450
  - ๐ŸŽฏ **๋ฌธ๋งฅ ๋งค์นญ**: ํƒ€๊ฒŸ๊ณผ ์ƒํ™ฉ์— ๋งž๋Š” ์นดํ”ผ ์ž๋™ ์„ ๋ณ„
451
  - ๐Ÿ“Š **๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜**: 37,671๊ฐœ ์‹ค์ œ ์นดํ”ผ์—์„œ ํ•™์Šตํ•œ ํŒจํ„ด
452
+
453
  **3. ์ฐฝ์˜์„ฑ ์กฐ์ ˆ:**
454
  - **๋ณด์ˆ˜์ **: ์•ˆ์ „ํ•œ ํด๋ผ์ด์–ธํŠธ, ๊ฒ€์ฆ๋œ ์ ‘๊ทผ
455
  - **๊ท ํ˜•**: ์ผ๋ฐ˜์ ์ธ ํ”„๋กœ์ ํŠธ (์ถ”์ฒœ!)
456
  - **์ฐฝ์˜์ **: ํ˜์‹ ์  ๋ธŒ๋žœ๋“œ, ํŒŒ๊ฒฉ์  ์บ ํŽ˜์ธ
457
+
458
  **4. ์ฐธ๊ณ  ์นดํ”ผ ํ™œ์šฉ:**
459
  - ์ƒ์„ฑ๋œ ์นดํ”ผ์™€ ์ฐธ๊ณ  ์นดํ”ผ๋ฅผ ๋น„๊ต ๋ถ„์„
460
  - ํŠธ๋ Œ๋“œ์™€ ํŒจํ„ด ํŒŒ์•… ๊ฐ€๋Šฅ
 
473
  st.sidebar.markdown("### ๐Ÿ”ง ๋””๋ฒ„๊ทธ ์ •๋ณด")
474
  if 'embeddings' in locals():
475
  st.sidebar.write(f"์ž„๋ฒ ๋”ฉ ๋ฉ”๋ชจ๋ฆฌ: {embeddings.nbytes / (1024*1024):.1f}MB")
476
+ st.sidebar.write(f"Streamlit ๋ฒ„์ „: {st.__version__}")