parkkyujin commited on
Commit
a28c751
ยท
verified ยท
1 Parent(s): d40349c

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +476 -0
app.py ADDED
@@ -0,0 +1,476 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
10
+ import json
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 ๊ธฐ๋ฐ˜ ๊ด‘๊ณ  ์นดํ”ผ ์ƒ์„ฑ",
22
+ page_icon="โœจ",
23
+ layout="wide",
24
+ initial_sidebar_state="expanded"
25
+ )
26
+
27
+ # ์ œ๋ชฉ ๋ฐ ์„ค๋ช…
28
+ st.title("โœจ AI ์นดํ”ผ๋ผ์ดํ„ฐ")
29
+ st.markdown("### ๐ŸŽฏ 37,671๊ฐœ ์‹ค์ œ ๊ด‘๊ณ  ์นดํ”ผ ๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜ RAG ์‹œ์Šคํ…œ")
30
+ st.markdown("---")
31
+
32
+ # ์‚ฌ์ด๋“œ๋ฐ” ์„ค์ •
33
+ st.sidebar.header("๐ŸŽ›๏ธ ์นดํ”ผ ์ƒ์„ฑ ์„ค์ •")
34
+
35
+ # API ํ‚ค ์ž…๋ ฅ (ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์šฐ์„  ์‚ฌ์šฉ)
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())
159
+ selected_category = st.sidebar.selectbox(
160
+ "๐Ÿ“‚ ์นดํ…Œ๊ณ ๋ฆฌ",
161
+ categories,
162
+ help="ํŠน์ • ์นดํ…Œ๊ณ ๋ฆฌ๋กœ ๊ฒ€์ƒ‰์„ ์ œํ•œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค"
163
+ )
164
+
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
+
179
+ # ์ฐฝ์˜์„ฑ ์ˆ˜์ค€
180
+ creative_level = st.sidebar.select_slider(
181
+ "๐Ÿง  ์ฐฝ์˜์„ฑ ์ˆ˜์ค€",
182
+ options=['๋ณด์ˆ˜์ ', '๊ท ํ˜•', '์ฐฝ์˜์ '],
183
+ value='๊ท ํ˜•',
184
+ help="๋ณด์ˆ˜์ : ์•ˆ์ „ํ•œ ํ‘œํ˜„, ์ฐฝ์˜์ : ๋…์ฐฝ์  ํ‘œํ˜„"
185
+ )
186
+
187
+ # ๋ฉ”์ธ ์ž…๋ ฅ ์˜์—ญ
188
+ st.markdown("## ๐Ÿ’ญ ์–ด๋–ค ์นดํ”ผ๋ฅผ ๋งŒ๋“ค๊ณ  ์‹ถ์œผ์‹ ๊ฐ€์š”?")
189
+
190
+ # ์ž…๋ ฅ ๋ฐฉ์‹ ์„ ํƒ
191
+ input_method = st.radio(
192
+ "์ž…๋ ฅ ๋ฐฉ์‹ ์„ ํƒ:",
193
+ ["์ง์ ‘ ์ž…๋ ฅ", "ํ…œํ”Œ๋ฆฟ ์„ ํƒ"],
194
+ horizontal=True
195
+ )
196
+
197
+ if input_method == "์ง์ ‘ ์ž…๋ ฅ":
198
+ user_request = st.text_area(
199
+ "์นดํ”ผ ์š”์ฒญ์„ ์ž์„ธํžˆ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”:",
200
+ placeholder="์˜ˆ: 30๋Œ€ ์ง์žฅ ์—ฌ์„ฑ์šฉ ํ”„๋ฆฌ๋ฏธ์—„ ์Šคํ‚จ์ผ€์–ด ์‹ ์ œํ’ˆ ๋Ÿฐ์นญ ์นดํ”ผ",
201
+ height=100
202
+ )
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
+ # ๊ณ ๊ธ‰ ์˜ต์…˜
231
+ with st.expander("๐Ÿ”ง ๊ณ ๊ธ‰ ์˜ต์…˜"):
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}
317
+ **ํƒ€๊ฒŸ ๊ณ ๊ฐ:** {target}
318
+ **๋ธŒ๋žœ๋“œ ํ†ค:** {tone}
319
+ **์ฐฝ์˜์„ฑ ์ˆ˜์ค€:** {creative}
320
+
321
+ **์ฐธ๊ณ  ์นดํ”ผ๋“ค (์˜๋ฏธ์  ์œ ์‚ฌ๋„ ๊ธฐ๋ฐ˜ ์„ ๋ณ„):**
322
+ {references_text}
323
+
324
+ **์ž‘์„ฑ ๊ฐ€์ด๋“œ๋ผ์ธ:**
325
+ 1. ์œ„ ์ฐธ๊ณ  ์นดํ”ผ๋“ค์˜ ์Šคํƒ€์ผ๊ณผ ํ†ค์„ ๋ถ„์„ํ•˜์—ฌ ์œ ์‚ฌํ•œ ๋А๋‚Œ์œผ๋กœ ์ž‘์„ฑ
326
+ 2. {creativity_guidance[creative]} ์ƒˆ๋กœ์šด ์นดํ”ผ {num_concepts}๊ฐœ๋ฅผ ์ž‘์„ฑ
327
+ 3. ๊ฐ ์นดํ”ผ๋Š” ํ•œ๊ตญ์–ด๋กœ ์ž์—ฐ์Šค๋Ÿฝ๊ณ  ๋งค๋ ฅ์ ์ด์–ด์•ผ ํ•จ
328
+ 4. {target}์—๊ฒŒ ์–ดํ•„ํ•  ์ˆ˜ ์žˆ๋Š” ํ‘œํ˜„ ์‚ฌ์šฉ
329
+ 5. {tone} ํ†ค์•ค๋งค๋„ˆ ์œ ์ง€
330
+
331
+ **์ถœ๋ ฅ ํ˜•์‹:**
332
+ 1. [์นดํ”ผ1]
333
+ - ์„ค๋ช…: ์™œ ์ด ์นดํ”ผ๊ฐ€ ํšจ๊ณผ์ ์ธ์ง€ ๊ฐ„๋‹จํžˆ ์„ค๋ช…
334
+
335
+ 2. [์นดํ”ผ2]
336
+ - ์„ค๋ช…: ์™œ ์ด ์นดํ”ผ๊ฐ€ ํšจ๊ณผ์ ์ธ์ง€ ๊ฐ„๋‹จํžˆ ์„ค๋ช…
337
+
338
+ 3. [์นดํ”ผ3]
339
+ - ์„ค๋ช…: ์™œ ์ด ์นดํ”ผ๊ฐ€ ํšจ๊ณผ์ ์ธ์ง€ ๊ฐ„๋‹จํžˆ ์„ค๋ช…
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,
363
+ 'tone': tone,
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,
385
+ tone=brand_tone,
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,
424
+ file_name=f"copy_result_{datetime.now().strftime('%Y%m%d_%H%M%S')}.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(
467
+ "๐Ÿ’ก **AI ์นดํ”ผ๋ผ์ดํ„ฐ** | 37,671๊ฐœ ์‹ค์ œ ๊ด‘๊ณ  ์นดํ”ผ ๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜ | "
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__}")