ginipick commited on
Commit
4f228ef
Β·
verified Β·
1 Parent(s): 62d2b15

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +106 -68
app.py CHANGED
@@ -45,45 +45,38 @@ logging.basicConfig(level=logging.INFO,
45
  client = anthropic.Anthropic(api_key=ANTHROPIC_KEY)
46
 
47
  # ──────────────────────────────── λΈ”λ‘œκ·Έ μž‘μ„± μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ ────────────────
48
- def get_system_prompt(template="standard", tone="professional", word_count=1750) -> str:
 
49
  base_prompt = """
50
  당신은 μ „λ¬Έ λΈ”λ‘œκ·Έ μž‘μ„± μ „λ¬Έκ°€μž…λ‹ˆλ‹€. λͺ¨λ“  λΈ”λ‘œκ·Έ κΈ€ μž‘μ„± μš”μ²­μ— λŒ€ν•΄ λ‹€μŒμ˜ 8단계 ν”„λ ˆμž„μ›Œν¬λ₯Ό μ² μ €νžˆ λ”°λ₯΄λ˜, μžμ—°μŠ€λŸ½κ³  λ§€λ ₯적인 글이 λ˜λ„λ‘ μž‘μ„±ν•΄μ•Ό ν•©λ‹ˆλ‹€:
51
-
52
  λ…μž μ—°κ²° 단계
53
  1.1. κ³΅κ°λŒ€ ν˜•μ„±μ„ μœ„ν•œ μΉœκ·Όν•œ 인사
54
  1.2. λ…μžμ˜ μ‹€μ œ 고민을 λ°˜μ˜ν•œ λ„μž… 질문
55
  1.3. μ£Όμ œμ— λŒ€ν•œ 즉각적 관심 μœ λ„
56
-
57
  문제 μ •μ˜ 단계
58
  2.1. λ…μžμ˜ 페인포인트 ꡬ체화
59
  2.2. 문제의 μ‹œκΈ‰μ„±κ³Ό 영ν–₯도 뢄석
60
  2.3. ν•΄κ²° ν•„μš”μ„±μ— λŒ€ν•œ κ³΅κ°λŒ€ ν˜•μ„±
61
-
62
  μ „λ¬Έμ„± μž…μ¦ 단계
63
  3.1. 객관적 데이터 기반 뢄석
64
  3.2. μ „λ¬Έκ°€ 견해와 연ꡬ κ²°κ³Ό 인용
65
  3.3. μ‹€μ œ 사둀λ₯Ό ν†΅ν•œ 문제 ꡬ체화
66
-
67
  μ†”λ£¨μ…˜ 제곡 단계
68
  4.1. 단계별 μ‹€μ²œ κ°€μ΄λ“œλΌμΈ μ œμ‹œ
69
  4.2. μ¦‰μ‹œ 적용 κ°€λŠ₯ν•œ ꡬ체적 팁
70
  4.3. μ˜ˆμƒ μž₯μ• λ¬Όκ³Ό 극볡 λ°©μ•ˆ 포함
71
-
72
  신뒰도 κ°•ν™” 단계
73
  5.1. μ‹€μ œ 성곡 사둀 μ œμ‹œ
74
  5.2. ꡬ체적 μ‚¬μš©μž ν›„κΈ° 인용
75
  5.3. 객관적 λ°μ΄ν„°λ‘œ 효과 μž…μ¦
76
-
77
  행동 μœ λ„ 단계
78
  6.1. λͺ…ν™•ν•œ 첫 μ‹€μ²œ 단계 μ œμ‹œ
79
  6.2. μ‹œκΈ‰μ„±μ„ κ°•μ‘°ν•œ 행동 촉ꡬ
80
  6.3. μ‹€μ²œ 동기 λΆ€μ—¬ μš”μ†Œ 포함
81
-
82
  μ§„μ •μ„± κ°•ν™” 단계
83
  7.1. μ†”λ£¨μ…˜μ˜ ν•œκ³„ 투λͺ…ν•˜κ²Œ 곡개
84
  7.2. κ°œμΈλ³„ 차이 쑴재 인정
85
  7.3. ν•„μš” 쑰건과 μ£Όμ˜μ‚¬ν•­ λͺ…μ‹œ
86
-
87
  관계 지속 단계
88
  8.1. μ§„μ •μ„± μžˆλŠ” 감사 인사
89
  8.2. λ‹€μŒ 컨텐츠 예고둜 κΈ°λŒ€κ° μ‘°μ„±
@@ -145,6 +138,17 @@ def get_system_prompt(template="standard", tone="professional", word_count=1750)
145
  "storytelling": "이야기λ₯Ό λ“€λ €μ£Όλ“― 감성적이고 λͺ°μž…감 μžˆλŠ” ν†€μœΌλ‘œ μž‘μ„±ν•΄ μ£Όμ„Έμš”. 인물, λ°°κ²½, κ°ˆλ“±, 해결과정이 λ‹΄κΈ΄ λ‚΄λŸ¬ν‹°λΈŒ ꡬ쑰λ₯Ό ν™œμš©ν•˜μ„Έμš”."
146
  }
147
 
 
 
 
 
 
 
 
 
 
 
 
148
  # μ΅œμ’… ν”„λ‘¬ν”„νŠΈ μ‘°ν•©
149
  final_prompt = base_prompt
150
 
@@ -156,6 +160,10 @@ def get_system_prompt(template="standard", tone="professional", word_count=1750)
156
  if tone in tone_guides:
157
  final_prompt += f"\n\nν†€μ•€λ§€λ„ˆ: {tone_guides[tone]}"
158
 
 
 
 
 
159
  # κΈ€μž 수 μ§€μΉ¨ μΆ”κ°€
160
  final_prompt += f"\n\nμž‘μ„± μ‹œ μ€€μˆ˜μ‚¬ν•­\n9.1. κΈ€μž 수: {word_count-250}-{word_count+250}자 λ‚΄μ™Έ\n9.2. 문단 길이: 3-4λ¬Έμž₯ 이내\n9.3. μ‹œκ°μ  ꡬ뢄: μ†Œμ œλͺ©, ꡬ뢄선, 번호 λͺ©λ‘ ν™œμš©\n9.4. 데이터: λͺ¨λ“  μ •λ³΄μ˜ 좜처 λͺ…μ‹œ\n9.5. 가독성: λͺ…ν™•ν•œ 단락 ꡬ뢄과 강쑰점 μ‚¬μš©"
161
 
@@ -228,12 +236,12 @@ def mock_results(query: str) -> str:
228
  def do_web_search(query: str) -> str:
229
  """μ›Ή 검색 μˆ˜ν–‰ 및 κ²°κ³Ό ν¬λ§·νŒ…"""
230
  try:
231
- arts = brave_search(query, 20) # 여기도 20으둜 λ³€κ²½
232
  if not arts:
233
  logging.warning("검색 κ²°κ³Ό μ—†μŒ, λŒ€μ²΄ μ½˜ν…μΈ  μ‚¬μš©")
234
  return mock_results(query)
235
 
236
- hdr = "# μ›Ή 검색 κ²°κ³Ό\nμ•„λž˜ 정보λ₯Ό μ°Έκ³ ν•΄μ„œ λ‹΅λ³€ν•˜μ„Έμš”.\n\n"
237
  body = "\n".join(
238
  f"### Result {a['index']}: {a['title']}\n\n{a['snippet']}\n\n"
239
  f"**좜처**: [{a['displayed_link']}]({a['link']})\n\n---\n"
@@ -397,63 +405,93 @@ def process_input(prompt):
397
  with st.chat_message("assistant"):
398
  placeholder = st.empty(); answer = ""
399
 
400
- # μ„ νƒλœ ν…œν”Œλ¦Ώ, 톀, 단어 수둜 μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ 생성
401
- sys_prompt = get_system_prompt(
402
- template=st.session_state.blog_template,
403
- tone=st.session_state.blog_tone,
404
- word_count=st.session_state.word_count
405
- )
406
-
407
- if st.session_state.use_web_search:
408
- with st.spinner("μ›Ή 검색 쀑…"):
409
- search_md = do_web_search(keywords(prompt))
410
- sys_prompt += f"\n\n검색 κ²°κ³Ό:\n{search_md}\n"
411
-
412
- # Claude 슀트리밍
413
- with client.messages.stream(
414
- model=st.session_state.ai_model, max_tokens=MAX_TOKENS,
415
- system=sys_prompt,
416
- messages=[{"role": m["role"], "content": m["content"]}
417
- for m in st.session_state.messages]
418
- ) as stream:
419
- for t in stream.text_stream:
420
- answer += t or ""
421
- placeholder.markdown(answer + "β–Œ")
422
- placeholder.markdown(answer)
423
-
424
- # 이미지 μ˜΅μ…˜
425
- answer_entry_saved = False
426
- if st.session_state.generate_image:
427
- with st.spinner("이미지 생성 쀑…"):
428
- ip = extract_image_prompt(answer, prompt)
429
- img, cap = generate_image(ip)
430
- if img:
431
- st.image(img, caption=cap)
432
- st.session_state.messages.append(
433
- {"role": "assistant", "content": answer,
434
- "image": img, "image_caption": cap})
435
- answer_entry_saved = True
436
- if not answer_entry_saved:
437
- st.session_state.messages.append(
438
- {"role": "assistant", "content": answer})
439
-
440
- # λ³Έλ¬Έ λ‹€μš΄λ‘œλ“œ λ²„νŠΌ (MD / HTML)
441
- st.subheader("이 λΈ”λ‘œκ·Έ λ‹€μš΄λ‘œλ“œ")
442
- b1, b2 = st.columns(2)
443
- b1.download_button("λ§ˆν¬λ‹€μš΄", answer,
444
- file_name=f"{prompt[:30]}.md", mime="text/markdown")
445
- b2.download_button("HTML", md_to_html(answer, prompt[:30]),
446
- file_name=f"{prompt[:30]}.html", mime="text/html")
447
-
448
- # ── μžλ™ λ°±μ—… μ €μž₯
449
- if st.session_state.auto_save and st.session_state.messages:
450
- try:
451
- fn = f"chat_history_auto_{datetime.now():%Y%m%d_%H%M%S}.json"
452
- with open(fn, "w", encoding="utf-8") as fp:
453
- json.dump(st.session_state.messages, fp,
454
- ensure_ascii=False, indent=2)
455
- except Exception as e:
456
- logging.error(f"μžλ™ μ €μž₯ μ‹€νŒ¨: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
457
 
458
  # ──────────────────────────────── main / requirements ──────────────────────
459
  def main(): ginigen_app()
 
45
  client = anthropic.Anthropic(api_key=ANTHROPIC_KEY)
46
 
47
  # ──────────────────────────────── λΈ”λ‘œκ·Έ μž‘μ„± μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ ────────────────
48
+ # ──────────────────────────────── λΈ”λ‘œκ·Έ μž‘μ„± μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ ────────────────
49
+ def get_system_prompt(template="standard", tone="professional", word_count=1750, include_search_results=False) -> str:
50
  base_prompt = """
51
  당신은 μ „λ¬Έ λΈ”λ‘œκ·Έ μž‘μ„± μ „λ¬Έκ°€μž…λ‹ˆλ‹€. λͺ¨λ“  λΈ”λ‘œκ·Έ κΈ€ μž‘μ„± μš”μ²­μ— λŒ€ν•΄ λ‹€μŒμ˜ 8단계 ν”„λ ˆμž„μ›Œν¬λ₯Ό μ² μ €νžˆ λ”°λ₯΄λ˜, μžμ—°μŠ€λŸ½κ³  λ§€λ ₯적인 글이 λ˜λ„λ‘ μž‘μ„±ν•΄μ•Ό ν•©λ‹ˆλ‹€:
 
52
  λ…μž μ—°κ²° 단계
53
  1.1. κ³΅κ°λŒ€ ν˜•μ„±μ„ μœ„ν•œ μΉœκ·Όν•œ 인사
54
  1.2. λ…μžμ˜ μ‹€μ œ 고민을 λ°˜μ˜ν•œ λ„μž… 질문
55
  1.3. μ£Όμ œμ— λŒ€ν•œ 즉각적 관심 μœ λ„
 
56
  문제 μ •μ˜ 단계
57
  2.1. λ…μžμ˜ 페인포인트 ꡬ체화
58
  2.2. 문제의 μ‹œκΈ‰μ„±κ³Ό 영ν–₯도 뢄석
59
  2.3. ν•΄κ²° ν•„μš”μ„±μ— λŒ€ν•œ κ³΅κ°λŒ€ ν˜•μ„±
 
60
  μ „λ¬Έμ„± μž…μ¦ 단계
61
  3.1. 객관적 데이터 기반 뢄석
62
  3.2. μ „λ¬Έκ°€ 견해와 연ꡬ κ²°κ³Ό 인용
63
  3.3. μ‹€μ œ 사둀λ₯Ό ν†΅ν•œ 문제 ꡬ체화
 
64
  μ†”λ£¨μ…˜ 제곡 단계
65
  4.1. 단계별 μ‹€μ²œ κ°€μ΄λ“œλΌμΈ μ œμ‹œ
66
  4.2. μ¦‰μ‹œ 적용 κ°€λŠ₯ν•œ ꡬ체적 팁
67
  4.3. μ˜ˆμƒ μž₯μ• λ¬Όκ³Ό 극볡 λ°©μ•ˆ 포함
 
68
  신뒰도 κ°•ν™” 단계
69
  5.1. μ‹€μ œ 성곡 사둀 μ œμ‹œ
70
  5.2. ꡬ체적 μ‚¬μš©μž ν›„κΈ° 인용
71
  5.3. 객관적 λ°μ΄ν„°λ‘œ 효과 μž…μ¦
 
72
  행동 μœ λ„ 단계
73
  6.1. λͺ…ν™•ν•œ 첫 μ‹€μ²œ 단계 μ œμ‹œ
74
  6.2. μ‹œκΈ‰μ„±μ„ κ°•μ‘°ν•œ 행동 촉ꡬ
75
  6.3. μ‹€μ²œ 동기 λΆ€μ—¬ μš”μ†Œ 포함
 
76
  μ§„μ •μ„± κ°•ν™” 단계
77
  7.1. μ†”λ£¨μ…˜μ˜ ν•œκ³„ 투λͺ…ν•˜κ²Œ 곡개
78
  7.2. κ°œμΈλ³„ 차이 쑴재 인정
79
  7.3. ν•„μš” 쑰건과 μ£Όμ˜μ‚¬ν•­ λͺ…μ‹œ
 
80
  관계 지속 단계
81
  8.1. μ§„μ •μ„± μžˆλŠ” 감사 인사
82
  8.2. λ‹€μŒ 컨텐츠 예고둜 κΈ°λŒ€κ° μ‘°μ„±
 
138
  "storytelling": "이야기λ₯Ό λ“€λ €μ£Όλ“― 감성적이고 λͺ°μž…감 μžˆλŠ” ν†€μœΌλ‘œ μž‘μ„±ν•΄ μ£Όμ„Έμš”. 인물, λ°°κ²½, κ°ˆλ“±, 해결과정이 λ‹΄κΈ΄ λ‚΄λŸ¬ν‹°λΈŒ ꡬ쑰λ₯Ό ν™œμš©ν•˜μ„Έμš”."
139
  }
140
 
141
+ # 검색 κ²°κ³Ό ν™œμš© μ§€μΉ¨ μΆ”κ°€
142
+ search_guide = """
143
+ 검색 κ²°κ³Ό ν™œμš© μ§€μΉ¨:
144
+ - 제곡된 검색 κ²°κ³Όμ—μ„œ 핡심 정보λ₯Ό μ •ν™•ν•˜κ²Œ μΆ”μΆœν•˜μ—¬ λ°˜μ˜ν•˜μ„Έμš”
145
+ - 데이터, 톡계, μ‚¬λ‘€λŠ” 검색 결과의 μ΅œμ‹  정보λ₯Ό ν™œμš©ν•˜μ„Έμš”
146
+ - 인용 μ‹œ 좜처λ₯Ό λ³Έλ¬Έ 내에 μ–ΈκΈ‰ν•˜μ„Έμš” (예: "OO μ‚¬μ΄νŠΈμ— λ”°λ₯΄λ©΄...")
147
+ - λΈ”λ‘œκ·Έ λ§ˆμ§€λ§‰μ— "μ°Έκ³  자료" μ„Ήμ…˜μ„ μΆ”κ°€ν•˜κ³  μ‚¬μš©ν•œ μ£Όμš” 좜처λ₯Ό 링크와 ν•¨κ»˜ λ‚˜μ—΄ν•˜μ„Έμš”
148
+ - μΆœμ²˜κ°€ μ—†λŠ” μ •λ³΄λŠ” "일반적으둜..." λ“±μ˜ ν‘œν˜„μ„ μ‚¬μš©ν•˜μ„Έμš”
149
+ - 검색 결과의 정보가 상좩할 경우, λ‹€μ–‘ν•œ 관점을 κ· ν˜•μžˆκ²Œ μ œμ‹œν•˜μ„Έμš”
150
+ """
151
+
152
  # μ΅œμ’… ν”„λ‘¬ν”„νŠΈ μ‘°ν•©
153
  final_prompt = base_prompt
154
 
 
160
  if tone in tone_guides:
161
  final_prompt += f"\n\nν†€μ•€λ§€λ„ˆ: {tone_guides[tone]}"
162
 
163
+ # 검색 κ²°κ³Ό ν™œμš© μ§€μΉ¨ μΆ”κ°€ (μ›Ή 검색 ν™œμ„±ν™” μ‹œ)
164
+ if include_search_results:
165
+ final_prompt += f"\n\n{search_guide}"
166
+
167
  # κΈ€μž 수 μ§€μΉ¨ μΆ”κ°€
168
  final_prompt += f"\n\nμž‘μ„± μ‹œ μ€€μˆ˜μ‚¬ν•­\n9.1. κΈ€μž 수: {word_count-250}-{word_count+250}자 λ‚΄μ™Έ\n9.2. 문단 길이: 3-4λ¬Έμž₯ 이내\n9.3. μ‹œκ°μ  ꡬ뢄: μ†Œμ œλͺ©, ꡬ뢄선, 번호 λͺ©λ‘ ν™œμš©\n9.4. 데이터: λͺ¨λ“  μ •λ³΄μ˜ 좜처 λͺ…μ‹œ\n9.5. 가독성: λͺ…ν™•ν•œ 단락 ꡬ뢄과 강쑰점 μ‚¬μš©"
169
 
 
236
  def do_web_search(query: str) -> str:
237
  """μ›Ή 검색 μˆ˜ν–‰ 및 κ²°κ³Ό ν¬λ§·νŒ…"""
238
  try:
239
+ arts = brave_search(query, 20)
240
  if not arts:
241
  logging.warning("검색 κ²°κ³Ό μ—†μŒ, λŒ€μ²΄ μ½˜ν…μΈ  μ‚¬μš©")
242
  return mock_results(query)
243
 
244
+ hdr = "# μ›Ή 검색 κ²°κ³Ό\n제곡된 검색 결과의 정보λ₯Ό μ •ν™•ν•˜κ²Œ λ°˜μ˜ν•˜μ—¬ 신뒰도 높은 λΈ”λ‘œκ·Έλ₯Ό μž‘μ„±ν•˜μ„Έμš”. 정보 인용 μ‹œ 좜처λ₯Ό λͺ…μ‹œν•˜κ³ , λΈ”λ‘œκ·Έ λ§ˆμ§€λ§‰μ— μ°Έκ³  자료 μ„Ήμ…˜μ„ μΆ”κ°€ν•˜μ„Έμš”.\n\n"
245
  body = "\n".join(
246
  f"### Result {a['index']}: {a['title']}\n\n{a['snippet']}\n\n"
247
  f"**좜처**: [{a['displayed_link']}]({a['link']})\n\n---\n"
 
405
  with st.chat_message("assistant"):
406
  placeholder = st.empty(); answer = ""
407
 
408
+ # μ›Ή 검색 ν™œμ„±ν™” μ—¬λΆ€ 확인
409
+ use_web_search = st.session_state.use_web_search
410
+
411
+ try:
412
+ # μ„ νƒλœ ν…œν”Œλ¦Ώ, 톀, 단어 수둜 μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ 생성
413
+ # μ›Ή 검색 ν™œμ„±ν™” μ‹œ 이λ₯Ό μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈμ— 반영
414
+ sys_prompt = get_system_prompt(
415
+ template=st.session_state.blog_template,
416
+ tone=st.session_state.blog_tone,
417
+ word_count=st.session_state.word_count,
418
+ include_search_results=use_web_search
419
+ )
420
+
421
+ search_results = None
422
+ if use_web_search:
423
+ with st.spinner("μ›Ή 검색 쀑…"):
424
+ search_results = do_web_search(keywords(prompt))
425
+
426
+ # λ©”μ‹œμ§€ λ°°μ—΄ μ€€λΉ„
427
+ messages = [{"role": m["role"], "content": m["content"]}
428
+ for m in st.session_state.messages]
429
+
430
+ # μ›Ή 검색 κ²°κ³Όκ°€ 있으면 별도 λ©”μ‹œμ§€λ‘œ μΆ”κ°€ (μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ λŒ€μ‹ )
431
+ if search_results:
432
+ messages.append({"role": "user", "content": search_results})
433
+
434
+ # Claude 슀트리밍
435
+ with client.messages.stream(
436
+ model=st.session_state.ai_model, max_tokens=MAX_TOKENS,
437
+ system=sys_prompt,
438
+ messages=messages
439
+ ) as stream:
440
+ for t in stream.text_stream:
441
+ answer += t or ""
442
+ placeholder.markdown(answer + "β–Œ")
443
+ placeholder.markdown(answer)
444
+
445
+ # 이미지 μ˜΅μ…˜
446
+ answer_entry_saved = False
447
+ if st.session_state.generate_image:
448
+ with st.spinner("이미지 생성 쀑…"):
449
+ ip = extract_image_prompt(answer, prompt)
450
+ img, cap = generate_image(ip)
451
+ if img:
452
+ st.image(img, caption=cap)
453
+ st.session_state.messages.append(
454
+ {"role": "assistant", "content": answer,
455
+ "image": img, "image_caption": cap})
456
+ answer_entry_saved = True
457
+ if not answer_entry_saved:
458
+ st.session_state.messages.append(
459
+ {"role": "assistant", "content": answer})
460
+
461
+ # λ³Έλ¬Έ λ‹€μš΄λ‘œλ“œ λ²„νŠΌ (MD / HTML)
462
+ st.subheader("이 λΈ”λ‘œκ·Έ λ‹€μš΄λ‘œλ“œ")
463
+ b1, b2 = st.columns(2)
464
+ b1.download_button("λ§ˆν¬λ‹€μš΄", answer,
465
+ file_name=f"{prompt[:30]}.md", mime="text/markdown")
466
+ b2.download_button("HTML", md_to_html(answer, prompt[:30]),
467
+ file_name=f"{prompt[:30]}.html", mime="text/html")
468
+
469
+ # ── μžλ™ λ°±μ—… μ €μž₯
470
+ if st.session_state.auto_save and st.session_state.messages:
471
+ try:
472
+ fn = f"chat_history_auto_{datetime.now():%Y%m%d_%H%M%S}.json"
473
+ with open(fn, "w", encoding="utf-8") as fp:
474
+ json.dump(st.session_state.messages, fp,
475
+ ensure_ascii=False, indent=2)
476
+ except Exception as e:
477
+ logging.error(f"μžλ™ μ €μž₯ μ‹€νŒ¨: {e}")
478
+
479
+ except anthropic.BadRequestError as e:
480
+ error_message = str(e)
481
+ if "credit balance is too low" in error_message:
482
+ placeholder.error("⚠️ API ν¬λ ˆλ”§ λΆ€μ‘±: Anthropic API 계정에 ν¬λ ˆλ”§μ„ μΆ©μ „ν•΄μ£Όμ„Έμš”.")
483
+ answer = "API ν¬λ ˆλ”§μ΄ λΆ€μ‘±ν•˜μ—¬ λΈ”λ‘œκ·Έλ₯Ό 생성할 수 μ—†μŠ΅λ‹ˆλ‹€. API 계정에 ν¬λ ˆλ”§μ„ μΆ©μ „ν•œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”."
484
+ else:
485
+ placeholder.error(f"API μš”μ²­ 였λ₯˜: {error_message}")
486
+ answer = f"API μš”μ²­ 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: {error_message}"
487
+
488
+ st.session_state.messages.append({"role": "assistant", "content": answer})
489
+
490
+ except Exception as e:
491
+ error_message = str(e)
492
+ placeholder.error(f"였λ₯˜ λ°œμƒ: {error_message}")
493
+ answer = f"μš”μ²­ 처리 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: {error_message}"
494
+ st.session_state.messages.append({"role": "assistant", "content": answer})
495
 
496
  # ──────────────────────────────── main / requirements ──────────────────────
497
  def main(): ginigen_app()