ginipick commited on
Commit
5eb15fa
Β·
verified Β·
1 Parent(s): 87fde17

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +179 -47
app.py CHANGED
@@ -1,5 +1,5 @@
1
  # ──────────────────────────────── Imports ────────────────────────────────
2
- import os, json, re, logging, requests, markdown
3
  from datetime import datetime
4
 
5
  import streamlit as st
@@ -14,6 +14,22 @@ BRAVE_ENDPOINT = "https://api.search.brave.com/res/v1/web/search"
14
  IMAGE_API_URL = "http://211.233.58.201:7896"
15
  MAX_TOKENS = 7_999
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  # ──────────────────────────────── λ‘œκΉ… ──────────────────────────────────────
18
  logging.basicConfig(level=logging.INFO,
19
  format="%(asctime)s - %(levelname)s - %(message)s")
@@ -22,8 +38,8 @@ logging.basicConfig(level=logging.INFO,
22
  client = anthropic.Anthropic(api_key=ANTHROPIC_KEY)
23
 
24
  # ──────────────────────────────── λΈ”λ‘œκ·Έ μž‘μ„± μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ ────────────────
25
- def get_system_prompt() -> str:
26
- return """
27
  당신은 μ „λ¬Έ λΈ”λ‘œκ·Έ μž‘μ„± μ „λ¬Έκ°€μž…λ‹ˆλ‹€. λͺ¨λ“  λΈ”λ‘œκ·Έ κΈ€ μž‘μ„± μš”μ²­μ— λŒ€ν•΄ λ‹€μŒμ˜ 8단계 ν”„λ ˆμž„μ›Œν¬λ₯Ό μ² μ €νžˆ λ”°λ₯΄λ˜, μžμ—°μŠ€λŸ½κ³  λ§€λ ₯적인 글이 λ˜λ„λ‘ μž‘μ„±ν•΄μ•Ό ν•©λ‹ˆλ‹€:
28
 
29
  λ…μž μ—°κ²° 단계
@@ -65,15 +81,78 @@ def get_system_prompt() -> str:
65
  8.1. μ§„μ •μ„± μžˆλŠ” 감사 인사
66
  8.2. λ‹€μŒ 컨텐츠 예고둜 κΈ°λŒ€κ° μ‘°μ„±
67
  8.3. μ†Œν†΅ 채널 μ•ˆλ‚΄
 
68
 
69
- μž‘μ„± μ‹œ μ€€μˆ˜μ‚¬ν•­
70
- 9.1. κΈ€μž 수: 1500-2000자 λ‚΄μ™Έ
71
- 9.2. 문단 길이: 3-4λ¬Έμž₯ 이내
72
- 9.3. μ‹œκ°μ  ꡬ뢄: μ†Œμ œλͺ©, ꡬ뢄선, 번호 λͺ©λ‘ ν™œμš©
73
- 9.4. ν†€μ•€λ§€λ„ˆ: μΉœκ·Όν•˜κ³  전문적인 λŒ€ν™”μ²΄
74
- 9.5. 데이터: λͺ¨λ“  μ •λ³΄μ˜ 좜처 λͺ…μ‹œ
75
- 9.6. 가독성: λͺ…ν™•ν•œ 단락 ꡬ뢄과 강쑰점 μ‚¬μš©
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
  # ──────────────────────────────── Brave Search API ─────────────────────────
79
  def brave_search(query: str, count: int = 5):
@@ -90,48 +169,74 @@ def brave_search(query: str, count: int = 5):
90
  "X-Subscription-Token": BRAVE_KEY
91
  }
92
  params = {"q": query, "count": str(count)}
93
- r = requests.get(BRAVE_ENDPOINT, headers=headers, params=params, timeout=15)
94
- r.raise_for_status()
95
- data = r.json()
96
-
97
- raw = data.get("web", {}).get("results") or data.get("results", [])
98
- arts = []
99
- for i, res in enumerate(raw[:count], 1):
100
- url = res.get("url", res.get("link", ""))
101
- host = re.sub(r"https?://(www\.)?", "", url).split("/")[0]
102
- arts.append({
103
- "index": i,
104
- "title": res.get("title", "제λͺ© μ—†μŒ"),
105
- "link": url,
106
- "snippet": res.get("description", res.get("text", "λ‚΄μš© μ—†μŒ")),
107
- "displayed_link": host
108
- })
109
- return arts
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
  def mock_results(query: str) -> str:
 
112
  ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
113
- return (f"# 가상 검색 κ²°κ³Ό (생성: {ts})\n\n"
114
- f"### Result 1: {query} κ΄€λ ¨ μ˜ˆμ‹œ κ²°κ³Ό\n\n"
115
- "API 호좜 μ‹€νŒ¨λ‘œ μƒμ„±λœ μž„μ‹œ λ°μ΄ν„°μž…λ‹ˆλ‹€.\n\n"
116
- "**좜처**: [example.com](https://example.com)\n\n---\n")
 
 
 
 
117
 
118
  def do_web_search(query: str) -> str:
 
119
  try:
120
  arts = brave_search(query, 5)
 
 
 
 
 
 
 
 
 
 
 
121
  except Exception as e:
122
- logging.error(f"Brave 검색 μ‹€νŒ¨: {e}")
123
- return mock_results(query)
124
- if not arts:
125
  return mock_results(query)
126
 
127
- hdr = "# μ›Ή 검색 κ²°κ³Ό\nμ•„λž˜ 정보λ₯Ό μ°Έκ³ ν•΄μ„œ λ‹΅λ³€ν•˜μ„Έμš”.\n\n"
128
- body = "\n".join(
129
- f"### Result {a['index']}: {a['title']}\n\n{a['snippet']}\n\n"
130
- f"**좜처**: [{a['displayed_link']}]({a['link']})\n\n---\n"
131
- for a in arts
132
- )
133
- return hdr + body
134
-
135
  # ──────────────────────────────── 이미지 Β· λ³€ν™˜ μœ ν‹Έ ────────────────────────
136
  def generate_image(prompt, w=768, h=768, g=3.5, steps=30, seed=3):
137
  if not prompt: return None, "ν”„λ‘¬ν”„νŠΈ λΆ€μ‘±"
@@ -174,17 +279,38 @@ def ginigen_app():
174
  messages=[],
175
  auto_save=True,
176
  generate_image=False,
177
- use_web_search=False
 
 
 
178
  )
179
  for k, v in defaults.items():
180
  st.session_state.setdefault(k, v)
181
 
182
  # ── μ‚¬μ΄λ“œλ°” 컨트둀
183
  sb = st.sidebar
184
- sb.title("λŒ€ν™” 기둝 관리")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  sb.toggle("μžλ™ μ €μž₯", key="auto_save")
186
  sb.toggle("이미지 μžλ™ 생성", key="generate_image")
187
- sb.toggle("μ›Ή 검색 μ‚¬μš©", key="use_web_search")
 
 
 
 
188
 
189
  # ── 졜근 λΈ”λ‘œκ·Έ λ‹€μš΄λ‘œλ“œ (λ§ˆν¬λ‹€μš΄ / HTML)
190
  latest_blog = next(
@@ -231,7 +357,13 @@ def ginigen_app():
231
 
232
  with st.chat_message("assistant"):
233
  placeholder = st.empty(); answer = ""
234
- sys_prompt = get_system_prompt()
 
 
 
 
 
 
235
 
236
  if st.session_state.use_web_search:
237
  with st.spinner("μ›Ή 검색 쀑…"):
@@ -297,4 +429,4 @@ if __name__ == "__main__":
297
  "markdown>=3.5.1",
298
  "pillow>=10.1.0"
299
  ]))
300
- main()
 
1
  # ──────────────────────────────── Imports ────────────────────────────────
2
+ import os, json, re, logging, requests, markdown, time
3
  from datetime import datetime
4
 
5
  import streamlit as st
 
14
  IMAGE_API_URL = "http://211.233.58.201:7896"
15
  MAX_TOKENS = 7_999
16
 
17
+ # λΈ”λ‘œκ·Έ ν…œν”Œλ¦Ώ 및 μŠ€νƒ€μΌ μ •μ˜
18
+ BLOG_TEMPLATES = {
19
+ "standard": "μ „λ¬Έ λΈ”λ‘œκ·Έ μž‘μ„± μ „λ¬Έκ°€λ‘œμ„œ 8단계 ν”„λ ˆμž„μ›Œν¬λ₯Ό 따라 μžμ—°μŠ€λŸ½κ³  λ§€λ ₯적인 κΈ€ μž‘μ„±",
20
+ "tutorial": "단계별 νŠœν† λ¦¬μ–Ό ν˜•μ‹μœΌλ‘œ, λͺ…ν™•ν•œ κ³Όμ •κ³Ό κ²°κ³Όλ₯Ό λ³΄μ—¬μ£ΌλŠ” κ°€μ΄λ“œ μž‘μ„±",
21
+ "review": "μ œν’ˆ/μ„œλΉ„μŠ€ 뢄석 μ€‘μ‹¬μ˜ 리뷰 ν˜•μ‹, μž₯단점 뢄석과 μΆ”μ²œ 포함",
22
+ "storytelling": "개인 κ²½ν—˜μ΄λ‚˜ 사둀λ₯Ό μ€‘μ‹¬μœΌλ‘œ ν•œ μŠ€ν† λ¦¬ν…”λ§ ν˜•μ‹μ˜ λΈ”λ‘œκ·Έ μž‘μ„±",
23
+ "seo_optimized": "검색엔진 μ΅œμ ν™”(SEO)λ₯Ό κ³ λ €ν•œ ν‚€μ›Œλ“œ 쀑심 λΈ”λ‘œκ·Έ μž‘μ„±"
24
+ }
25
+
26
+ BLOG_TONES = {
27
+ "professional": "전문적이고 곡식적인 μ–΄μ‘°λ‘œ μž‘μ„±",
28
+ "casual": "μΉœκ·Όν•˜κ³  λŒ€ν™”μ²΄ μ€‘μ‹¬μ˜ νŽΈμ•ˆν•œ ν†€μœΌλ‘œ μž‘μ„±",
29
+ "humorous": "μœ λ¨Έμ™€ 재치λ₯Ό κ°€λ―Έν•œ κ°€λ²Όμš΄ μ–΄μ‘°λ‘œ μž‘μ„±",
30
+ "storytelling": "이야기λ₯Ό λ“€λ €μ£Όλ“― 감성적이고 λͺ°μž…감 μžˆλŠ” ν†€μœΌλ‘œ μž‘μ„±"
31
+ }
32
+
33
  # ──────────────────────────────── λ‘œκΉ… ──────────────────────────────────────
34
  logging.basicConfig(level=logging.INFO,
35
  format="%(asctime)s - %(levelname)s - %(message)s")
 
38
  client = anthropic.Anthropic(api_key=ANTHROPIC_KEY)
39
 
40
  # ──────────────────────────────── λΈ”λ‘œκ·Έ μž‘μ„± μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ ────────────────
41
+ def get_system_prompt(template="standard", tone="professional", word_count=1750) -> str:
42
+ base_prompt = """
43
  당신은 μ „λ¬Έ λΈ”λ‘œκ·Έ μž‘μ„± μ „λ¬Έκ°€μž…λ‹ˆλ‹€. λͺ¨λ“  λΈ”λ‘œκ·Έ κΈ€ μž‘μ„± μš”μ²­μ— λŒ€ν•΄ λ‹€μŒμ˜ 8단계 ν”„λ ˆμž„μ›Œν¬λ₯Ό μ² μ €νžˆ λ”°λ₯΄λ˜, μžμ—°μŠ€λŸ½κ³  λ§€λ ₯적인 글이 λ˜λ„λ‘ μž‘μ„±ν•΄μ•Ό ν•©λ‹ˆλ‹€:
44
 
45
  λ…μž μ—°κ²° 단계
 
81
  8.1. μ§„μ •μ„± μžˆλŠ” 감사 인사
82
  8.2. λ‹€μŒ 컨텐츠 예고둜 κΈ°λŒ€κ° μ‘°μ„±
83
  8.3. μ†Œν†΅ 채널 μ•ˆλ‚΄
84
+ """
85
 
86
+ # ν…œν”Œλ¦Ώλ³„ μΆ”κ°€ μ§€μΉ¨
87
+ template_guides = {
88
+ "tutorial": """
89
+ 이 λΈ”λ‘œκ·ΈλŠ” νŠœν† λ¦¬μ–Ό ν˜•μ‹μœΌλ‘œ μž‘μ„±ν•΄ μ£Όμ„Έμš”:
90
+ - λͺ…ν™•ν•œ λͺ©ν‘œμ™€ μ΅œμ’… κ²°κ³Όλ¬Ό λ¨Όμ € μ œμ‹œ
91
+ - λ‹¨κ³„λ³„λ‘œ λͺ…ν™•ν•˜κ²Œ κ΅¬λΆ„λœ κ³Όμ • μ„€λͺ…
92
+ - 각 λ‹¨κ³„λ§ˆλ‹€ 이미지λ₯Ό μ‚½μž…ν•  μœ„μΉ˜ ν‘œμ‹œ
93
+ - μ˜ˆμƒ μ†Œμš” μ‹œκ°„κ³Ό λ‚œμ΄λ„ λͺ…μ‹œ
94
+ - ν•„μš”ν•œ λ„κ΅¬λ‚˜ 사전 지식 μ•ˆλ‚΄
95
+ - λ¬Έμ œν•΄κ²° 팁과 자주 λ°œμƒν•˜λŠ” μ‹€μˆ˜ 포함
96
+ - μ™„λ£Œ ν›„ λ‹€μŒ λ‹¨κ³„λ‚˜ μ‘μš©λ²• μ œμ•ˆ
97
+ """,
98
+
99
+ "review": """
100
+ 이 λΈ”λ‘œκ·ΈλŠ” 리뷰 ν˜•μ‹μœΌλ‘œ μž‘μ„±ν•΄ μ£Όμ„Έμš”:
101
+ - 객관적 사싀과 주관적 평가 ꡬ뢄
102
+ - λͺ…ν™•ν•œ 평가 κΈ°μ€€ μ œμ‹œ
103
+ - μž₯점과 단점 κ· ν˜•μžˆκ²Œ μ„œμˆ 
104
+ - μœ μ‚¬ μ œν’ˆ/μ„œλΉ„μŠ€μ™€ 비ꡐ
105
+ - λˆ„κ΅¬μ—κ²Œ μ ν•©ν•œμ§€ νƒ€κ²Ÿ μ„€λͺ…
106
+ - ꡬ체적인 μ‚¬μš© κ²½ν—˜κ³Ό κ²°κ³Ό 포함
107
+ - μ΅œμ’… μΆ”μ²œ 여뢀와 λŒ€μ•ˆ μ œμ‹œ
108
+ """,
109
+
110
+ "storytelling": """
111
+ 이 λΈ”λ‘œκ·ΈλŠ” μŠ€ν† λ¦¬ν…”λ§ ν˜•μ‹μœΌλ‘œ μž‘μ„±ν•΄ μ£Όμ„Έμš”:
112
+ - μ‹€μ œ μΈλ¬Όμ΄λ‚˜ μ‚¬λ‘€λ‘œ μ‹œμž‘
113
+ - 문제 상황과 감정적 μ—°κ²° κ°•ν™”
114
+ - κ°ˆλ“±κ³Ό ν•΄κ²°κ³Όμ • μ€‘μ‹¬μ˜ λ‚΄λŸ¬ν‹°λΈŒ
115
+ - κ΅ν›ˆκ³Ό 배움을 μžμ—°μŠ€λŸ½κ²Œ 포함
116
+ - λ…μžκ°€ 곡감할 수 μžˆλŠ” 감정선 μœ μ§€
117
+ - 이야기와 μœ μš©ν•œ μ •λ³΄μ˜ κ· ν˜• μœ μ§€
118
+ - λ…μžμ—κ²Œ μžμ‹ μ˜ 이야기λ₯Ό μƒκ°ν•΄λ³΄κ²Œ μœ λ„
119
+ """,
120
+
121
+ "seo_optimized": """
122
+ 이 λΈ”λ‘œκ·ΈλŠ” SEO μ΅œμ ν™” ν˜•μ‹μœΌλ‘œ μž‘μ„±ν•΄ μ£Όμ„Έμš”:
123
+ - 핡심 ν‚€μ›Œλ“œλ₯Ό 제λͺ©, μ†Œμ œλͺ©, 첫 단락에 배치
124
+ - κ΄€λ ¨ ν‚€μ›Œλ“œλ₯Ό μžμ—°μŠ€λŸ½κ²Œ 본문에 λΆ„μ‚°
125
+ - 300-500자 λΆ„λŸ‰μ˜ λͺ…ν™•ν•œ 단락 ꡬ성
126
+ - 질문 ν˜•μ‹μ˜ μ†Œμ œλͺ© ν™œμš©
127
+ - λͺ©λ‘, ν‘œ, κ°•μ‘° ν…μŠ€νŠΈ λ“± λ‹€μ–‘ν•œ μ„œμ‹ ν™œμš©
128
+ - λ‚΄λΆ€ 링크 μ‚½μž… μœ„μΉ˜ ν‘œμ‹œ
129
+ - 2000-3000자 μ΄μƒμ˜ μΆ©λΆ„ν•œ μ½˜ν…μΈ  제곡
130
  """
131
+ }
132
+
133
+ # 톀별 μΆ”κ°€ μ§€μΉ¨
134
+ tone_guides = {
135
+ "professional": "전문적이고 κΆŒμœ„μžˆλŠ” μ–΄μ‘°λ‘œ μž‘μ„±ν•˜λ˜, μ „λ¬Έ μš©μ–΄λŠ” 적절히 μ„€λͺ…ν•΄ μ£Όμ„Έμš”. 데이터와 연ꡬ κ²°κ³Όλ₯Ό οΏ½οΏ½οΏ½μ‹¬μœΌλ‘œ 논리적 흐름을 μœ μ§€ν•˜μ„Έμš”.",
136
+ "casual": "μΉœκ·Όν•˜κ³  λŒ€ν™”ν•˜λ“― νŽΈμ•ˆν•œ μ–΄μ‘°λ‘œ μž‘μ„±ν•΄ μ£Όμ„Έμš”. '~λ„€μš”', '~ν•΄μš”' 같은 λŒ€ν™”μ²΄λ₯Ό μ‚¬μš©ν•˜κ³ , 개인적 κ²½ν—˜κ³Ό λΉ„μœ λ₯Ό 톡해 λ‚΄μš©μ„ μ „λ‹¬ν•˜μ„Έμš”.",
137
+ "humorous": "μœ λ¨Έμ™€ μž¬μΉ˜μžˆλŠ” ν‘œν˜„μ„ 적절히 ν™œμš©ν•΄ μ£Όμ„Έμš”. μž¬λ―ΈμžˆλŠ” λΉ„μœ λ‚˜ μ˜ˆμ‹œ, κ°€λ²Όμš΄ 농담을 ν¬ν•¨ν•˜λ˜, μ •λ³΄μ˜ μ •ν™•μ„±κ³Ό μœ μš©μ„±μ€ μœ μ§€ν•˜μ„Έμš”.",
138
+ "storytelling": "이야기λ₯Ό λ“€λ €μ£Όλ“― 감성적이고 λͺ°μž…감 μžˆλŠ” ν†€μœΌλ‘œ μž‘μ„±ν•΄ μ£Όμ„Έμš”. 인물, λ°°κ²½, κ°ˆλ“±, 해결과정이 λ‹΄κΈ΄ λ‚΄λŸ¬ν‹°λΈŒ ꡬ쑰λ₯Ό ν™œμš©ν•˜μ„Έμš”."
139
+ }
140
+
141
+ # μ΅œμ’… ν”„λ‘¬ν”„νŠΈ μ‘°ν•©
142
+ final_prompt = base_prompt
143
+
144
+ # μ„ νƒλœ ν…œν”Œλ¦Ώ μ§€μΉ¨ μΆ”κ°€
145
+ if template in template_guides:
146
+ final_prompt += "\n" + template_guides[template]
147
+
148
+ # μ„ νƒλœ 톀 μ§€μΉ¨ μΆ”κ°€
149
+ if tone in tone_guides:
150
+ final_prompt += f"\n\nν†€μ•€λ§€λ„ˆ: {tone_guides[tone]}"
151
+
152
+ # κΈ€μž 수 μ§€μΉ¨ μΆ”κ°€
153
+ final_prompt += f"\n\nμž‘μ„± μ‹œ μ€€μˆ˜μ‚¬ν•­\n9.1. κΈ€μž 수: {word_count-250}-{word_count+250}자 λ‚΄μ™Έ\n9.2. 문단 길이: 3-4λ¬Έμž₯ 이내\n9.3. μ‹œκ°μ  ꡬ뢄: μ†Œμ œλͺ©, ꡬ뢄선, 번호 λͺ©λ‘ ν™œμš©\n9.4. 데이터: λͺ¨λ“  μ •λ³΄μ˜ 좜처 λͺ…μ‹œ\n9.5. 가독성: λͺ…ν™•ν•œ 단락 ꡬ뢄과 강쑰점 μ‚¬μš©"
154
+
155
+ return final_prompt
156
 
157
  # ──────────────────────────────── Brave Search API ─────────────────────────
158
  def brave_search(query: str, count: int = 5):
 
169
  "X-Subscription-Token": BRAVE_KEY
170
  }
171
  params = {"q": query, "count": str(count)}
172
+
173
+ for attempt in range(3): # μ΅œλŒ€ 3번 μž¬μ‹œλ„
174
+ try:
175
+ r = requests.get(BRAVE_ENDPOINT, headers=headers, params=params, timeout=15)
176
+ r.raise_for_status()
177
+ data = r.json()
178
+
179
+ # κ²°κ³Ό ν˜•μ‹ 확인 및 λ‘œκΉ…
180
+ logging.info(f"Brave 검색 κ²°κ³Ό 데이터 ꡬ쑰: {list(data.keys())}")
181
+
182
+ raw = data.get("web", {}).get("results") or data.get("results", [])
183
+ if not raw:
184
+ logging.warning(f"Brave 검색 κ²°κ³Ό μ—†μŒ. 응닡: {data}")
185
+ raise ValueError("검색 κ²°κ³Όκ°€ μ—†μŠ΅λ‹ˆλ‹€")
186
+
187
+ arts = []
188
+ for i, res in enumerate(raw[:count], 1):
189
+ url = res.get("url", res.get("link", ""))
190
+ host = re.sub(r"https?://(www\.)?", "", url).split("/")[0]
191
+ arts.append({
192
+ "index": i,
193
+ "title": res.get("title", "제λͺ© μ—†μŒ"),
194
+ "link": url,
195
+ "snippet": res.get("description", res.get("text", "λ‚΄μš© μ—†μŒ")),
196
+ "displayed_link": host
197
+ })
198
+
199
+ logging.info(f"Brave 검색 성곡: {len(arts)}개 κ²°κ³Ό")
200
+ return arts
201
+
202
+ except Exception as e:
203
+ logging.error(f"Brave 검색 μ‹€νŒ¨ (μ‹œλ„ {attempt+1}/3): {e}")
204
+ if attempt < 2: # λ§ˆμ§€λ§‰ μ‹œλ„κ°€ μ•„λ‹ˆλ©΄ λŒ€κΈ° ν›„ μž¬μ‹œλ„
205
+ time.sleep(2)
206
+
207
+ return [] # λͺ¨λ“  μ‹œλ„ μ‹€νŒ¨ μ‹œ 빈 λͺ©λ‘ λ°˜ν™˜
208
 
209
  def mock_results(query: str) -> str:
210
+ """검색 API μ‹€νŒ¨ μ‹œ 가상 검색 κ²°κ³Ό 제곡"""
211
  ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
212
+ return (f"# 검색 κ²°κ³Ό λŒ€μ²΄ λ‚΄μš© (생성: {ts})\n\n"
213
+ f"검색 API 호좜이 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€. 주제 '{query}'에 λŒ€ν•΄ κΈ°μ‘΄ 지식을 ν™œμš©ν•΄ λ‹΅λ³€ν•΄ μ£Όμ„Έμš”.\n\n"
214
+ f"λ‹€μŒ λ‚΄μš©μ΄ 도움이 될 수 μžˆμŠ΅λ‹ˆλ‹€:\n\n"
215
+ f"- {query}의 κΈ°λ³Έ κ°œλ…κ³Ό μ€‘μš”μ„±\n"
216
+ f"- 일반적으둜 μ•Œλ €μ§„ κ΄€λ ¨ 톡계와 νŠΈλ Œλ“œ\n"
217
+ f"- ν•΄λ‹Ή μ£Όμ œμ— λŒ€ν•œ μ „λ¬Έκ°€λ“€μ˜ 일반적인 견해\n"
218
+ f"- λ…μžλ“€μ΄ μ‹€μ œλ‘œ κΆκΈˆν•΄ν•  λ§Œν•œ μ§ˆλ¬Έλ“€\n\n"
219
+ f"μ°Έκ³ : 이 λ‚΄μš©μ€ μ‹€μ‹œκ°„ 검색 κ²°κ³Όκ°€ μ•„λ‹Œ λŒ€μ²΄ μ•ˆλ‚΄μž…λ‹ˆλ‹€.\n\n")
220
 
221
  def do_web_search(query: str) -> str:
222
+ """μ›Ή 검색 μˆ˜ν–‰ 및 κ²°κ³Ό ν¬λ§·νŒ…"""
223
  try:
224
  arts = brave_search(query, 5)
225
+ if not arts:
226
+ logging.warning("검색 κ²°κ³Ό μ—†μŒ, λŒ€μ²΄ μ½˜ν…μΈ  μ‚¬μš©")
227
+ return mock_results(query)
228
+
229
+ hdr = "# μ›Ή 검색 κ²°κ³Ό\nμ•„λž˜ 정보λ₯Ό μ°Έκ³ ν•΄μ„œ λ‹΅λ³€ν•˜μ„Έμš”.\n\n"
230
+ body = "\n".join(
231
+ f"### Result {a['index']}: {a['title']}\n\n{a['snippet']}\n\n"
232
+ f"**좜처**: [{a['displayed_link']}]({a['link']})\n\n---\n"
233
+ for a in arts
234
+ )
235
+ return hdr + body
236
  except Exception as e:
237
+ logging.error(f"μ›Ή 검색 전체 ν”„λ‘œμ„ΈμŠ€ μ‹€νŒ¨: {str(e)}")
 
 
238
  return mock_results(query)
239
 
 
 
 
 
 
 
 
 
240
  # ──────────────────────────────── 이미지 Β· λ³€ν™˜ μœ ν‹Έ ────────────────────────
241
  def generate_image(prompt, w=768, h=768, g=3.5, steps=30, seed=3):
242
  if not prompt: return None, "ν”„λ‘¬ν”„νŠΈ λΆ€μ‘±"
 
279
  messages=[],
280
  auto_save=True,
281
  generate_image=False,
282
+ use_web_search=False,
283
+ blog_template="standard",
284
+ blog_tone="professional",
285
+ word_count=1750
286
  )
287
  for k, v in defaults.items():
288
  st.session_state.setdefault(k, v)
289
 
290
  # ── μ‚¬μ΄λ“œλ°” 컨트둀
291
  sb = st.sidebar
292
+ sb.title("λΈ”λ‘œκ·Έ μ„€μ •")
293
+
294
+ # λΈ”λ‘œκ·Έ ν…œν”Œλ¦Ώ 및 μŠ€νƒ€μΌ 선택
295
+ sb.subheader("λΈ”λ‘œκ·Έ μŠ€νƒ€μΌ μ„€μ •")
296
+ sb.selectbox("λΈ”λ‘œκ·Έ ν…œν”Œλ¦Ώ", options=list(BLOG_TEMPLATES.keys()),
297
+ format_func=lambda x: x.replace("_", " ").title(),
298
+ key="blog_template")
299
+
300
+ sb.selectbox("λΈ”λ‘œκ·Έ 톀", options=list(BLOG_TONES.keys()),
301
+ format_func=lambda x: x.replace("_", " ").title(),
302
+ key="blog_tone")
303
+
304
+ sb.slider("λΈ”λ‘œκ·Έ 길이 (단어 수)", 800, 3000, 1750, key="word_count")
305
+
306
+ sb.subheader("기타 μ„€μ •")
307
  sb.toggle("μžλ™ μ €μž₯", key="auto_save")
308
  sb.toggle("이미지 μžλ™ 생성", key="generate_image")
309
+
310
+ # μ›Ή 검색 ν† κΈ€ (λͺ¨λ‹ˆν„°λ§μ„ μœ„ν•΄ μœ μ§€ν•˜λ˜ 기본값은 False)
311
+ search_enabled = sb.toggle("μ›Ή 검색 μ‚¬μš©", value=False, key="use_web_search")
312
+ if search_enabled:
313
+ st.warning("⚠️ μ›Ή 검색 κΈ°λŠ₯은 ν˜„μž¬ λΆˆμ•ˆμ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 검색 κ²°κ³Όκ°€ μ—†μœΌλ©΄ κΈ°λ³Έ μ§€μ‹μœΌλ‘œ λŒ€μ²΄λ©λ‹ˆλ‹€.")
314
 
315
  # ── 졜근 λΈ”λ‘œκ·Έ λ‹€μš΄λ‘œλ“œ (λ§ˆν¬λ‹€μš΄ / HTML)
316
  latest_blog = next(
 
357
 
358
  with st.chat_message("assistant"):
359
  placeholder = st.empty(); answer = ""
360
+
361
+ # μ„ νƒλœ ν…œν”Œλ¦Ώ, 톀, 단어 수둜 μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ 생성
362
+ sys_prompt = get_system_prompt(
363
+ template=st.session_state.blog_template,
364
+ tone=st.session_state.blog_tone,
365
+ word_count=st.session_state.word_count
366
+ )
367
 
368
  if st.session_state.use_web_search:
369
  with st.spinner("μ›Ή 검색 쀑…"):
 
429
  "markdown>=3.5.1",
430
  "pillow>=10.1.0"
431
  ]))
432
+ main()