parkkyujin commited on
Commit
37ceaca
ยท
verified ยท
1 Parent(s): 793b7a4

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +250 -167
src/streamlit_app.py CHANGED
@@ -1,11 +1,9 @@
1
- # Hugging Face Spaces์šฉ app.py
2
- # ์›๋ณธ streamlit_app.py์—์„œ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์ฒ˜๋ฆฌ๋งŒ ๊ฐœ์„ 
3
 
4
  import streamlit as st
5
  import pandas as pd
6
  import numpy as np
7
- from sentence_transformers import SentenceTransformer
8
- from sklearn.metrics.pairwise import cosine_similarity
9
  import pickle
10
  import google.generativeai as genai
11
  import time
@@ -13,6 +11,11 @@ import json
13
  import os
14
  from datetime import datetime
15
 
 
 
 
 
 
16
  # ํŽ˜์ด์ง€ ์„ค์ •
17
  st.set_page_config(
18
  page_title="AI ์นดํ”ผ๋ผ์ดํ„ฐ | RAG ๊ธฐ๋ฐ˜ ๊ด‘๊ณ  ์นดํ”ผ ์ƒ์„ฑ",
@@ -23,7 +26,7 @@ st.set_page_config(
23
 
24
  # ์ œ๋ชฉ ๋ฐ ์„ค๋ช…
25
  st.title("โœจ AI ์นดํ”ผ๋ผ์ดํ„ฐ")
26
- st.markdown("### ๐ŸŽฏ 37,000๊ฐœ ์‹ค์ œ ๊ด‘๊ณ  ์นดํ”ผ ๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜ RAG ์‹œ์Šคํ…œ")
27
  st.markdown("---")
28
 
29
  # ์‚ฌ์ด๋“œ๋ฐ” ์„ค์ •
@@ -33,59 +36,123 @@ st.sidebar.header("๐ŸŽ›๏ธ ์นดํ”ผ ์ƒ์„ฑ ์„ค์ •")
33
  default_api_key = os.getenv("GEMINI_API_KEY", "")
34
 
35
  api_key = st.sidebar.text_input(
36
- "๐Ÿ”‘ Gemini API ํ‚ค",
37
  value=default_api_key,
38
  type="password",
39
- help="https://makersuite.google.com/app/apikey ์—์„œ ๋ฐœ๊ธ‰\nํ™˜๊ฒฝ๋ณ€์ˆ˜์— GEMINI_API_KEY๋กœ ์„ค์ •ํ•˜๋ฉด ์ž๋™ ์ž…๋ ฅ๋ฉ๋‹ˆ๋‹ค"
40
  )
41
 
42
  if not api_key:
43
  st.warning("โš ๏ธ Gemini API ํ‚ค๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”")
44
- st.info("๐Ÿ’ก Hugging Face Spaces ๊ด€๋ฆฌ์ž๋ผ๋ฉด Settings โ†’ Repository secrets์—์„œ GEMINI_API_KEY๋ฅผ ์„ค์ •ํ•˜์„ธ์š”")
45
  st.stop()
46
 
47
- # ์‹œ์Šคํ…œ ์ดˆ๊ธฐํ™” (์บ์‹ฑ)
48
- @st.cache_resource
49
  def load_system():
50
- """์‹œ์Šคํ…œ ์ปดํฌ๋„ŒํŠธ ๋กœ๋”ฉ (ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰)"""
51
- try:
52
- # API ์„ค์ •
53
- genai.configure(api_key=api_key)
54
- model = genai.GenerativeModel('gemini-2.5-flash-preview-05-20')
55
-
56
- # ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ ๋กœ๋“œ
57
- with st.spinner("๐Ÿค– Korean SBERT ๋ชจ๋ธ ๋กœ๋”ฉ ์ค‘... (์ตœ์ดˆ 1-2๋ถ„ ์†Œ์š”)"):
58
- embedding_model = SentenceTransformer('jhgan/ko-sbert-nli')
59
-
60
- # ๋ฐ์ดํ„ฐ ๋กœ๋“œ
61
- with st.spinner("๐Ÿ“Š ์นดํ”ผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋กœ๋”ฉ ์ค‘..."):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  df = pd.read_excel('๊ด‘๊ณ ์นดํ”ผ๋ฐ์ดํ„ฐ_๋ธŒ๋žœ๋“œ์ถ”์ถœ์™„๋ฃŒ.xlsx')
63
-
64
- # ์ž„๋ฒ ๋”ฉ ๋กœ๋“œ
65
- with st.spinner("๐Ÿ” ๋ฒกํ„ฐ ์ž„๋ฒ ๋”ฉ ๋กœ๋”ฉ ์ค‘..."):
 
 
 
 
 
 
66
  with open('copy_embeddings.pkl', 'rb') as f:
67
  embeddings_data = pickle.load(f)
68
  embeddings = embeddings_data['embeddings']
69
-
70
- return model, embedding_model, df, embeddings
71
- except FileNotFoundError as e:
72
- st.error(f"โŒ ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: {e}")
73
- st.error("ํ•„์š”ํ•œ ํŒŒ์ผ๋“ค์ด ์˜ฌ๋ฐ”๋ฅธ ์œ„์น˜์— ์žˆ๋Š”์ง€ ํ™•์ธํ•ด์ฃผ์„ธ์š”:")
74
- st.code("- ๊ด‘๊ณ ์นดํ”ผ๋ฐ์ดํ„ฐ_๋ธŒ๋žœ๋“œ์ถ”์ถœ์™„๋ฃŒ.xlsx\n- copy_embeddings.pkl")
75
- return None, None, None, None
76
- except Exception as e:
77
- st.error(f"โŒ ์‹œ์Šคํ…œ ๋กœ๋”ฉ ์‹คํŒจ: {e}")
78
- return None, None, None, None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
  # ์‹œ์Šคํ…œ ๋กœ๋”ฉ
81
  with st.spinner("๐Ÿš€ AI ์นดํ”ผ๋ผ์ดํ„ฐ ์‹œ์Šคํ…œ ์ดˆ๊ธฐํ™” ์ค‘..."):
82
  model, embedding_model, df, embeddings = load_system()
83
 
84
- if model is None:
85
- st.error("โŒ ์‹œ์Šคํ…œ์„ ๋กœ๋”ฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
86
  st.stop()
87
 
88
- st.success(f"โœ… ์‹œ์Šคํ…œ ๋กœ๋”ฉ ์™„๋ฃŒ: {len(df):,}๊ฐœ ์นดํ”ผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค")
 
89
 
90
  # ์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ
91
  categories = ['์ „์ฒด'] + sorted(df['์นดํ…Œ๊ณ ๋ฆฌ'].unique().tolist())
@@ -98,14 +165,14 @@ selected_category = st.sidebar.selectbox(
98
  # ํƒ€๊ฒŸ ๊ณ ๊ฐ ์„ค์ •
99
  target_audience = st.sidebar.selectbox(
100
  "๐ŸŽฏ ํƒ€๊ฒŸ ๊ณ ๊ฐ",
101
- ['์ผ๋ฐ˜', '10๋Œ€', '20๋Œ€', '30๋Œ€', '40๋Œ€', '50๋Œ€+', '๋‚จ์„ฑ', '์—ฌ์„ฑ', '์ง์žฅ์ธ', 'ํ•™์ƒ', '์ฃผ๋ถ€'],
102
- index=2 # 20๋Œ€ ๊ธฐ๋ณธ ์„ ํƒ
103
  )
104
 
105
  # ๋ธŒ๋žœ๋“œ ํ†ค์•ค๋งค๋„ˆ
106
  brand_tone = st.sidebar.selectbox(
107
  "๐ŸŽจ ๋ธŒ๋žœ๋“œ ํ†ค",
108
- ['์นœ๊ทผํ•œ', '์„ธ๋ จ๋œ', '๊ณ ๊ธ‰์Šค๋Ÿฌ์šด', 'ํ™œ๊ธฐ์ฐฌ', '์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š”', '์ Š์€', '๋”ฐ๋œปํ•œ', '์ „๋ฌธ์ ์ธ'],
109
  help="์›ํ•˜๋Š” ๋ธŒ๋žœ๋“œ ์ด๋ฏธ์ง€๋ฅผ ์„ ํƒํ•˜์„ธ์š”"
110
  )
111
 
@@ -136,37 +203,28 @@ if input_method == "์ง์ ‘ ์ž…๋ ฅ":
136
  else:
137
  # ํ…œํ”Œ๋ฆฟ ์„ ํƒ
138
  templates = {
139
- "์‹ ์ œํ’ˆ ๋Ÿฐ์นญ": "{ํƒ€๊ฒŸ} ๋Œ€์ƒ {์นดํ…Œ๊ณ ๋ฆฌ} ์‹ ์ œํ’ˆ ๋Ÿฐ์นญ ์นดํ”ผ",
140
- "ํ• ์ธ ์ด๋ฒคํŠธ": "{์นดํ…Œ๊ณ ๋ฆฌ} {ํƒ€๊ฒŸ} ํ• ์ธ ์ด๋ฒคํŠธ ํ”„๋กœ๋ชจ์…˜ ์นดํ”ผ",
141
  "๋ธŒ๋žœ๋“œ ์Šฌ๋กœ๊ฑด": "{์นดํ…Œ๊ณ ๋ฆฌ} ๋ธŒ๋žœ๋“œ์˜ ๋Œ€ํ‘œ ์Šฌ๋กœ๊ฑด",
142
- "์•ฑ/์„œ๋น„์Šค ๋ฆฌ๋‰ด์–ผ": "{ํƒ€๊ฒŸ} ๋Œ€์ƒ {์„œ๋น„์Šค๋ช…} ์ƒˆ ๋ฒ„์ „ ์ถœ์‹œ ์นดํ”ผ",
143
  "์‹œ์ฆŒ ํ•œ์ •": "{์‹œ์ฆŒ} ํ•œ์ • {์นดํ…Œ๊ณ ๋ฆฌ} ํŠน๋ณ„ ์—๋””์…˜ ์นดํ”ผ"
144
  }
145
-
146
  selected_template = st.selectbox("ํ…œํ”Œ๋ฆฟ ์„ ํƒ:", list(templates.keys()))
147
-
148
- # ํ…œํ”Œ๋ฆฟ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•
149
  col1, col2 = st.columns(2)
150
  with col1:
151
- template_target = st.text_input("ํƒ€๊ฒŸ ๊ณ ๊ฐ:", value=target_audience)
152
- with col2:
153
  template_category = st.text_input("์ œํ’ˆ/์„œ๋น„์Šค:", value="")
154
-
155
- if selected_template == "์•ฑ/์„œ๋น„์Šค ๋ฆฌ๋‰ด์–ผ":
156
- service_name = st.text_input("์„œ๋น„์Šค๋ช…:", placeholder="์˜ˆ: ๋ฐฐ๋‹ฌ์•ฑ, ๊ธˆ์œต์•ฑ")
157
- user_request = templates[selected_template].format(
158
- ํƒ€๊ฒŸ=template_target, ์„œ๋น„์Šค๋ช…=service_name
159
- )
160
- elif selected_template == "์‹œ์ฆŒ ํ•œ์ •":
161
- season = st.selectbox("์‹œ์ฆŒ:", ["๋ด„", "์—ฌ๋ฆ„", "๊ฐ€์„", "๊ฒจ์šธ", "ํฌ๋ฆฌ์Šค๋งˆ์Šค", "์‹ ๋…„"])
162
- user_request = templates[selected_template].format(
163
- ์‹œ์ฆŒ=season, ์นดํ…Œ๊ณ ๋ฆฌ=template_category
164
- )
165
- else:
166
- user_request = templates[selected_template].format(
167
- ํƒ€๊ฒŸ=template_target, ์นดํ…Œ๊ณ ๋ฆฌ=template_category
168
- )
169
-
170
  st.text_area("์ƒ์„ฑ๋œ ์š”์ฒญ:", value=user_request, height=80, disabled=True)
171
 
172
  # ๊ณ ๊ธ‰ ์˜ต์…˜
@@ -174,81 +232,85 @@ with st.expander("๐Ÿ”ง ๊ณ ๊ธ‰ ์˜ต์…˜"):
174
  col1, col2 = st.columns(2)
175
  with col1:
176
  num_concepts = st.slider("์ƒ์„ฑํ•  ์ปจ์…‰ ์ˆ˜:", 1, 5, 3)
177
- exclude_brand = st.text_input("์ œ์™ธํ•  ๋ธŒ๋žœ๋“œ:", placeholder="๊ฒฝ์Ÿ์‚ฌ ๋ธŒ๋žœ๋“œ๋ช…")
178
- with col2:
179
  min_similarity = st.slider("์ตœ์†Œ ์œ ์‚ฌ๋„:", 0.0, 1.0, 0.3, 0.1)
 
180
  show_references = st.checkbox("์ฐธ๊ณ  ์นดํ”ผ ๋ณด๊ธฐ", value=True)
 
181
 
182
- # ์นดํ”ผ ์ƒ์„ฑ ํ•จ์ˆ˜
183
- def generate_copy_web(user_request, category, target, tone, creative, num_concepts):
184
- """์›น์šฉ ์นดํ”ผ ์ƒ์„ฑ ํ•จ์ˆ˜"""
185
-
186
  if not user_request.strip():
187
  st.error("โŒ ์นดํ”ผ ์š”์ฒญ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”")
188
  return None
189
-
190
  # ์ง„ํ–‰ ์ƒํ™ฉ ํ‘œ์‹œ
191
  progress_bar = st.progress(0)
192
  status_text = st.empty()
193
-
194
- # 1๋‹จ๊ณ„: ๊ฒ€์ƒ‰
195
- status_text.text("๐Ÿ” ๊ด€๋ จ ์นดํ”ผ ๊ฒ€์ƒ‰ ์ค‘...")
196
  progress_bar.progress(20)
197
-
198
- # ๊ฒ€์ƒ‰ ์ฟผ๋ฆฌ ์ƒ์„ฑ
199
- search_query = f"{user_request} {target} ๊ด‘๊ณ  ์นดํ”ผ"
200
- query_embedding = embedding_model.encode([search_query])
201
-
202
- # ์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ๋ง
203
- if category != '์ „์ฒด':
204
- filtered_df = df[df['์นดํ…Œ๊ณ ๋ฆฌ'] == category]
205
- else:
206
- filtered_df = df
207
-
208
- progress_bar.progress(40)
209
-
210
- # ์œ ์‚ฌ๋„ ๊ณ„์‚ฐ
211
- filtered_indices = filtered_df.index.tolist()
212
- filtered_embeddings = embeddings[filtered_indices]
213
- similarities = cosine_similarity(query_embedding, filtered_embeddings)[0]
214
-
215
- # ์ƒ์œ„ 5๊ฐœ ์„ ๋ณ„
216
- top_indices = np.argsort(similarities)[::-1][:5]
217
-
218
- reference_copies = []
219
- for idx in top_indices:
220
- original_idx = filtered_indices[idx]
221
- row = df.iloc[original_idx]
222
- if similarities[idx] >= min_similarity:
223
- reference_copies.append({
224
- 'copy': row['์นดํ”ผ ๋‚ด์šฉ'],
225
- 'brand': row['๋ธŒ๋žœ๋“œ'],
226
- 'similarity': similarities[idx]
227
- })
228
-
229
- progress_bar.progress(60)
230
-
231
- if not reference_copies:
232
- st.warning(f"โš ๏ธ ์œ ์‚ฌ๋„ {min_similarity} ์ด์ƒ์ธ ์ฐธ๊ณ  ์นดํ”ผ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
233
- return None
234
-
235
- # 2๋‹จ๊ณ„: ์นดํ”ผ ์ƒ์„ฑ
236
- status_text.text("๐Ÿค– AI ์นดํ”ผ ์ƒ์„ฑ ์ค‘...")
237
- progress_bar.progress(80)
238
-
239
- # ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ
240
- references_text = "\n".join([
241
- f"{i}. \"{ref['copy']}\" - {ref['brand']}"
242
- for i, ref in enumerate(reference_copies, 1)
243
- ])
244
-
245
- creativity_guidance = {
246
- "๋ณด์ˆ˜์ ": "์•ˆ์ „ํ•˜๊ณ  ๊ฒ€์ฆ๋œ ํ‘œํ˜„์„ ์‚ฌ์šฉํ•˜์—ฌ",
247
- "๊ท ํ˜•": "์ฐฝ์˜์ ์ด๋ฉด์„œ๋„ ์ ์ ˆํ•œ ์ˆ˜์ค€์—์„œ",
248
- "์ฐฝ์˜์ ": "๋…์ฐฝ์ ์ด๊ณ  ํ˜์‹ ์ ์ธ ํ‘œํ˜„์œผ๋กœ"
249
- }
250
-
251
- prompt = f"""
 
 
 
 
252
  ๋‹น์‹ ์€ ํ•œ๊ตญ์˜ ์ „๋ฌธ ๊ด‘๊ณ  ์นดํ”ผ๋ผ์ดํ„ฐ์ž…๋‹ˆ๋‹ค.
253
 
254
  **์š”์ฒญ์‚ฌํ•ญ:** {user_request}
@@ -256,7 +318,7 @@ def generate_copy_web(user_request, category, target, tone, creative, num_concep
256
  **๋ธŒ๋žœ๋“œ ํ†ค:** {tone}
257
  **์ฐฝ์˜์„ฑ ์ˆ˜์ค€:** {creative}
258
 
259
- **์ฐธ๊ณ  ์นดํ”ผ๋“ค:**
260
  {references_text}
261
 
262
  **์ž‘์„ฑ ๊ฐ€์ด๋“œ๋ผ์ธ:**
@@ -270,7 +332,7 @@ def generate_copy_web(user_request, category, target, tone, creative, num_concep
270
  1. [์นดํ”ผ1]
271
  - ์„ค๋ช…: ์™œ ์ด ์นดํ”ผ๊ฐ€ ํšจ๊ณผ์ ์ธ์ง€ ๊ฐ„๋‹จํžˆ ์„ค๋ช…
272
 
273
- 2. [์นดํ”ผ2]
274
  - ์„ค๋ช…: ์™œ ์ด ์นดํ”ผ๊ฐ€ ํšจ๊ณผ์ ์ธ์ง€ ๊ฐ„๋‹จํžˆ ์„ค๋ช…
275
 
276
  3. [์นดํ”ผ3]
@@ -278,19 +340,23 @@ def generate_copy_web(user_request, category, target, tone, creative, num_concep
278
 
279
  **์ถ”์ฒœ ์นดํ”ผ:** ์œ„ ์ค‘ ๊ฐ€์žฅ ์ถ”์ฒœํ•˜๋Š” ์นดํ”ผ์™€ ์ด์œ 
280
  """
281
-
282
- try:
283
  response = model.generate_content(prompt)
284
  progress_bar.progress(100)
285
  status_text.text("โœ… ์™„๋ฃŒ!")
286
-
287
  time.sleep(0.5)
288
  progress_bar.empty()
289
  status_text.empty()
290
-
291
  return {
292
  'references': reference_copies,
293
  'generated_content': response.text,
 
 
 
 
 
294
  'settings': {
295
  'category': category,
296
  'target': target,
@@ -298,19 +364,21 @@ def generate_copy_web(user_request, category, target, tone, creative, num_concep
298
  'creative': creative
299
  }
300
  }
301
-
302
  except Exception as e:
303
  st.error(f"โŒ ์นดํ”ผ ์ƒ์„ฑ ์‹คํŒจ: {e}")
 
 
304
  return None
305
 
306
  # ์ƒ์„ฑ ๋ฒ„ํŠผ
307
  if st.button("๐Ÿš€ ์นดํ”ผ ์ƒ์„ฑํ•˜๊ธฐ", type="primary", use_container_width=True):
308
-
309
  if not user_request or not user_request.strip():
310
  st.error("โŒ ์นดํ”ผ ์š”์ฒญ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”")
311
  else:
312
- # ์นดํ”ผ ์ƒ์„ฑ
313
- result = generate_copy_web(
314
  user_request=user_request,
315
  category=selected_category,
316
  target=target_audience,
@@ -318,33 +386,38 @@ if st.button("๐Ÿš€ ์นดํ”ผ ์ƒ์„ฑํ•˜๊ธฐ", type="primary", use_container_width=Tru
318
  creative=creative_level,
319
  num_concepts=num_concepts
320
  )
321
-
322
  if result:
323
  # ๊ฒฐ๊ณผ ํ‘œ์‹œ
324
  st.markdown("## ๐ŸŽ‰ ์ƒ์„ฑ๋œ ์นดํ”ผ")
325
  st.markdown("---")
326
-
 
 
 
 
327
  # ์ฐธ๊ณ  ์นดํ”ผ ํ‘œ์‹œ
328
  if show_references and result['references']:
329
- with st.expander("๐Ÿ“š ์ฐธ๊ณ ํ•œ ์นดํ”ผ๋“ค"):
330
  for i, ref in enumerate(result['references'], 1):
331
  st.markdown(f"**{i}.** \"{ref['copy']}\"")
332
- st.markdown(f" - ๋ธŒ๋žœ๋“œ: {ref['brand']}")
333
  st.markdown(f" - ์œ ์‚ฌ๋„: {ref['similarity']:.3f}")
334
  st.markdown("")
335
-
336
  # ์ƒ์„ฑ๋œ ์นดํ”ผ ํ‘œ์‹œ
337
  st.markdown("### โœจ AI๊ฐ€ ์ƒ์„ฑํ•œ ์นดํ”ผ:")
338
  st.markdown(result['generated_content'])
339
-
340
  # ๊ฒฐ๊ณผ ๋‹ค์šด๋กœ๋“œ
341
  result_json = json.dumps({
342
  'timestamp': datetime.now().isoformat(),
343
  'request': user_request,
344
  'settings': result['settings'],
 
345
  'generated_content': result['generated_content']
346
  }, ensure_ascii=False, indent=2)
347
-
348
  st.download_button(
349
  label="๐Ÿ’พ ๊ฒฐ๊ณผ ๋‹ค์šด๋กœ๋“œ (JSON)",
350
  data=result_json,
@@ -352,35 +425,42 @@ if st.button("๐Ÿš€ ์นดํ”ผ ์ƒ์„ฑํ•˜๊ธฐ", type="primary", use_container_width=Tru
352
  mime="application/json"
353
  )
354
 
 
 
 
 
 
 
 
 
 
 
 
355
  # ์‚ฌ์šฉ๋ฒ• ๊ฐ€์ด๋“œ
356
- with st.expander("๐Ÿ’ก ์‚ฌ์šฉ๋ฒ• ๊ฐ€์ด๋“œ"):
357
  st.markdown("""
358
  ### ๐ŸŽฏ ํšจ๊ณผ์ ์ธ ์‚ฌ์šฉ๋ฒ•
359
-
360
  **1. ๊ตฌ์ฒด์ ์ธ ์š”์ฒญํ•˜๊ธฐ:**
361
  - โŒ "์นดํ”ผ ์จ์ค˜"
362
  - โœ… "30๋Œ€ ์ง์žฅ ์—ฌ์„ฑ์šฉ ํ”„๋ฆฌ๋ฏธ์—„ ์Šคํ‚จ์ผ€์–ด ์‹ ์ œํ’ˆ ๋Ÿฐ์นญ ์นดํ”ผ"
363
-
364
- **2. ์นดํ…Œ๊ณ ๋ฆฌ ํ™œ์šฉ:**
365
- - ์ •ํ™•ํ•œ ์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ์œผ๋กœ ๋” ๊ด€๋ จ์„ฑ ๋†’์€ ์ฐธ๊ณ  ์ž๋ฃŒ ํ™•๋ณด
366
-
 
 
367
  **3. ์ฐฝ์˜์„ฑ ์กฐ์ ˆ:**
368
  - **๋ณด์ˆ˜์ **: ์•ˆ์ „ํ•œ ํด๋ผ์ด์–ธํŠธ, ๊ฒ€์ฆ๋œ ์ ‘๊ทผ
369
  - **๊ท ํ˜•**: ์ผ๋ฐ˜์ ์ธ ํ”„๋กœ์ ํŠธ (์ถ”์ฒœ!)
370
  - **์ฐฝ์˜์ **: ํ˜์‹ ์  ๋ธŒ๋žœ๋“œ, ํŒŒ๊ฒฉ์  ์บ ํŽ˜์ธ
371
-
372
- **4. ์—ฌ๋Ÿฌ ๋ฒ„์ „ ์ƒ์„ฑ:**
373
- - ์ปจ์…‰ ์ˆ˜๋ฅผ 3-5๊ฐœ๋กœ ์„ค์ •ํ•ด์„œ ๋‹ค์–‘ํ•œ ์˜ต์…˜ ํ™•๋ณด
 
 
374
  """)
375
 
376
- # ์‹œ์Šคํ…œ ์ •๋ณด (์‚ฌ์ด๋“œ๋ฐ” ํ•˜๋‹จ)
377
- st.sidebar.markdown("---")
378
- st.sidebar.markdown("### ๐Ÿ“Š ์‹œ์Šคํ…œ ์ •๋ณด")
379
- st.sidebar.markdown(f"**๋ฐ์ดํ„ฐ**: {len(df):,}๊ฐœ ์นดํ”ผ")
380
- st.sidebar.markdown(f"**์นดํ…Œ๊ณ ๋ฆฌ**: {df['์นดํ…Œ๊ณ ๋ฆฌ'].nunique()}๊ฐœ")
381
- st.sidebar.markdown(f"**๋ธŒ๋žœ๋“œ**: {df['๋ธŒ๋žœ๋“œ'].nunique()}๊ฐœ")
382
- st.sidebar.markdown("**ํ˜ธ์ŠคํŒ…**: ๐Ÿค— Hugging Face")
383
-
384
  # ํ‘ธํ„ฐ
385
  st.markdown("---")
386
  st.markdown(
@@ -388,6 +468,9 @@ st.markdown(
388
  "RAG(๊ฒ€์ƒ‰ ์ฆ๊ฐ• ์ƒ์„ฑ) ์‹œ์Šคํ…œ powered by Korean SBERT + Gemini AI"
389
  )
390
 
391
- # Hugging Face ์ „์šฉ ์ •๋ณด
392
- if os.getenv("SPACE_ID"): # Hugging Face Spaces์—์„œ๋งŒ ํ‘œ์‹œ
393
- st.markdown("๐Ÿš€ **Powered by Hugging Face Spaces** | ์™„์ „ ๋ฌด๋ฃŒ AI ํ”Œ๋žซํผ")
 
 
 
 
1
+ # ์•ˆ์ •์ ์ธ AI ์นดํ”ผ๋ผ์ดํ„ฐ - ์ž„๋ฒ ๋”ฉ ๊ธฐ๋ฐ˜ RAG ์‹œ์Šคํ…œ
2
+ # Hugging Face Spaces ํ™˜๊ฒฝ ์ตœ์ ํ™” ๋ฒ„์ „
3
 
4
  import streamlit as st
5
  import pandas as pd
6
  import numpy as np
 
 
7
  import pickle
8
  import google.generativeai as genai
9
  import time
 
11
  import os
12
  from datetime import datetime
13
 
14
+ # ํ™˜๊ฒฝ ์„ค์ • (๊ถŒํ•œ ๋ฌธ์ œ ํ•ด๊ฒฐ)
15
+ os.environ['STREAMLIT_BROWSER_GATHER_USAGE_STATS'] = 'false'
16
+ os.environ['TRANSFORMERS_CACHE'] = '/tmp/transformers'
17
+ os.environ['SENTENCE_TRANSFORMERS_HOME'] = '/tmp/sentence_transformers'
18
+
19
  # ํŽ˜์ด์ง€ ์„ค์ •
20
  st.set_page_config(
21
  page_title="AI ์นดํ”ผ๋ผ์ดํ„ฐ | RAG ๊ธฐ๋ฐ˜ ๊ด‘๊ณ  ์นดํ”ผ ์ƒ์„ฑ",
 
26
 
27
  # ์ œ๋ชฉ ๋ฐ ์„ค๋ช…
28
  st.title("โœจ AI ์นดํ”ผ๋ผ์ดํ„ฐ")
29
+ st.markdown("### ๐ŸŽฏ 37,671๊ฐœ ์‹ค์ œ ๊ด‘๊ณ  ์นดํ”ผ ๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜ RAG ์‹œ์Šคํ…œ")
30
  st.markdown("---")
31
 
32
  # ์‚ฌ์ด๋“œ๋ฐ” ์„ค์ •
 
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๋กœ ์„ค์ •ํ•˜๋ฉด ์ž๋™ ์ž…๋ ฅ๋ฉ๋‹ˆ๋‹ค"
43
  )
44
 
45
  if not api_key:
46
  st.warning("โš ๏ธ Gemini API ํ‚ค๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”")
47
+ st.info("๐Ÿ’ก Settings โ†’ Repository secrets์—์„œ GEMINI_API_KEY๋ฅผ ์„ค์ •ํ•˜์„ธ์š”")
48
  st.stop()
49
 
50
+ # ์‹œ์Šคํ…œ ์ดˆ๊ธฐํ™” (์บ์‹ฑ) - ์ž„๋ฒ ๋”ฉ ํ•„์ˆ˜!
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:
65
+ genai.configure(api_key=api_key)
66
+ model = genai.GenerativeModel('gemini-2.0-flash')
67
+ total_progress.progress(10)
68
+ st.success("โœ… Gemini API ์„ค์ • ์™„๋ฃŒ")
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:
111
  with open('copy_embeddings.pkl', 'rb') as f:
112
  embeddings_data = pickle.load(f)
113
  embeddings = embeddings_data['embeddings']
114
+ total_progress.progress(90)
115
+ st.success(f"โœ… ์ž„๋ฒ ๋”ฉ ๋กœ๋”ฉ ์™„๋ฃŒ: {embeddings.shape[0]:,}๊ฐœ ร— {embeddings.shape[1]}์ฐจ์›")
116
+ except Exception as e:
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:
130
+ st.metric("์นดํ”ผ ๋ฐ์ดํ„ฐ", f"{len(df):,}๊ฐœ")
131
+ with success_col2:
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("โŒ ์‹œ์Šคํ…œ ๋กœ๋”ฉ ์‹คํŒจ: ํ•„์ˆ˜ ๊ตฌ์„ฑ์š”์†Œ ๋ˆ„๋ฝ")
144
+ return None, None, None, None
145
 
146
  # ์‹œ์Šคํ…œ ๋กœ๋”ฉ
147
  with st.spinner("๐Ÿš€ AI ์นดํ”ผ๋ผ์ดํ„ฐ ์‹œ์Šคํ…œ ์ดˆ๊ธฐํ™” ์ค‘..."):
148
  model, embedding_model, df, embeddings = load_system()
149
 
150
+ if model is None or embedding_model is None or df is None or embeddings is None:
151
+ st.error("โŒ ์‹œ์Šคํ…œ์„ ๋กœ๋”ฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•˜๊ฑฐ๋‚˜ ๊ด€๋ฆฌ์ž์—๊ฒŒ ๋ฌธ์˜ํ•˜์„ธ์š”.")
152
  st.stop()
153
 
154
+ # ์‚ฌ์ด๋“œ๋ฐ” ์„ค์ • (์‹œ์Šคํ…œ ๋กœ๋”ฉ ์„ฑ๊ณต ํ›„)
155
+ st.sidebar.success("๐ŸŽ‰ RAG ์‹œ์Šคํ…œ ์ค€๋น„ ์™„๋ฃŒ!")
156
 
157
  # ์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ
158
  categories = ['์ „์ฒด'] + sorted(df['์นดํ…Œ๊ณ ๋ฆฌ'].unique().tolist())
 
165
  # ํƒ€๊ฒŸ ๊ณ ๊ฐ ์„ค์ •
166
  target_audience = st.sidebar.selectbox(
167
  "๐ŸŽฏ ํƒ€๊ฒŸ ๊ณ ๊ฐ",
168
+ ['20๋Œ€', '30๋Œ€', '์ผ๋ฐ˜', '10๋Œ€', '40๋Œ€', '50๋Œ€+', '๋‚จ์„ฑ', '์—ฌ์„ฑ', '์ง์žฅ์ธ', 'ํ•™์ƒ', '์ฃผ๋ถ€'],
169
+ help="ํƒ€๊ฒŸ ๊ณ ๊ฐ์— ๋งž๋Š” ํ†ค์•ค๋งค๋„ˆ๋กœ ์นดํ”ผ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค"
170
  )
171
 
172
  # ๋ธŒ๋žœ๋“œ ํ†ค์•ค๋งค๋„ˆ
173
  brand_tone = st.sidebar.selectbox(
174
  "๐ŸŽจ ๋ธŒ๋žœ๋“œ ํ†ค",
175
+ ['์„ธ๋ จ๋œ', '์นœ๊ทผํ•œ', '๊ณ ๊ธ‰์Šค๋Ÿฌ์šด', 'ํ™œ๊ธฐ์ฐฌ', '์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š”', '์ Š์€', '๋”ฐ๋œปํ•œ', '์ „๋ฌธ์ ์ธ'],
176
  help="์›ํ•˜๋Š” ๋ธŒ๋žœ๋“œ ์ด๋ฏธ์ง€๋ฅผ ์„ ํƒํ•˜์„ธ์š”"
177
  )
178
 
 
203
  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="")
218
+ with col2:
219
+ if selected_template == "์•ฑ/์„œ๋น„์Šค ๋ฆฌ๋‰ด์–ผ":
220
+ service_name = st.text_input("์„œ๋น„์Šค๋ช…:", placeholder="์˜ˆ: ๋ฐฐ๋‹ฌ์•ฑ, ๊ธˆ์œต์•ฑ")
221
+ user_request = templates[selected_template].format(์„œ๋น„์Šค๋ช…=service_name)
222
+ elif selected_template == "์‹œ์ฆŒ ํ•œ์ •":
223
+ season = st.selectbox("์‹œ์ฆŒ:", ["๋ด„", "์—ฌ๋ฆ„", "๊ฐ€์„", "๊ฒจ์šธ", "ํฌ๋ฆฌ์Šค๋งˆ์Šค", "์‹ ๋…„"])
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
  # ๊ณ ๊ธ‰ ์˜ต์…˜
 
232
  col1, col2 = st.columns(2)
233
  with col1:
234
  num_concepts = st.slider("์ƒ์„ฑํ•  ์ปจ์…‰ ์ˆ˜:", 1, 5, 3)
 
 
235
  min_similarity = st.slider("์ตœ์†Œ ์œ ์‚ฌ๋„:", 0.0, 1.0, 0.3, 0.1)
236
+ with col2:
237
  show_references = st.checkbox("์ฐธ๊ณ  ์นดํ”ผ ๋ณด๊ธฐ", value=True)
238
+ num_references = st.slider("์ฐธ๊ณ  ์นดํ”ผ ์ˆ˜:", 3, 10, 5)
239
 
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]
281
+ row = df.iloc[original_idx]
282
+ if similarities[idx] >= min_similarity:
283
+ reference_copies.append({
284
+ 'copy': row['์นดํ”ผ ๋‚ด์šฉ'],
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
 
316
  **์š”์ฒญ์‚ฌํ•ญ:** {user_request}
 
318
  **๋ธŒ๋žœ๋“œ ํ†ค:** {tone}
319
  **์ฐฝ์˜์„ฑ ์ˆ˜์ค€:** {creative}
320
 
321
+ **์ฐธ๊ณ  ์นดํ”ผ๋“ค (์˜๋ฏธ์  ์œ ์‚ฌ๋„ ๊ธฐ๋ฐ˜ ์„ ๋ณ„):**
322
  {references_text}
323
 
324
  **์ž‘์„ฑ ๊ฐ€์ด๋“œ๋ผ์ธ:**
 
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,
355
+ 'search_info': {
356
+ 'query': search_query,
357
+ 'total_candidates': len(filtered_df),
358
+ 'selected_references': len(reference_copies)
359
+ },
360
  'settings': {
361
  'category': category,
362
  'target': target,
 
364
  'creative': creative
365
  }
366
  }
367
+
368
  except Exception as e:
369
  st.error(f"โŒ ์นดํ”ผ ์ƒ์„ฑ ์‹คํŒจ: {e}")
370
+ progress_bar.empty()
371
+ status_text.empty()
372
  return None
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:
380
+ # RAG ์นดํ”ผ ์ƒ์„ฑ
381
+ result = generate_copy_with_rag(
382
  user_request=user_request,
383
  category=selected_category,
384
  target=target_audience,
 
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(),
415
  'request': user_request,
416
  'settings': result['settings'],
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,
 
425
  mime="application/json"
426
  )
427
 
428
+ # ์‹œ์Šคํ…œ ์ •๋ณด (์‚ฌ์ด๋“œ๋ฐ” ํ•˜๋‹จ)
429
+ st.sidebar.markdown("---")
430
+ st.sidebar.markdown("### ๐Ÿ“Š RAG ์‹œ์Šคํ…œ ์ •๋ณด")
431
+ if df is not None and embeddings is not None:
432
+ st.sidebar.markdown(f"**์นดํ”ผ ๋ฐ์ดํ„ฐ**: {len(df):,}๊ฐœ")
433
+ st.sidebar.markdown(f"**์นดํ…Œ๊ณ ๋ฆฌ**: {df['์นดํ…Œ๊ณ ๋ฆฌ'].nunique()}๊ฐœ")
434
+ st.sidebar.markdown(f"**๋ธŒ๋žœ๋“œ**: {df['๋ธŒ๋žœ๋“œ'].nunique()}๊ฐœ")
435
+ st.sidebar.markdown(f"**์ž„๋ฒ ๋”ฉ**: {embeddings.shape[1]}์ฐจ์›")
436
+ st.sidebar.markdown("**๊ฒ€์ƒ‰ ์—”์ง„**: Korean SBERT")
437
+ st.sidebar.markdown("**ํ˜ธ์ŠคํŒ…**: ๐Ÿค— Hugging Face")
438
+
439
  # ์‚ฌ์šฉ๋ฒ• ๊ฐ€์ด๋“œ
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
+ - ํŠธ๋ Œ๋“œ์™€ ํŒจํ„ด ํŒŒ์•… ๊ฐ€๋Šฅ
461
+ - ๊ฒฝ์Ÿ์‚ฌ ๋ถ„์„ ์ž๋ฃŒ๋กœ ํ™œ์šฉ
462
  """)
463
 
 
 
 
 
 
 
 
 
464
  # ํ‘ธํ„ฐ
465
  st.markdown("---")
466
  st.markdown(
 
468
  "RAG(๊ฒ€์ƒ‰ ์ฆ๊ฐ• ์ƒ์„ฑ) ์‹œ์Šคํ…œ powered by Korean SBERT + Gemini AI"
469
  )
470
 
471
+ # ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง (๊ฐœ๋ฐœ์ž์šฉ)
472
+ 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__}")