ginipick commited on
Commit
79f2daf
ยท
verified ยท
1 Parent(s): d835c63

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +684 -38
app.py CHANGED
@@ -157,7 +157,7 @@ def extract_keywords(text: str, top_k: int = 5) -> str:
157
  key_tokens = tokens[:top_k]
158
  return " ".join(key_tokens)
159
 
160
- # Mock ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ƒ์„ฑ ํ•จ์ˆ˜ ์ถ”๊ฐ€ (API ํ‚ค๊ฐ€ ์—†๊ฑฐ๋‚˜ ์—ฐ๊ฒฐ ์‹คํŒจ ์‹œ ๋Œ€์ฒด ์‚ฌ์šฉ)
161
  def generate_mock_search_results(query):
162
  """API ์—ฐ๊ฒฐ์ด ์•ˆ๋  ๋•Œ ์‚ฌ์šฉํ•  ๊ฐ€์ƒ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ƒ์„ฑ"""
163
  current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@@ -173,6 +173,628 @@ def generate_mock_search_results(query):
173
  "link": "https://example.org/research",
174
  "snippet": "์ด๊ฒƒ์€ API ์—ฐ๊ฒฐ ๋ฌธ์ œ๋กœ ์ธํ•œ ๊ฐ€์ƒ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค. ์‹ค์ œ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์—ฌ๋“œ๋ฆฌ์ง€ ๋ชปํ•ด ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ๋Œ€์‹  AI์˜ ๊ธฐ์กด ์ง€์‹์„ ํ™œ์šฉํ•˜์—ฌ ๋‹ต๋ณ€๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.",
175
  "displayed_link": "example.org/research"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  }
177
  ]
178
 
@@ -198,23 +820,55 @@ def generate_mock_search_results(query):
198
 
199
  return notice + "\n".join(summary_lines)
200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  # ์›น ๊ฒ€์ƒ‰ ํ•จ์ˆ˜
202
  def do_web_search(query: str) -> str:
203
  """
204
- ์ƒ์œ„ 20๊ฐœ 'organic' ๊ฒฐ๊ณผ item ์ „์ฒด(์ œ๋ชฉ, link, snippet ๋“ฑ)๋ฅผ
205
- JSON ๋ฌธ์ž์—ด ํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜
206
  """
207
  try:
208
- url = "https://api.serphouse.com/serp/live"
 
 
 
209
 
210
- # ๊ธฐ๋ณธ GET ๋ฐฉ์‹์œผ๋กœ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ„์†Œํ™”ํ•˜๊ณ  ๊ฒฐ๊ณผ ์ˆ˜๋ฅผ 20๊ฐœ๋กœ ์ œํ•œ
 
211
  params = {
212
  "q": query,
213
  "domain": "google.com",
214
- "serp_type": "web", # ๊ธฐ๋ณธ ์›น ๊ฒ€์ƒ‰
215
  "device": "desktop",
216
- "lang": "ko", # ํ•œ๊ตญ์–ด ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ์œ„ํ•ด ๋ณ€๊ฒฝ
217
- "num": "10" # ๊ฒฐ๊ณผ ์ˆ˜๋ฅผ 10๊ฐœ๋กœ ์ค„์ž„ (๋น ๋ฅธ ์‘๋‹ต์„ ์œ„ํ•ด)
218
  }
219
 
220
  headers = {
@@ -222,15 +876,9 @@ def do_web_search(query: str) -> str:
222
  }
223
 
224
  logging.info(f"SerpHouse API ํ˜ธ์ถœ ์ค‘... ๊ฒ€์ƒ‰์–ด: {query}")
225
- logging.info(f"์š”์ฒญ URL: {url} - ํŒŒ๋ผ๋ฏธํ„ฐ: {params}")
226
-
227
- # Mock ์‘๋‹ต (์‹ค์ œ API ์—ฐ๊ฒฐ์ด ์–ด๋ ค์šธ ๊ฒฝ์šฐ)
228
- if not SERPHOUSE_API_KEY or "mock" in SERPHOUSE_API_KEY.lower():
229
- logging.warning("API ํ‚ค๊ฐ€ ์—†๊ฑฐ๋‚˜ Mock ๋ชจ๋“œ์ž…๋‹ˆ๋‹ค. ๋ชจ์˜ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.")
230
- return generate_mock_search_results(query)
231
 
232
- # ํƒ€์ž„์•„์›ƒ ์ค„์ž„ (30์ดˆ)
233
- response = requests.get(url, headers=headers, params=params, timeout=30)
234
  response.raise_for_status()
235
 
236
  logging.info(f"SerpHouse API ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ: {response.status_code}")
@@ -243,28 +891,23 @@ def do_web_search(query: str) -> str:
243
  # ๊ฐ€๋Šฅํ•œ ์‘๋‹ต ๊ตฌ์กฐ 1
244
  if isinstance(results, dict) and "organic" in results:
245
  organic = results["organic"]
246
-
247
- # ๊ฐ€๋Šฅํ•œ ์‘๋‹ต ๊ตฌ์กฐ 2 (์ค‘์ฒฉ๋œ results)
248
  elif isinstance(results, dict) and "results" in results:
249
  if isinstance(results["results"], dict) and "organic" in results["results"]:
250
  organic = results["results"]["organic"]
251
-
252
- # ๊ฐ€๋Šฅํ•œ ์‘๋‹ต ๊ตฌ์กฐ 3 (์ตœ์ƒ์œ„ organic)
253
  elif "organic" in data:
254
  organic = data["organic"]
255
 
256
  if not organic:
257
- logging.warning("์‘๋‹ต์—์„œ organic ๊ฒฐ๊ณผ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
258
- logging.debug(f"์‘๋‹ต ๊ตฌ์กฐ: {list(data.keys()) if isinstance(data, dict) else 'not a dict'}")
259
- if isinstance(results, dict):
260
- logging.debug(f"results ๊ตฌ์กฐ: {list(results.keys())}")
261
- return "๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  ๊ธฐ์กด ์ง€์‹์„ ํ™œ์šฉํ•˜์—ฌ ๋‹ต๋ณ€ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค."
262
 
263
  # ๊ฒฐ๊ณผ ์ˆ˜ ์ œํ•œ ๋ฐ ์ปจํ…์ŠคํŠธ ๊ธธ์ด ์ตœ์ ํ™”
264
- max_results = min(10, len(organic))
265
  limited_organic = organic[:max_results]
266
 
267
- # ๊ฒฐ๊ณผ ํ˜•์‹ ๊ฐœ์„  - ๋งˆํฌ๋‹ค์šด ํ˜•์‹์œผ๋กœ ์ถœ๋ ฅํ•˜์—ฌ ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ
268
  summary_lines = []
269
  for idx, item in enumerate(limited_organic, start=1):
270
  title = item.get("title", "No title")
@@ -272,7 +915,6 @@ def do_web_search(query: str) -> str:
272
  snippet = item.get("snippet", "No description")
273
  displayed_link = item.get("displayed_link", link)
274
 
275
- # ๋งˆํฌ๋‹ค์šด ํ˜•์‹ (๋งํฌ ํด๋ฆญ ๊ฐ€๋Šฅ)
276
  summary_lines.append(
277
  f"### Result {idx}: {title}\n\n"
278
  f"{snippet}\n\n"
@@ -295,11 +937,11 @@ def do_web_search(query: str) -> str:
295
  return search_results
296
 
297
  except requests.exceptions.Timeout:
298
- logging.error("Web search timed out")
299
- return "Web search timed out. ๊ฒ€์ƒ‰ ์‹œ๊ฐ„์ด ์ดˆ๊ณผ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ์กด ์ง€์‹์„ ํ™œ์šฉํ•˜์—ฌ ๋‹ต๋ณ€ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค."
300
  except Exception as e:
301
- logging.error(f"Web search failed: {e}")
302
- return f"Web search failed: {str(e)}. ๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ์กด ์ง€์‹์„ ํ™œ์šฉํ•˜์—ฌ ๋‹ต๋ณ€ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค."
303
 
304
  def chatbot_interface():
305
  st.title("Ginigen Blog")
@@ -452,18 +1094,22 @@ def chatbot_interface():
452
  with st.spinner("์›น์—์„œ ๊ด€๋ จ ์ •๋ณด๋ฅผ ๊ฒ€์ƒ‰ ์ค‘..."):
453
  try:
454
  search_query = extract_keywords(prompt, top_k=5)
 
 
 
455
  search_results = do_web_search(search_query)
456
 
457
- if "search failed" not in search_results.lower() and "timed out" not in search_results.lower():
458
- # ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ์— ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ถ”๊ฐ€
459
- system_prompt += f"\n\n๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ:\n{search_results}\n"
460
- st.success(f"๊ฒ€์ƒ‰ ์™„๋ฃŒ: '{search_query}'์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ˆ˜์ง‘ํ–ˆ์Šต๋‹ˆ๋‹ค.")
461
  else:
462
- st.warning("์›น ๊ฒ€์ƒ‰์„ ์ง„ํ–‰ํ–ˆ์œผ๋‚˜ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š”๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ์กด ์ง€์‹์„ ํ™œ์šฉํ•˜์—ฌ ๋‹ต๋ณ€ํ•ฉ๋‹ˆ๋‹ค.")
463
- logging.warning(f"์›น ๊ฒ€์ƒ‰ ์‹คํŒจ ๋˜๋Š” ํƒ€์ž„์•„์›ƒ: {search_results}")
 
 
464
  except Exception as e:
465
  st.error(f"์›น ๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}")
466
  logging.error(f"์›น ๊ฒ€์ƒ‰ ์˜ค๋ฅ˜: {str(e)}")
 
467
 
468
  # API ํ˜ธ์ถœ
469
  with client.messages.stream(
 
157
  key_tokens = tokens[:top_k]
158
  return " ".join(key_tokens)
159
 
160
+ # Mock ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ƒ์„ฑ ํ•จ์ˆ˜
161
  def generate_mock_search_results(query):
162
  """API ์—ฐ๊ฒฐ์ด ์•ˆ๋  ๋•Œ ์‚ฌ์šฉํ•  ๊ฐ€์ƒ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ƒ์„ฑ"""
163
  current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
 
173
  "link": "https://example.org/research",
174
  "snippet": "์ด๊ฒƒ์€ API ์—ฐ๊ฒฐ ๋ฌธ์ œ๋กœ ์ธํ•œ ๊ฐ€์ƒ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค. ์‹ค์ œ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์—ฌ๋“œ๋ฆฌ์ง€ ๋ชปํ•ด ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ๋Œ€์‹  AI์˜ ๊ธฐ์กด ์ง€์‹์„ ํ™œ์šฉํ•˜์—ฌ ๋‹ต๋ณ€๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.",
175
  "displayed_link": "example.org/research"
176
+ },
177
+ {
178
+ "title": f"{query}์˜ ์—ญ์‚ฌ์  ๋ฐฐ๊ฒฝ",
179
+ "link": "https://example.net/history",
180
+ "snippet": "์ด ๊ฐ€์ƒ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋Š” API ์—ฐ๊ฒฐ ๋ฌธ์ œ๋กœ ์ธํ•ด ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ฐธ๊ณ ์šฉ์œผ๋กœ๋งŒ ์‚ฌ์šฉํ•ด์ฃผ์„ธ์š”.",
181
+ "displayed_link": "example.net/history"
182
+ }
183
+ ]
184
+
185
+ summary_lines = []
186
+ for idx, item in enumerate(mock_results, start=1):
187
+ title = item.get("title", "No title")
188
+ link = item.get("link", "#")
189
+ snippet = item.get("snippet", "No description")
190
+ displayed_link = item.get("displayed_link", link)
191
+
192
+ summary_lines.append(
193
+ f"### Result {idx}: {title}\n\n"
194
+ f"{snippet}\n\n"
195
+ f"**์ถœ์ฒ˜**: [{displayed_link}]({link})\n\n"
196
+ f"---\n"
197
+ )
198
+
199
+ notice = """
200
+ # ๊ฐ€์ƒ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ (API ์—ฐ๊ฒฐ ๋ฌธ์ œ๋กœ ์ธํ•ด ์ƒ์„ฑ๋จ)
201
+ ์•„๋ž˜๋Š” API ์—ฐ๊ฒฐ ๋ฌธ์ œ๋กœ ์ธํ•ด ์ƒ์„ฑ๋œ ๊ฐ€์ƒ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค. ์‹ค์ œ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์•„๋‹˜์„ ์ฐธ๊ณ ํ•˜์„ธ์š”.
202
+ ๋Œ€์‹  AI์˜ ๊ธฐ์กด ์ง€์‹์„ ํ™œ์šฉํ•˜์—ฌ ์ตœ๋Œ€ํ•œ ์ •ํ™•ํ•œ ๋‹ต๋ณ€์„ ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.
203
+ """
204
+
205
+ return notice + "\n".join(summary_lines)
206
+
207
+ # Google ๊ฒ€์ƒ‰ ํ•จ์ˆ˜ (SerpAPI ๋Œ€์‹  ์ง์ ‘ ๊ฒ€์ƒ‰)
208
+ def do_google_search(query, num_results=5):
209
+ try:
210
+ # ๊ธฐ๋ณธ ํ—ค๋” ์„ค์ • (๋ธŒ๋ผ์šฐ์ €์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ)
211
+ headers = {
212
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
213
+ }
214
+
215
+ # ๊ฒ€์ƒ‰ URL ๋งŒ๋“ค๊ธฐ
216
+ search_url = f"https://www.google.com/search?q={query}&num={num_results}"
217
+ logging.info(f"๊ตฌ๊ธ€ ๊ฒ€์ƒ‰ URL: {search_url}")
218
+
219
+ # ์š”์ฒญ ๋ณด๋‚ด๊ธฐ (์งง์€ ํƒ€์ž„์•„์›ƒ ์„ค์ •)
220
+ response = requests.get(search_url, headers=headers, timeout=10)
221
+
222
+ # ์‘๋‹ต์ด ์„ฑ๊ณต์ ์ธ์ง€ ํ™•์ธ
223
+ if response.status_code != 200:
224
+ logging.error(f"Google ๊ฒ€์ƒ‰ ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ: {response.status_code}")
225
+ return generate_mock_search_results(query)
226
+
227
+ # ์—ฌ๊ธฐ์„œ๋Š” HTML ๋ถ„์„์ด ๋ณต์žกํ•˜๋ฏ€๋กœ, ์‹ค์ œ๋กœ๋Š” BeautifulSoup ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Œ
228
+ # ๊ฐ„๋‹จํ•œ ๋ฐ๋ชจ ๋ชฉ์ ์œผ๋กœ ๋Œ€์‹  Mock ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜
229
+ logging.info("Google ๊ฒ€์ƒ‰ ์„ฑ๊ณตํ–ˆ์œผ๋‚˜ ํŒŒ์‹ฑ ์ œํ•œ์œผ๋กœ Mock ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜")
230
+ return generate_mock_search_results(query)
231
+
232
+ except Exception as e:
233
+ logging.error(f"Google ๊ฒ€์ƒ‰ ์‹คํŒจ: {e}")
234
+ return generate_mock_search_results(query)
235
+
236
+ # ์›น ๊ฒ€์ƒ‰ ํ•จ์ˆ˜
237
+ def do_web_search(query: str) -> str:
238
+ """
239
+ ์›น ๊ฒ€์ƒ‰์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํ•จ์ˆ˜ - SerpHouse API ๋˜๋Š” ์ง์ ‘ ๊ตฌ๊ธ€ ๊ฒ€์ƒ‰
240
+ """
241
+ try:
242
+ # API ํ‚ค๊ฐ€ ์—†๊ฑฐ๋‚˜ 'mock'์ธ ๊ฒฝ์šฐ
243
+ if not SERPHOUSE_API_KEY or "mock" in SERPHOUSE_API_KEY.lower():
244
+ logging.warning("API ํ‚ค๊ฐ€ ์—†๊ฑฐ๋‚˜ Mock ๋ชจ๋“œ์ž…๋‹ˆ๋‹ค. ๊ฐ€์ƒ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.")
245
+ return generate_mock_search_results(query)
246
+
247
+ # SerpHouse API ์‚ฌ์šฉ
248
+ url = "https://api.serphouse.com/serp/live"
249
+ params = {
250
+ "q": query,
251
+ "domain": "google.com",
252
+ "serp_type": "web",
253
+ "device": "desktop",
254
+ "lang": "ko", # ํ•œ๊ตญ์–ด ๊ฒฐ๊ณผ
255
+ "num": "5" # ๊ฒฐ๊ณผ ์ˆ˜ ์ค„์ž„
256
+ }
257
+
258
+ headers = {
259
+ "Authorization": f"Bearer {SERPHOUSE_API_KEY}"
260
+ }
261
+
262
+ logging.info(f"SerpHouse API ํ˜ธ์ถœ ์ค‘... ๊ฒ€์ƒ‰์–ด: {query}")
263
+
264
+ # ์งง์€ ํƒ€์ž„์•„์›ƒ์œผ๋กœ ์š”์ฒญ ์‹œ๋„
265
+ response = requests.get(url, headers=headers, params=params, timeout=15)
266
+ response.raise_for_status()
267
+
268
+ logging.info(f"SerpHouse API ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ: {response.status_code}")
269
+ data = response.json()
270
+
271
+ # ๋‹ค์–‘ํ•œ ์‘๋‹ต ๊ตฌ์กฐ ์ฒ˜๋ฆฌ
272
+ results = data.get("results", {})
273
+ organic = None
274
+
275
+ # ๊ฐ€๋Šฅํ•œ ์‘๋‹ต ๊ตฌ์กฐ 1
276
+ if isinstance(results, dict) and "organic" in results:
277
+ organic = results["organic"]
278
+ # ๊ฐ€๋Šฅํ•œ ์‘๋‹ต ๊ตฌ์กฐ 2
279
+ elif isinstance(results, dict) and "results" in results:
280
+ if isinstance(results["results"], dict) and "organic" in results["results"]:
281
+ organic = results["results"]["organic"]
282
+ # ๊ฐ€๋Šฅํ•œ ์‘๋‹ต ๊ตฌ์กฐ 3
283
+ elif "organic" in data:
284
+ organic = data["organic"]
285
+
286
+ if not organic:
287
+ logging.warning("์‘๋‹ต์—์„œ organic ๊ฒฐ๊ณผ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ตฌ๊ธ€ ์ง์ ‘ ๊ฒ€์ƒ‰์œผ๋กœ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค.")
288
+ return do_google_search(query)
289
+
290
+ # ๊ฒฐ๊ณผ ์ˆ˜ ์ œํ•œ ๋ฐ ์ปจํ…์ŠคํŠธ ๊ธธ์ด ์ตœ์ ํ™”
291
+ max_results = min(5, len(organic))
292
+ limited_organic = organic[:max_results]
293
+
294
+ # ๊ฒฐ๊ณผ ํ˜•์‹ ๊ฐœ์„ 
295
+ summary_lines = []
296
+ for idx, item in enumerate(limited_organic, start=1):
297
+ title = item.get("title", "No title")
298
+ link = item.get("link", "#")
299
+ snippet = item.get("snippet", "No description")
300
+ displayed_link = item.get("displayed_link", link)
301
+
302
+ summary_lines.append(
303
+ f"### Result {idx}: {title}\n\n"
304
+ f"{snippet}\n\n"
305
+ f"**์ถœ์ฒ˜**: [{displayed_link}]({link})\n\n"
306
+ f"---\n"
307
+ )
308
+
309
+ # ๋ชจ๋ธ์—๊ฒŒ ๋ช…ํ™•ํ•œ ์ง€์นจ ์ถ”๊ฐ€
310
+ instructions = """
311
+ # ์›น ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ
312
+ ์•„๋ž˜๋Š” ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค. ์งˆ๋ฌธ์— ๋‹ต๋ณ€ํ•  ๋•Œ ์ด ์ •๋ณด๋ฅผ ํ™œ์šฉํ•˜์„ธ์š”:
313
+ 1. ๊ฐ ๊ฒฐ๊ณผ์˜ ์ œ๋ชฉ, ๋‚ด์šฉ, ์ถœ์ฒ˜ ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”
314
+ 2. ๋‹ต๋ณ€์— ๊ด€๋ จ ์ •๋ณด์˜ ์ถœ์ฒ˜๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ธ์šฉํ•˜์„ธ์š” (์˜ˆ: "X ์ถœ์ฒ˜์— ๋”ฐ๋ฅด๋ฉด...")
315
+ 3. ์‘๋‹ต์— ์‹ค์ œ ์ถœ์ฒ˜ ๋งํฌ๋ฅผ ํฌํ•จํ•˜์„ธ์š”
316
+ 4. ์—ฌ๋Ÿฌ ์ถœ์ฒ˜์˜ ์ •๋ณด๋ฅผ ์ข…ํ•ฉํ•˜์—ฌ ๋‹ต๋ณ€ํ•˜์„ธ์š”
317
+ """
318
+
319
+ search_results = instructions + "\n".join(summary_lines)
320
+ logging.info(f"๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ {len(limited_organic)}๊ฐœ ์ฒ˜๋ฆฌ ์™„๋ฃŒ")
321
+ return search_results
322
+
323
+ except requests.exceptions.Timeout:
324
+ logging.error("Web search timed out, ์ง์ ‘ ๊ตฌ๊ธ€ ๊ฒ€์ƒ‰์œผ๋กœ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค.")
325
+ return do_google_search(query)
326
+ except Exception as e:
327
+ logging.error(f"Web search failed: {e}, ์ง์ ‘ ๊ตฌ๊ธ€ ๊ฒ€์ƒ‰์œผ๋กœ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค.")
328
+ return do_google_search(query)
329
+
330
+ def chatbot_interface():
331
+ st.title("Ginigen Blog")
332
+
333
+ # ๋ชจ๋ธ ๊ณ ์ • ์„ค์ •
334
+ if "ai_model" not in st.session_state:
335
+ st.session_state["ai_model"] = "claude-3-7-sonnet-20250219"
336
+
337
+ # ์„ธ์…˜ ์ƒํƒœ ์ดˆ๊ธฐํ™”
338
+ if "messages" not in st.session_state:
339
+ st.session_state.messages = []
340
+
341
+ # ์ž๋™ ์ €์žฅ ๊ธฐ๋Šฅ
342
+ if "auto_save" not in st.session_state:
343
+ st.session_state.auto_save = True
344
+
345
+ # ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ† ๊ธ€
346
+ if "generate_image" not in st.session_state:
347
+ st.session_state.generate_image = False
348
+
349
+ # ์›น ๊ฒ€์ƒ‰ ํ† ๊ธ€
350
+ if "use_web_search" not in st.session_state:
351
+ st.session_state.use_web_search = False
352
+
353
+ # ์ด๋ฏธ์ง€ API ์ƒํƒœ
354
+ if "image_api_status" not in st.session_state:
355
+ st.session_state.image_api_status = test_image_api_connection()
356
+
357
+ # ๋Œ€ํ™” ๊ธฐ๋ก ๊ด€๋ฆฌ (์‚ฌ์ด๋“œ๋ฐ”)
358
+ st.sidebar.title("๋Œ€ํ™” ๊ธฐ๋ก ๊ด€๋ฆฌ")
359
+
360
+ # ์ž๋™ ์ €์žฅ ํ† ๊ธ€
361
+ st.session_state.auto_save = st.sidebar.toggle("์ž๋™ ์ €์žฅ", value=st.session_state.auto_save)
362
+
363
+ # ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ† ๊ธ€
364
+ st.session_state.generate_image = st.sidebar.toggle("๋ธ”๋กœ๊ทธ ๊ธ€ ์ž‘์„ฑ ํ›„ ์ด๋ฏธ์ง€ ์ž๋™ ์ƒ์„ฑ", value=st.session_state.generate_image)
365
+
366
+ # ์›น ๊ฒ€์ƒ‰ ํ† ๊ธ€
367
+ st.session_state.use_web_search = st.sidebar.toggle("์ฃผ์ œ ์›น ๊ฒ€์ƒ‰ ๋ฐ ๋ถ„์„", value=st.session_state.use_web_search)
368
+
369
+ # ์ด๋ฏธ์ง€ API ์ƒํƒœ ํ‘œ์‹œ
370
+ st.sidebar.text(st.session_state.image_api_status)
371
+
372
+ # ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์„ค์ • (ํ† ๊ธ€์ด ์ผœ์ ธ ์žˆ์„ ๋•Œ๋งŒ ํ‘œ์‹œ)
373
+ if st.session_state.generate_image:
374
+ st.sidebar.subheader("์ด๋ฏธ์ง€ ์ƒ์„ฑ ์„ค์ •")
375
+ width = st.sidebar.slider("๋„ˆ๋น„", 256, 1024, 768, 64)
376
+ height = st.sidebar.slider("๋†’์ด", 256, 1024, 768, 64)
377
+ guidance = st.sidebar.slider("๊ฐ€์ด๋˜์Šค ์Šค์ผ€์ผ", 1.0, 20.0, 3.5, 0.1)
378
+ inference_steps = st.sidebar.slider("์ธํผ๋Ÿฐ์Šค ์Šคํ…", 1, 50, 30, 1)
379
+ seed = st.sidebar.number_input("์‹œ๋“œ", value=3, min_value=0, step=1)
380
+ else:
381
+ # ๊ธฐ๋ณธ๊ฐ’ ์„ค์ •
382
+ width, height, guidance, inference_steps, seed = 768, 768, 3.5, 30, 3
383
+
384
+ # ๋ธ”๋กœ๊ทธ ๋‚ด์šฉ ๋‹ค์šด๋กœ๋“œ ์„น์…˜
385
+ st.sidebar.title("๋ธ”๋กœ๊ทธ ๋‹ค์šด๋กœ๋“œ")
386
+
387
+ # ์ตœ์‹  ๋ธ”๋กœ๊ทธ ๋‚ด์šฉ ๊ฐ€์ ธ์˜ค๊ธฐ
388
+ latest_blog = None
389
+ latest_blog_title = "๋ธ”๋กœ๊ทธ ๊ธ€"
390
+
391
+ if len(st.session_state.messages) > 0:
392
+ # ๊ฐ€์žฅ ์ตœ๊ทผ assistant ๋ฉ”์‹œ์ง€ ์ฐพ๊ธฐ
393
+ for msg in reversed(st.session_state.messages):
394
+ if msg["role"] == "assistant" and msg["content"].strip():
395
+ latest_blog = msg["content"]
396
+
397
+ # ํƒ€์ดํ‹€ ์ถ”์ถœ ์‹œ๋„ (์ฒซ ๋ฒˆ์งธ ์ œ๋ชฉ ํƒœ๊ทธ ์‚ฌ์šฉ)
398
+ title_match = re.search(r'# (.*?)(\n|$)', latest_blog)
399
+ if title_match:
400
+ latest_blog_title = title_match.group(1).strip()
401
+ # ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ํƒ€์ดํ‹€๋กœ ์‚ฌ์šฉ
402
+ elif len(st.session_state.messages) >= 2:
403
+ for i in range(len(st.session_state.messages)-1, -1, -1):
404
+ if st.session_state.messages[i]["role"] == "user":
405
+ latest_blog_title = st.session_state.messages[i]["content"][:30].strip()
406
+ if len(st.session_state.messages[i]["content"]) > 30:
407
+ latest_blog_title += "..."
408
+ break
409
+ break
410
+
411
+ # ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ๊ทธ๋ฃน
412
+ if latest_blog:
413
+ st.sidebar.subheader("์ตœ๊ทผ ๋ธ”๋กœ๊ทธ ๋‹ค์šด๋กœ๋“œ")
414
+
415
+ col1, col2 = st.sidebar.columns(2)
416
+
417
+ # ๋งˆํฌ๋‹ค์šด์œผ๋กœ ๋‹ค์šด๋กœ๋“œ
418
+ with col1:
419
+ st.download_button(
420
+ label="๋งˆํฌ๋‹ค์šด",
421
+ data=latest_blog,
422
+ file_name=f"{latest_blog_title}.md",
423
+ mime="text/markdown"
424
+ )
425
+
426
+ # HTML๋กœ ๋‹ค์šด๋กœ๋“œ
427
+ with col2:
428
+ html_content = convert_md_to_html(latest_blog, latest_blog_title)
429
+ st.download_button(
430
+ label="HTML",
431
+ data=html_content,
432
+ file_name=f"{latest_blog_title}.html",
433
+ mime="text/html"
434
+ )
435
+
436
+ # ๋Œ€ํ™” ๊ธฐ๋ก ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
437
+ uploaded_file = st.sidebar.file_uploader("๋Œ€ํ™” ๊ธฐ๋ก ๋ถˆ๋Ÿฌ์˜ค๊ธฐ", type=['json'])
438
+ if uploaded_file is not None:
439
+ try:
440
+ content = uploaded_file.getvalue().decode()
441
+ if content.strip():
442
+ st.session_state.messages = json.loads(content)
443
+ st.sidebar.success("๋Œ€ํ™” ๊ธฐ๋ก์„ ์„ฑ๊ณต์ ์œผ๋กœ ๋ถˆ๋Ÿฌ์™”์Šต๋‹ˆ๋‹ค!")
444
+ else:
445
+ st.sidebar.warning("์—…๋กœ๋“œ๋œ ํŒŒ์ผ์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.")
446
+ except json.JSONDecodeError:
447
+ st.sidebar.error("์˜ฌ๋ฐ”๋ฅธ JSON ํ˜•์‹์˜ ํŒŒ์ผ์ด ์•„๋‹™๋‹ˆ๋‹ค.")
448
+ except Exception as e:
449
+ st.sidebar.error(f"ํŒŒ์ผ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}")
450
+
451
+ # ๋Œ€ํ™” ๊ธฐ๋ก ์ดˆ๊ธฐํ™” ๋ฒ„ํŠผ
452
+ if st.sidebar.button("๋Œ€ํ™” ๊ธฐ๋ก ์ดˆ๊ธฐํ™”"):
453
+ st.session_state.messages = []
454
+ st.sidebar.success("๋Œ€ํ™” ๊ธฐ๋ก์ด ์ดˆ๊ธฐํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
455
+
456
+ # ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
457
+ for message in st.session_state.messages:
458
+ with st.chat_message(message["role"]):
459
+ st.markdown(message["content"])
460
+ # ์ด๋ฏธ์ง€๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ํ‘œ์‹œ
461
+ if "image" in message:
462
+ st.image(message["image"], caption=message.get("image_caption", "์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€"))
463
+
464
+ # ์‚ฌ์šฉ์ž ์ž…๋ ฅ
465
+ if prompt := st.chat_input("๋ฌด์—‡์„ ๋„์™€๋“œ๋ฆด๊นŒ์š”?"):
466
+ st.session_state.messages.append({"role": "user", "content": prompt})
467
+ with st.chat_message("user"):
468
+ st.markdown(prompt)
469
+
470
+ # AI ์‘๋‹ต ์ƒ์„ฑ
471
+ with st.chat_message("assistant"):
472
+ message_placeholder = st.empty()
473
+ full_response = ""
474
+
475
+ # ์›น ๊ฒ€์ƒ‰ ์ˆ˜ํ–‰ (์›น ๊ฒ€์ƒ‰ ์˜ต์…˜์ด ์ผœ์ ธ ์žˆ์„ ๊ฒฝ์šฐ)
476
+ system_prompt = get_system_prompt()
477
+ if st.session_state.use_web_search:
478
+ with st.spinner("์›น์—์„œ ๊ด€๋ จ ์ •๋ณด๋ฅผ ๊ฒ€์ƒ‰ ์ค‘..."):
479
+ try:
480
+ search_query = extract_keywords(prompt, top_k=5)
481
+ st.info(f"๊ฒ€์ƒ‰์–ด: {search_query}")
482
+
483
+ # ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ• ๋ชจ๋‘ ์‹œ๋„ (SerpHouse API์™€ ์ง์ ‘ ๊ฒ€์ƒ‰)
484
+ search_results = do_web_search(search_query)
485
+
486
+ if "๊ฐ€์ƒ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ" in search_results:
487
+ st.warning("์‹ค์ œ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์–ด ๊ธฐ์กด ์ง€์‹์„ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค.")
488
+ else:
489
+ st.success(f"๊ฒ€์ƒ‰ ์™„๋ฃŒ: '{search_query}'์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ˆ˜์ง‘ํ–ˆ์Šต๋‹ˆ๋‹ค.")
490
+
491
+ # ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ์— ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ถ”๊ฐ€
492
+ system_prompt += f"\n\n๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ:\n{search_results}\n"
493
+ except Exception as e:
494
+ st.error(f"์›น ๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}")
495
+ logging.error(f"์›น ๊ฒ€์ƒ‰ ์˜ค๋ฅ˜: {str(e)}")
496
+ system_prompt += "\n\n์›น ๊ฒ€์ƒ‰์ด ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ์กด ์ง€์‹์„ ๋ฐ”ํƒ•์œผ๋กœ ๋‹ต๋ณ€ํ•˜์„ธ์š”."
497
+
498
+ # API ํ˜ธ์ถœ
499
+ with client.messages.stream(
500
+ max_tokens=MAX_TOKENS,
501
+ system=system_prompt,
502
+ messages=[{"role": m["role"], "content": m["content"]} for m in st.session_state.messages],
503
+ model=st.session_state["ai_model"]
504
+ ) as stream:
505
+ for text in stream.text_stream:
506
+ full_response += str(text) if text is not None else ""
507
+ message_placeholder.markdown(full_response + "โ–Œ")
508
+
509
+ message_placeholder.markdown(full_response)
510
+
511
+ # ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์˜ต์…˜์ด ์ผœ์ ธ ์žˆ๋Š” ๊ฒฝ์šฐ
512
+ if st.session_state.generate_image:
513
+ with st.spinner("๋ธ”๋กœ๊ทธ์— ๋งž๋Š” ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘..."):
514
+ # ์ด๋ฏธ์ง€ ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ
515
+ image_prompt = extract_image_prompt(full_response, prompt)
516
+
517
+ # ์ด๋ฏธ์ง€ ์ƒ์„ฑ
518
+ image, image_caption = generate_image(
519
+ image_prompt,
520
+ width=width,
521
+ height=height,
522
+ guidance=guidance,
523
+ inference_steps=inference_steps,
524
+ seed=seed
525
+ )
526
+
527
+ if image:
528
+ st.image(image, caption=image_caption)
529
+ # ์ด๋ฏธ์ง€ ์ •๋ณด๋ฅผ ์‘๋‹ต์— ํฌํ•จ
530
+ st.session_state.messages.append({
531
+ "role": "assistant",
532
+ "content": full_response,
533
+ "image": image,
534
+ "image_caption": image_caption
535
+ })
536
+ else:
537
+ st.error(f"์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹คํŒจ: {image_caption}")
538
+ st.session_state.messages.append({
539
+ "role": "assistant",
540
+ "content": full_response
541
+ })
542
+ else:
543
+ # ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์—†์ด ์‘๋‹ต๋งŒ ์ €์žฅ
544
+ st.session_state.messages.append({
545
+ "role": "assistant",
546
+ "content": full_response
547
+ })
548
+
549
+ # ๋ธ”๋กœ๊ทธ ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ํ‘œ์‹œ (์‘๋‹ต ๋ฐ”๋กœ ์•„๋ž˜์—)
550
+ st.subheader("์ด ๋ธ”๋กœ๊ทธ ๋‹ค์šด๋กœ๋“œ:")
551
+ col1, col2 = st.columns(2)
552
+
553
+ with col1:
554
+ st.download_button(
555
+ label="๋งˆํฌ๋‹ค์šด์œผ๋กœ ์ €์žฅ",
556
+ data=full_response,
557
+ file_name=f"{prompt[:30]}.md",
558
+ mime="text/markdown"
559
+ )
560
+
561
+ with col2:
562
+ html_content = convert_md_to_html(full_response, prompt[:30])
563
+ st.download_button(
564
+ label="HTML๋กœ ์ €์žฅ",
565
+ data=html_content,
566
+ file_name=f"{prompt[:30]}.html",
567
+ mime="text/html"
568
+ )
569
+
570
+ # ์ž๋™ ์ €์žฅ ๊ธฐ๋Šฅ
571
+ if st.session_state.auto_save:
572
+ try:
573
+ # ์ด๋ฏธ์ง€ ์ •๋ณด๋Š” ์ €์žฅํ•˜์ง€ ์•Š์Œ (JSON์—๋Š” ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ง์ ‘ ์ €์žฅํ•  ์ˆ˜ ์—†์Œ)
574
+ save_messages = []
575
+ for msg in st.session_state.messages:
576
+ save_msg = {"role": msg["role"], "content": msg["content"]}
577
+ save_messages.append(save_msg)
578
+
579
+ # ํ˜„์žฌ ์‹œ๊ฐ„์„ ํฌํ•จํ•œ ํŒŒ์ผ๋ช… ์ƒ์„ฑ
580
+ current_time = datetime.now().strftime("%Y%m%d_%H%M%S")
581
+ filename = f'chat_history_auto_save_{current_time}.json'
582
+
583
+ with open(filename, 'w', encoding='utf-8') as f:
584
+ json.dump(save_messages, f, ensure_ascii=False, indent=4)
585
+ except Exception as e:
586
+ st.sidebar.error(f"์ž๋™ ์ €์žฅ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}")
587
+
588
+ # ๋Œ€ํ™” ๊ธฐ๋ก ๋‹ค์šด๋กœ๋“œ
589
+ if st.sidebar.button("๋Œ€ํ™” ๊ธฐ๋ก ๋‹ค์šด๋กœ๋“œ"):
590
+ # ์ด๋ฏธ์ง€ ์ •๋ณด๋Š” ์ €์žฅํ•˜์ง€ ์•Š์Œ
591
+ save_messages = []
592
+ for msg in st.session_state.messages:
593
+ save_msg = {"role": msg["role"], "content": msg["content"]}
594
+ save_messages.append(save_msg)
595
+
596
+ json_history = json.dumps(save_messages, indent=4, ensure_ascii=False)
597
+ st.sidebar.download_button(
598
+ label="๋Œ€ํ™” ๊ธฐ๋ก ์ €์žฅํ•˜๊ธฐ",
599
+ data=json_history,
600
+ file_name="chat_history.json",
601
+ mime="application/json"
602
+ )
603
+
604
+ def main():
605
+ chatbot_interface()
606
+
607
+ if __name__ == "__main__":
608
+ # requirements.txt ํŒŒ์ผ ์ƒ์„ฑ
609
+ with open("requirements.txt", "w") as f:
610
+ f.write("streamlit>=1.31.0\n")
611
+ f.write("anthropic>=0.18.1\n")
612
+ f.write("gradio-client>=1.8.0\n")
613
+ f.write("requests>=2.32.3\n")
614
+ f.write("markdown>=3.5.1\n")
615
+ f.write("pillow>=10.1.0\n")
616
+
617
+ main()import os
618
+ import streamlit as st
619
+ import json
620
+ import anthropic
621
+ import requests
622
+ import logging
623
+ from gradio_client import Client
624
+ import markdown
625
+ import tempfile
626
+ import base64
627
+ from datetime import datetime
628
+ import re
629
+
630
+ # ๋กœ๊น… ์„ค์ •
631
+ logging.basicConfig(
632
+ level=logging.INFO,
633
+ format='%(asctime)s - %(levelname)s - %(message)s')
634
+
635
+ # API ์„ค์ •
636
+ api_key = os.environ.get("API_KEY")
637
+ client = anthropic.Anthropic(api_key=api_key)
638
+
639
+ # ์ด๋ฏธ์ง€ ์ƒ์„ฑ API URL
640
+ IMAGE_API_URL = "http://211.233.58.201:7896"
641
+
642
+ # ์ตœ๋Œ€ ํ† ํฐ ์ˆ˜ ์„ค์ • (Claude-3 Sonnet์˜ ์ตœ๋Œ€ ํ† ํฐ ์ˆ˜)
643
+ MAX_TOKENS = 7999
644
+
645
+ # SerpHouse API Key ์„ค์ •
646
+ SERPHOUSE_API_KEY = os.environ.get("SERPHOUSE_API_KEY", "")
647
+
648
+ def get_system_prompt():
649
+ return """
650
+ ๋‹น์‹ ์€ ์ „๋ฌธ ๋ธ”๋กœ๊ทธ ์ž‘์„ฑ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ๋ชจ๋“  ๋ธ”๋กœ๊ทธ ๊ธ€ ์ž‘์„ฑ ์š”์ฒญ์— ๋Œ€ํ•ด ๋‹ค์Œ์˜ 8๋‹จ๊ณ„ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์ฒ ์ €ํžˆ ๋”ฐ๋ฅด๋˜, ์ž์—ฐ์Šค๋Ÿฝ๊ณ  ๋งค๋ ฅ์ ์ธ ๊ธ€์ด ๋˜๋„๋ก ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:
651
+
652
+ ๋…์ž ์—ฐ๊ฒฐ ๋‹จ๊ณ„ 1.1. ๊ณต๊ฐ๋Œ€ ํ˜•์„ฑ์„ ์œ„ํ•œ ์นœ๊ทผํ•œ ์ธ์‚ฌ 1.2. ๋…์ž์˜ ์‹ค์ œ ๊ณ ๋ฏผ์„ ๋ฐ˜์˜ํ•œ ๋„์ž… ์งˆ๋ฌธ 1.3. ์ฃผ์ œ์— ๋Œ€ํ•œ ์ฆ‰๊ฐ์  ๊ด€์‹ฌ ์œ ๋„
653
+
654
+ ๋ฌธ์ œ ์ •์˜ ๋‹จ๊ณ„ 2.1. ๋…์ž์˜ ํŽ˜์ธํฌ์ธํŠธ ๊ตฌ์ฒดํ™” 2.2. ๋ฌธ์ œ์˜ ์‹œ๊ธ‰์„ฑ๊ณผ ์˜ํ–ฅ๋„ ๋ถ„์„ 2.3. ํ•ด๊ฒฐ ํ•„์š”์„ฑ์— ๋Œ€ํ•œ ๊ณต๊ฐ๋Œ€ ํ˜•์„ฑ
655
+
656
+ ์ „๋ฌธ์„ฑ ์ž…์ฆ ๋‹จ๊ณ„ 3.1. ๊ฐ๊ด€์  ๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜ ๋ถ„์„ 3.2. ์ „๋ฌธ๊ฐ€ ๊ฒฌํ•ด์™€ ์—ฐ๊ตฌ ๊ฒฐ๊ณผ ์ธ์šฉ 3.3. ์‹ค์ œ ์‚ฌ๋ก€๋ฅผ ํ†ตํ•œ ๋ฌธ์ œ ๊ตฌ์ฒดํ™”
657
+
658
+ ์†”๋ฃจ์…˜ ์ œ๊ณต ๋‹จ๊ณ„ 4.1. ๋‹จ๊ณ„๋ณ„ ์‹ค์ฒœ ๊ฐ€์ด๋“œ๋ผ์ธ ์ œ์‹œ 4.2. ์ฆ‰์‹œ ์ ์šฉ ๊ฐ€๋Šฅํ•œ ๊ตฌ์ฒด์  ํŒ 4.3. ์˜ˆ์ƒ ์žฅ์• ๋ฌผ๊ณผ ๊ทน๋ณต ๋ฐฉ์•ˆ ํฌํ•จ
659
+
660
+ ์‹ ๋ขฐ๋„ ๊ฐ•ํ™” ๋‹จ๊ณ„ 5.1. ์‹ค์ œ ์„ฑ๊ณต ์‚ฌ๋ก€ ์ œ์‹œ 5.2. ๊ตฌ์ฒด์  ์‚ฌ์šฉ์ž ํ›„๊ธฐ ์ธ์šฉ 5.3. ๊ฐ๊ด€์  ๋ฐ์ดํ„ฐ๋กœ ํšจ๊ณผ ์ž…์ฆ
661
+
662
+ ํ–‰๋™ ์œ ๋„ ๋‹จ๊ณ„ 6.1. ๋ช…ํ™•ํ•œ ์ฒซ ์‹ค์ฒœ ๋‹จ๊ณ„ ์ œ์‹œ 6.2. ์‹œ๊ธ‰์„ฑ์„ ๊ฐ•์กฐํ•œ ํ–‰๋™ ์ด‰๊ตฌ 6.3. ์‹ค์ฒœ ๋™๊ธฐ ๋ถ€์—ฌ ์š”์†Œ ํฌํ•จ
663
+
664
+ ์ง„์ •์„ฑ ๊ฐ•ํ™” ๋‹จ๊ณ„ 7.1. ์†”๋ฃจ์…˜์˜ ํ•œ๊ณ„ ํˆฌ๋ช…ํ•˜๊ฒŒ ๊ณต๊ฐœ 7.2. ๊ฐœ์ธ๋ณ„ ์ฐจ์ด ์กด์žฌ ์ธ์ • 7.3. ํ•„์š” ์กฐ๊ฑด๊ณผ ์ฃผ์˜์‚ฌํ•ญ ๋ช…์‹œ
665
+
666
+ ๊ด€๊ณ„ ์ง€์† ๋‹จ๊ณ„ 8.1. ์ง„์ •์„ฑ ์žˆ๋Š” ๊ฐ์‚ฌ ์ธ์‚ฌ 8.2. ๋‹ค์Œ ์ปจํ…์ธ  ์˜ˆ๊ณ ๋กœ ๊ธฐ๋Œ€๊ฐ ์กฐ์„ฑ 8.3. ์†Œํ†ต ์ฑ„๋„ ์•ˆ๋‚ด
667
+
668
+ ์ž‘์„ฑ ์‹œ ์ค€์ˆ˜์‚ฌํ•ญ 9.1. ๊ธ€์ž ์ˆ˜: 1500-2000์ž ๋‚ด์™ธ 9.2. ๋ฌธ๋‹จ ๊ธธ์ด: 3-4๋ฌธ์žฅ ์ด๋‚ด 9.3. ์‹œ๊ฐ์  ๊ตฌ๋ถ„: ์†Œ์ œ๋ชฉ, ๊ตฌ๋ถ„์„ , ๋ฒˆํ˜ธ ๋ชฉ๋ก ํ™œ์šฉ 9.4. ํ†ค์•ค๋งค๋„ˆ: ์นœ๊ทผํ•˜๊ณ  ์ „๋ฌธ์ ์ธ ๋Œ€ํ™”์ฒด 9.5. ๋ฐ์ดํ„ฐ: ๋ชจ๋“  ์ •๋ณด์˜ ์ถœ์ฒ˜ ๋ช…์‹œ 9.6. ๊ฐ€๋…์„ฑ: ๋ช…ํ™•ํ•œ ๋‹จ๋ฝ ๊ตฌ๋ถ„๊ณผ ๊ฐ•์กฐ์  ์‚ฌ์šฉ
669
+
670
+ ์ด๋Ÿฌํ•œ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ, ์š”์ฒญ๋ฐ›์€ ์ฃผ์ œ์— ๋Œ€ํ•ด ์ฒด๊ณ„์ ์ด๊ณ  ๋งค๋ ฅ์ ์ธ ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
671
+ """
672
+
673
+ def test_image_api_connection():
674
+ """์ด๋ฏธ์ง€ API ์„œ๋ฒ„ ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ"""
675
+ try:
676
+ client = Client(IMAGE_API_URL)
677
+ return "์ด๋ฏธ์ง€ API ์—ฐ๊ฒฐ ์„ฑ๊ณต: ์ •์ƒ ์ž‘๋™ ์ค‘"
678
+ except Exception as e:
679
+ logging.error(f"์ด๋ฏธ์ง€ API ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ ์‹คํŒจ: {e}")
680
+ return f"์ด๋ฏธ์ง€ API ์—ฐ๊ฒฐ ์‹คํŒจ: {e}"
681
+
682
+ def generate_image(prompt, width=768, height=768, guidance=3.5, inference_steps=30, seed=3):
683
+ """์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ•จ์ˆ˜"""
684
+ if not prompt:
685
+ return None, "์˜ค๋ฅ˜: ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"
686
+
687
+ try:
688
+ client = Client(IMAGE_API_URL)
689
+ result = client.predict(
690
+ prompt=prompt,
691
+ width=int(width),
692
+ height=int(height),
693
+ guidance=float(guidance),
694
+ inference_steps=int(inference_steps),
695
+ seed=int(seed),
696
+ do_img2img=False,
697
+ init_image=None,
698
+ image2image_strength=0.8,
699
+ resize_img=True,
700
+ api_name="/generate_image"
701
+ )
702
+ logging.info(f"์ด๋ฏธ์ง€ ์ƒ์„ฑ ์„ฑ๊ณต: {result[1]}")
703
+ return result[0], f"์‚ฌ์šฉ๋œ ์‹œ๋“œ: {result[1]}"
704
+ except Exception as e:
705
+ logging.error(f"์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹คํŒจ: {str(e)}")
706
+ return None, f"์˜ค๋ฅ˜: {str(e)}"
707
+
708
+ def extract_image_prompt(blog_content, blog_topic):
709
+ """๋ธ”๋กœ๊ทธ ๋‚ด์šฉ์—์„œ ์ด๋ฏธ์ง€ ์ƒ์„ฑ์„ ์œ„ํ•œ ํ”„๋กฌํ”„ํŠธ ์ถ”์ถœ"""
710
+ image_prompt_system = f"""
711
+ ๋‹ค์Œ์€ '{blog_topic}'์— ๊ด€ํ•œ ๋ธ”๋กœ๊ทธ ๊ธ€์ž…๋‹ˆ๋‹ค. ์ด ๋ธ”๋กœ๊ทธ ๊ธ€์˜ ๋‚ด์šฉ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ ์ ˆํ•œ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•œ
712
+ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”. ํ”„๋กฌํ”„ํŠธ๋Š” ์˜์–ด๋กœ ์ž‘์„ฑํ•˜๊ณ , ๊ตฌ์ฒด์ ์ธ ์‹œ๊ฐ์  ์š”์†Œ๋ฅผ ๋‹ด์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.
713
+ ํ”„๋กฌํ”„ํŠธ๋งŒ ๋ฐ˜ํ™˜ํ•˜์„ธ์š”(๋‹ค๋ฅธ ์„ค๋ช… ์—†์ด).
714
+
715
+ ์˜ˆ์‹œ ํ˜•์‹:
716
+ "A professional photo of [subject], [specific details], [atmosphere], [lighting], [perspective], high quality, detailed"
717
+ """
718
+
719
+ try:
720
+ response = client.messages.create(
721
+ model="claude-3-7-sonnet-20250219",
722
+ max_tokens=150,
723
+ system=image_prompt_system,
724
+ messages=[{"role": "user", "content": blog_content}]
725
+ )
726
+
727
+ # ์‘๋‹ต์—์„œ ํ”„๋กฌํ”„ํŠธ ์ถ”์ถœ
728
+ image_prompt = response.content[0].text.strip()
729
+ logging.info(f"์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€ ํ”„๋กฌํ”„ํŠธ: {image_prompt}")
730
+ return image_prompt
731
+ except Exception as e:
732
+ logging.error(f"์ด๋ฏธ์ง€ ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ ์˜ค๋ฅ˜: {e}")
733
+ return f"A professional photo related to {blog_topic}, detailed, high quality"
734
+
735
+ # ๋งˆํฌ๋‹ค์šด์„ HTML๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜
736
+ def convert_md_to_html(md_text, title="Ginigen Blog"):
737
+ html_content = markdown.markdown(md_text)
738
+ html_doc = f"""
739
+ <!DOCTYPE html>
740
+ <html>
741
+ <head>
742
+ <title>{title}</title>
743
+ <meta charset="utf-8">
744
+ <style>
745
+ body {{ font-family: Arial, sans-serif; line-height: 1.6; max-width: 800px; margin: 0 auto; padding: 20px; }}
746
+ h1 {{ color: #2c3e50; font-size: 2.5em; margin-bottom: 20px; }}
747
+ h2 {{ color: #3498db; margin-top: 25px; font-size: 1.8em; }}
748
+ h3 {{ color: #2980b9; font-size: 1.5em; }}
749
+ p {{ margin-bottom: 15px; font-size: 1.1em; }}
750
+ blockquote {{ background: #f9f9f9; border-left: 10px solid #ccc; margin: 1.5em 10px; padding: 1em 10px; }}
751
+ ul, ol {{ margin-bottom: 15px; }}
752
+ li {{ margin-bottom: 5px; }}
753
+ hr {{ border: 0; height: 1px; background: #ddd; margin: 20px 0; }}
754
+ img {{ max-width: 100%; height: auto; display: block; margin: 20px auto; }}
755
+ </style>
756
+ </head>
757
+ <body>
758
+ {html_content}
759
+ </body>
760
+ </html>
761
+ """
762
+ return html_doc
763
+
764
+ # ์›น ๊ฒ€์ƒ‰ ํ‚ค์›Œ๋“œ ์ถ”์ถœ ํ•จ์ˆ˜
765
+ def extract_keywords(text: str, top_k: int = 5) -> str:
766
+ """
767
+ 1) ํ•œ๊ธ€(๊ฐ€-ํžฃ), ์˜์–ด(a-zA-Z), ์ˆซ์ž(0-9), ๊ณต๋ฐฑ๋งŒ ๋‚จ๊น€
768
+ 2) ๊ณต๋ฐฑ ๊ธฐ์ค€ ํ† ํฐ ๋ถ„๋ฆฌ
769
+ 3) ์ตœ๋Œ€ top_k๊ฐœ๋งŒ
770
+ """
771
+ text = re.sub(r"[^a-zA-Z0-9๊ฐ€-ํžฃ\s]", "", text)
772
+ tokens = text.split()
773
+ key_tokens = tokens[:top_k]
774
+ return " ".join(key_tokens)
775
+
776
+ # Mock ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ƒ์„ฑ ํ•จ์ˆ˜
777
+ def generate_mock_search_results(query):
778
+ """API ์—ฐ๊ฒฐ์ด ์•ˆ๋  ๋•Œ ์‚ฌ์šฉํ•  ๊ฐ€์ƒ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ƒ์„ฑ"""
779
+ current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
780
+ mock_results = [
781
+ {
782
+ "title": f"{query}์— ๊ด€ํ•œ ์ตœ์‹  ์ •๋ณด",
783
+ "link": "https://example.com/article1",
784
+ "snippet": f"{query}์— ๊ด€ํ•œ ๊ฐ€์ƒ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค. ์ด ๊ฒฐ๊ณผ๋Š” API ์—ฐ๊ฒฐ ๋ฌธ์ œ๋กœ ์ธํ•ด ์ƒ์„ฑ๋œ ๊ฐ€์ƒ ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค. ์‹ค์ œ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์•„๋‹˜์„ ์ฐธ๊ณ ํ•˜์„ธ์š”. ์ƒ์„ฑ ์‹œ๊ฐ„: {current_time}",
785
+ "displayed_link": "example.com/article1"
786
+ },
787
+ {
788
+ "title": f"{query} ๊ด€๋ จ ์—ฐ๊ตฌ ๋™ํ–ฅ",
789
+ "link": "https://example.org/research",
790
+ "snippet": "์ด๊ฒƒ์€ API ์—ฐ๊ฒฐ ๋ฌธ์ œ๋กœ ์ธํ•œ ๊ฐ€์ƒ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค. ์‹ค์ œ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์—ฌ๋“œ๋ฆฌ์ง€ ๋ชปํ•ด ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ๋Œ€์‹  AI์˜ ๊ธฐ์กด ์ง€์‹์„ ํ™œ์šฉํ•˜์—ฌ ๋‹ต๋ณ€๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.",
791
+ "displayed_link": "example.org/research"
792
+ },
793
+ {
794
+ "title": f"{query}์˜ ์—ญ์‚ฌ์  ๋ฐฐ๊ฒฝ",
795
+ "link": "https://example.net/history",
796
+ "snippet": "์ด ๊ฐ€์ƒ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋Š” API ์—ฐ๊ฒฐ ๋ฌธ์ œ๋กœ ์ธํ•ด ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ฐธ๊ณ ์šฉ์œผ๋กœ๋งŒ ์‚ฌ์šฉํ•ด์ฃผ์„ธ์š”.",
797
+ "displayed_link": "example.net/history"
798
  }
799
  ]
800
 
 
820
 
821
  return notice + "\n".join(summary_lines)
822
 
823
+ # Google ๊ฒ€์ƒ‰ ํ•จ์ˆ˜ (SerpAPI ๋Œ€์‹  ์ง์ ‘ ๊ฒ€์ƒ‰)
824
+ def do_google_search(query, num_results=5):
825
+ try:
826
+ # ๊ธฐ๋ณธ ํ—ค๋” ์„ค์ • (๋ธŒ๋ผ์šฐ์ €์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ)
827
+ headers = {
828
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
829
+ }
830
+
831
+ # ๊ฒ€์ƒ‰ URL ๋งŒ๋“ค๊ธฐ
832
+ search_url = f"https://www.google.com/search?q={query}&num={num_results}"
833
+ logging.info(f"๊ตฌ๊ธ€ ๊ฒ€์ƒ‰ URL: {search_url}")
834
+
835
+ # ์š”์ฒญ ๋ณด๋‚ด๊ธฐ (์งง์€ ํƒ€์ž„์•„์›ƒ ์„ค์ •)
836
+ response = requests.get(search_url, headers=headers, timeout=10)
837
+
838
+ # ์‘๋‹ต์ด ์„ฑ๊ณต์ ์ธ์ง€ ํ™•์ธ
839
+ if response.status_code != 200:
840
+ logging.error(f"Google ๊ฒ€์ƒ‰ ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ: {response.status_code}")
841
+ return generate_mock_search_results(query)
842
+
843
+ # ์—ฌ๊ธฐ์„œ๋Š” HTML ๋ถ„์„์ด ๋ณต์žกํ•˜๋ฏ€๋กœ, ์‹ค์ œ๋กœ๋Š” BeautifulSoup ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Œ
844
+ # ๊ฐ„๋‹จํ•œ ๋ฐ๋ชจ ๋ชฉ์ ์œผ๋กœ ๋Œ€์‹  Mock ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜
845
+ logging.info("Google ๊ฒ€์ƒ‰ ์„ฑ๊ณตํ–ˆ์œผ๋‚˜ ํŒŒ์‹ฑ ์ œํ•œ์œผ๋กœ Mock ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜")
846
+ return generate_mock_search_results(query)
847
+
848
+ except Exception as e:
849
+ logging.error(f"Google ๊ฒ€์ƒ‰ ์‹คํŒจ: {e}")
850
+ return generate_mock_search_results(query)
851
+
852
  # ์›น ๊ฒ€์ƒ‰ ํ•จ์ˆ˜
853
  def do_web_search(query: str) -> str:
854
  """
855
+ ์›น ๊ฒ€์ƒ‰์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํ•จ์ˆ˜ - SerpHouse API ๋˜๋Š” ์ง์ ‘ ๊ตฌ๊ธ€ ๊ฒ€์ƒ‰
 
856
  """
857
  try:
858
+ # API ํ‚ค๊ฐ€ ์—†๊ฑฐ๋‚˜ 'mock'์ธ ๊ฒฝ์šฐ
859
+ if not SERPHOUSE_API_KEY or "mock" in SERPHOUSE_API_KEY.lower():
860
+ logging.warning("API ํ‚ค๊ฐ€ ์—†๊ฑฐ๋‚˜ Mock ๋ชจ๋“œ์ž…๋‹ˆ๋‹ค. ๊ฐ€์ƒ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.")
861
+ return generate_mock_search_results(query)
862
 
863
+ # SerpHouse API ์‚ฌ์šฉ
864
+ url = "https://api.serphouse.com/serp/live"
865
  params = {
866
  "q": query,
867
  "domain": "google.com",
868
+ "serp_type": "web",
869
  "device": "desktop",
870
+ "lang": "ko", # ํ•œ๊ตญ์–ด ๊ฒฐ๊ณผ
871
+ "num": "5" # ๊ฒฐ๊ณผ ์ˆ˜ ์ค„์ž„
872
  }
873
 
874
  headers = {
 
876
  }
877
 
878
  logging.info(f"SerpHouse API ํ˜ธ์ถœ ์ค‘... ๊ฒ€์ƒ‰์–ด: {query}")
 
 
 
 
 
 
879
 
880
+ # ์งง์€ ํƒ€์ž„์•„์›ƒ์œผ๋กœ ์š”์ฒญ ์‹œ๋„
881
+ response = requests.get(url, headers=headers, params=params, timeout=15)
882
  response.raise_for_status()
883
 
884
  logging.info(f"SerpHouse API ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ: {response.status_code}")
 
891
  # ๊ฐ€๋Šฅํ•œ ์‘๋‹ต ๊ตฌ์กฐ 1
892
  if isinstance(results, dict) and "organic" in results:
893
  organic = results["organic"]
894
+ # ๊ฐ€๋Šฅํ•œ ์‘๋‹ต ๊ตฌ์กฐ 2
 
895
  elif isinstance(results, dict) and "results" in results:
896
  if isinstance(results["results"], dict) and "organic" in results["results"]:
897
  organic = results["results"]["organic"]
898
+ # ๊ฐ€๋Šฅํ•œ ์‘๋‹ต ๊ตฌ์กฐ 3
 
899
  elif "organic" in data:
900
  organic = data["organic"]
901
 
902
  if not organic:
903
+ logging.warning("์‘๋‹ต์—์„œ organic ๊ฒฐ๊ณผ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ตฌ๊ธ€ ์ง์ ‘ ๊ฒ€์ƒ‰์œผ๋กœ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค.")
904
+ return do_google_search(query)
 
 
 
905
 
906
  # ๊ฒฐ๊ณผ ์ˆ˜ ์ œํ•œ ๋ฐ ์ปจํ…์ŠคํŠธ ๊ธธ์ด ์ตœ์ ํ™”
907
+ max_results = min(5, len(organic))
908
  limited_organic = organic[:max_results]
909
 
910
+ # ๊ฒฐ๊ณผ ํ˜•์‹ ๊ฐœ์„ 
911
  summary_lines = []
912
  for idx, item in enumerate(limited_organic, start=1):
913
  title = item.get("title", "No title")
 
915
  snippet = item.get("snippet", "No description")
916
  displayed_link = item.get("displayed_link", link)
917
 
 
918
  summary_lines.append(
919
  f"### Result {idx}: {title}\n\n"
920
  f"{snippet}\n\n"
 
937
  return search_results
938
 
939
  except requests.exceptions.Timeout:
940
+ logging.error("Web search timed out, ์ง์ ‘ ๊ตฌ๊ธ€ ๊ฒ€์ƒ‰์œผ๋กœ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค.")
941
+ return do_google_search(query)
942
  except Exception as e:
943
+ logging.error(f"Web search failed: {e}, ์ง์ ‘ ๊ตฌ๊ธ€ ๊ฒ€์ƒ‰์œผ๋กœ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค.")
944
+ return do_google_search(query)
945
 
946
  def chatbot_interface():
947
  st.title("Ginigen Blog")
 
1094
  with st.spinner("์›น์—์„œ ๊ด€๋ จ ์ •๋ณด๋ฅผ ๊ฒ€์ƒ‰ ์ค‘..."):
1095
  try:
1096
  search_query = extract_keywords(prompt, top_k=5)
1097
+ st.info(f"๊ฒ€์ƒ‰์–ด: {search_query}")
1098
+
1099
+ # ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ• ๋ชจ๋‘ ์‹œ๋„ (SerpHouse API์™€ ์ง์ ‘ ๊ฒ€์ƒ‰)
1100
  search_results = do_web_search(search_query)
1101
 
1102
+ if "๊ฐ€์ƒ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ" in search_results:
1103
+ st.warning("์‹ค์ œ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์–ด ๊ธฐ์กด ์ง€์‹์„ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค.")
 
 
1104
  else:
1105
+ st.success(f"๊ฒ€์ƒ‰ ์™„๋ฃŒ: '{search_query}'์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ˆ˜์ง‘ํ–ˆ์Šต๋‹ˆ๋‹ค.")
1106
+
1107
+ # ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ์— ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ถ”๊ฐ€
1108
+ system_prompt += f"\n\n๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ:\n{search_results}\n"
1109
  except Exception as e:
1110
  st.error(f"์›น ๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}")
1111
  logging.error(f"์›น ๊ฒ€์ƒ‰ ์˜ค๋ฅ˜: {str(e)}")
1112
+ system_prompt += "\n\n์›น ๊ฒ€์ƒ‰์ด ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ์กด ์ง€์‹์„ ๋ฐ”ํƒ•์œผ๋กœ ๋‹ต๋ณ€ํ•˜์„ธ์š”."
1113
 
1114
  # API ํ˜ธ์ถœ
1115
  with client.messages.stream(