Spaces:
Running
Running
Update app-BACKUP2.py
Browse files- app-BACKUP2.py +378 -398
app-BACKUP2.py
CHANGED
@@ -1,452 +1,432 @@
|
|
1 |
-
|
|
|
|
|
|
|
2 |
import streamlit as st
|
3 |
-
import json
|
4 |
import anthropic
|
5 |
-
import requests
|
6 |
-
import logging
|
7 |
from gradio_client import Client
|
8 |
-
import
|
9 |
-
import tempfile
|
10 |
-
import base64
|
11 |
-
from weasyprint import HTML
|
12 |
|
13 |
-
#
|
14 |
-
|
15 |
-
|
16 |
-
|
|
|
|
|
17 |
|
18 |
-
#
|
19 |
-
|
20 |
-
|
|
|
|
|
|
|
|
|
|
|
21 |
|
22 |
-
|
23 |
-
|
|
|
|
|
|
|
|
|
24 |
|
25 |
-
#
|
26 |
-
|
|
|
27 |
|
28 |
-
|
29 |
-
|
30 |
-
λΉμ μ μ λ¬Έ λΈλ‘κ·Έ μμ± μ λ¬Έκ°μ
λλ€. λͺ¨λ λΈλ‘κ·Έ κΈ μμ± μμ²μ λν΄ λ€μμ 8λ¨κ³ νλ μμν¬λ₯Ό μ² μ ν λ°λ₯΄λ, μμ°μ€λ½κ³ λ§€λ ₯μ μΈ κΈμ΄ λλλ‘ μμ±ν΄μΌ ν©λλ€:
|
31 |
|
32 |
-
|
|
|
|
|
|
|
33 |
|
34 |
-
|
|
|
|
|
|
|
35 |
|
36 |
-
|
|
|
|
|
|
|
37 |
|
38 |
-
|
|
|
|
|
|
|
39 |
|
40 |
-
|
|
|
|
|
|
|
41 |
|
42 |
-
|
|
|
|
|
|
|
43 |
|
44 |
-
|
|
|
|
|
|
|
45 |
|
46 |
-
|
|
|
|
|
|
|
47 |
|
48 |
-
|
|
|
|
|
|
|
|
|
49 |
|
50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
|
|
|
|
66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
67 |
try:
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
image2image_strength=0.8,
|
79 |
-
resize_img=True,
|
80 |
-
api_name="/generate_image"
|
81 |
)
|
82 |
-
|
83 |
-
return result[0], f"μ¬μ©λ μλ: {result[1]}"
|
84 |
except Exception as e:
|
85 |
-
logging.error(f"
|
86 |
-
return
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
λ€μμ '{blog_topic}'μ κ΄ν λΈλ‘κ·Έ κΈμ
λλ€. μ΄ λΈλ‘κ·Έ κΈμ λ΄μ©μ κΈ°λ°μΌλ‘ μ μ ν μ΄λ―Έμ§λ₯Ό μμ±νκΈ° μν
|
92 |
-
ν둬ννΈλ₯Ό μμ±ν΄μ£ΌμΈμ. ν둬ννΈλ μμ΄λ‘ μμ±νκ³ , ꡬ체μ μΈ μκ°μ μμλ₯Ό λ΄μμΌ ν©λλ€.
|
93 |
-
ν둬ννΈλ§ λ°ννμΈμ(λ€λ₯Έ μ€λͺ
μμ΄).
|
94 |
-
|
95 |
-
μμ νμ:
|
96 |
-
"A professional photo of [subject], [specific details], [atmosphere], [lighting], [perspective], high quality, detailed"
|
97 |
-
"""
|
98 |
-
|
99 |
try:
|
100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
101 |
model="claude-3-7-sonnet-20250219",
|
102 |
-
max_tokens=
|
103 |
-
|
104 |
-
messages=[{"role": "user", "content": blog_content}]
|
105 |
)
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
logging.info(f"μμ±λ μ΄λ―Έμ§ ν둬ννΈ: {image_prompt}")
|
110 |
-
return image_prompt
|
111 |
-
except Exception as e:
|
112 |
-
logging.error(f"μ΄λ―Έμ§ ν둬ννΈ μμ± μ€λ₯: {e}")
|
113 |
-
return f"A professional photo related to {blog_topic}, detailed, high quality"
|
114 |
-
|
115 |
-
# λ§ν¬λ€μ΄μ HTMLλ‘ λ³ννλ ν¨μ
|
116 |
-
def convert_md_to_html(md_text, title="Ginigen Blog"):
|
117 |
-
html_content = markdown.markdown(md_text)
|
118 |
-
html_doc = f"""
|
119 |
-
<!DOCTYPE html>
|
120 |
-
<html>
|
121 |
-
<head>
|
122 |
-
<title>{title}</title>
|
123 |
-
<meta charset="utf-8">
|
124 |
-
<style>
|
125 |
-
body {{ font-family: Arial, sans-serif; line-height: 1.6; max-width: 800px; margin: 0 auto; padding: 20px; }}
|
126 |
-
h1 {{ color: #2c3e50; font-size: 2.5em; margin-bottom: 20px; }}
|
127 |
-
h2 {{ color: #3498db; margin-top: 25px; font-size: 1.8em; }}
|
128 |
-
h3 {{ color: #2980b9; font-size: 1.5em; }}
|
129 |
-
p {{ margin-bottom: 15px; font-size: 1.1em; }}
|
130 |
-
blockquote {{ background: #f9f9f9; border-left: 10px solid #ccc; margin: 1.5em 10px; padding: 1em 10px; }}
|
131 |
-
ul, ol {{ margin-bottom: 15px; }}
|
132 |
-
li {{ margin-bottom: 5px; }}
|
133 |
-
hr {{ border: 0; height: 1px; background: #ddd; margin: 20px 0; }}
|
134 |
-
img {{ max-width: 100%; height: auto; display: block; margin: 20px auto; }}
|
135 |
-
</style>
|
136 |
-
</head>
|
137 |
-
<body>
|
138 |
-
{html_content}
|
139 |
-
</body>
|
140 |
-
</html>
|
141 |
-
"""
|
142 |
-
return html_doc
|
143 |
|
144 |
-
|
145 |
-
|
146 |
-
html_content = convert_md_to_html(md_text, title)
|
147 |
-
|
148 |
-
# μμ νμΌ μμ±
|
149 |
-
with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as tmp:
|
150 |
-
tmp_path = tmp.name
|
151 |
-
|
152 |
-
# HTMLμ PDFλ‘ λ³ν
|
153 |
-
HTML(string=html_content).write_pdf(tmp_path)
|
154 |
-
|
155 |
-
# μμ±λ PDF μ½κΈ°
|
156 |
-
with open(tmp_path, 'rb') as f:
|
157 |
-
pdf_data = f.read()
|
158 |
-
|
159 |
-
# μμ νμΌ μμ
|
160 |
-
os.unlink(tmp_path)
|
161 |
-
|
162 |
-
return pdf_data
|
163 |
|
164 |
-
|
165 |
-
|
166 |
-
b64 = base64.b64encode(bin_data).decode()
|
167 |
-
href = f'<a href="data:application/octet-stream;base64,{b64}" download="{download_filename}">{link_text}</a>'
|
168 |
-
return href
|
169 |
|
170 |
-
|
|
|
171 |
st.title("Ginigen Blog")
|
172 |
-
|
173 |
-
# λͺ¨λΈ κ³ μ οΏ½οΏ½μ
|
174 |
-
if "ai_model" not in st.session_state:
|
175 |
-
st.session_state["ai_model"] = "claude-3-7-sonnet-20250219"
|
176 |
-
|
177 |
-
# μΈμ
μν μ΄κΈ°ν
|
178 |
-
if "messages" not in st.session_state:
|
179 |
-
st.session_state.messages = []
|
180 |
-
|
181 |
-
# μλ μ μ₯ κΈ°λ₯
|
182 |
-
if "auto_save" not in st.session_state:
|
183 |
-
st.session_state.auto_save = True
|
184 |
-
|
185 |
-
# μ΄λ―Έμ§ μμ± ν κΈ
|
186 |
-
if "generate_image" not in st.session_state:
|
187 |
-
st.session_state.generate_image = False
|
188 |
-
|
189 |
-
# μ΄λ―Έμ§ API μν
|
190 |
-
if "image_api_status" not in st.session_state:
|
191 |
-
st.session_state.image_api_status = test_image_api_connection()
|
192 |
|
193 |
-
#
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
204 |
|
205 |
-
#
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
guidance = st.sidebar.slider("κ°μ΄λμ€ μ€μΌμΌ", 1.0, 20.0, 3.5, 0.1)
|
211 |
-
inference_steps = st.sidebar.slider("μΈνΌλ°μ€ μ€ν
", 1, 50, 30, 1)
|
212 |
-
seed = st.sidebar.number_input("μλ", value=3, min_value=0, step=1)
|
213 |
-
else:
|
214 |
-
# κΈ°λ³Έκ° μ€μ
|
215 |
-
width, height, guidance, inference_steps, seed = 768, 768, 3.5, 30, 3
|
216 |
|
217 |
-
|
218 |
-
|
|
|
219 |
|
220 |
-
|
221 |
-
latest_blog = None
|
222 |
-
latest_blog_title = "λΈλ‘κ·Έ κΈ"
|
223 |
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
if msg["role"] == "assistant" and msg["content"].strip():
|
228 |
-
latest_blog = msg["content"]
|
229 |
-
|
230 |
-
# νμ΄ν μΆμΆ μλ (첫 λ²μ§Έ μ λͺ© νκ·Έ μ¬μ©)
|
231 |
-
import re
|
232 |
-
title_match = re.search(r'# (.*?)(\n|$)', latest_blog)
|
233 |
-
if title_match:
|
234 |
-
latest_blog_title = title_match.group(1).strip()
|
235 |
-
# μ¬μ©μ μ
λ ₯μ νμ΄νλ‘ μ¬μ©
|
236 |
-
elif len(st.session_state.messages) >= 2:
|
237 |
-
for i in range(len(st.session_state.messages)-1, -1, -1):
|
238 |
-
if st.session_state.messages[i]["role"] == "user":
|
239 |
-
latest_blog_title = st.session_state.messages[i]["content"][:30].strip()
|
240 |
-
if len(st.session_state.messages[i]["content"]) > 30:
|
241 |
-
latest_blog_title += "..."
|
242 |
-
break
|
243 |
-
break
|
244 |
|
245 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
246 |
if latest_blog:
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
# HTMLλ‘ λ€μ΄λ‘λ
|
261 |
-
with col2:
|
262 |
-
html_content = convert_md_to_html(latest_blog, latest_blog_title)
|
263 |
-
st.download_button(
|
264 |
-
label="HTML",
|
265 |
-
data=html_content,
|
266 |
-
file_name=f"{latest_blog_title}.html",
|
267 |
-
mime="text/html"
|
268 |
-
)
|
269 |
-
|
270 |
-
# PDFλ‘ λ€μ΄λ‘λ
|
271 |
-
with col3:
|
272 |
-
try:
|
273 |
-
pdf_data = convert_md_to_pdf(latest_blog, latest_blog_title)
|
274 |
-
st.download_button(
|
275 |
-
label="PDF",
|
276 |
-
data=pdf_data,
|
277 |
-
file_name=f"{latest_blog_title}.pdf",
|
278 |
-
mime="application/pdf"
|
279 |
-
)
|
280 |
-
except Exception as e:
|
281 |
-
st.error(f"PDF μμ± μ€λ₯: {e}")
|
282 |
-
logging.error(f"PDF μμ± μ€λ₯: {e}")
|
283 |
-
|
284 |
-
# λν κΈ°λ‘ λΆλ¬μ€κΈ°
|
285 |
-
uploaded_file = st.sidebar.file_uploader("λν κΈ°λ‘ λΆλ¬μ€κΈ°", type=['json'])
|
286 |
-
if uploaded_file is not None:
|
287 |
try:
|
288 |
-
|
289 |
-
|
290 |
-
st.session_state.messages = json.loads(content)
|
291 |
-
st.sidebar.success("λν κΈ°λ‘μ μ±κ³΅μ μΌλ‘ λΆλ¬μμ΅λλ€!")
|
292 |
-
else:
|
293 |
-
st.sidebar.warning("μ
λ‘λλ νμΌμ΄ λΉμ΄ μμ΅λλ€.")
|
294 |
-
except json.JSONDecodeError:
|
295 |
-
st.sidebar.error("μ¬λ°λ₯Έ JSON νμμ νμΌμ΄ μλλλ€.")
|
296 |
except Exception as e:
|
297 |
-
|
298 |
-
|
299 |
-
# λν κΈ°λ‘
|
300 |
-
if
|
301 |
-
st.session_state.messages
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
|
|
313 |
if prompt := st.chat_input("무μμ λμλ릴κΉμ?"):
|
314 |
st.session_state.messages.append({"role": "user", "content": prompt})
|
315 |
-
with st.chat_message("user"):
|
316 |
-
st.markdown(prompt)
|
317 |
|
318 |
-
# AI μλ΅ μμ±
|
319 |
with st.chat_message("assistant"):
|
320 |
-
|
321 |
-
full_response = ""
|
322 |
|
323 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
324 |
with client.messages.stream(
|
325 |
-
max_tokens=MAX_TOKENS,
|
326 |
-
system=
|
327 |
-
messages=[{"role": m["role"], "content": m["content"]}
|
328 |
-
|
329 |
) as stream:
|
330 |
-
for
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
335 |
-
|
336 |
-
# μ΄λ―Έμ§ μμ± μ΅μ
μ΄ μΌμ Έ μλ κ²½μ°
|
337 |
if st.session_state.generate_image:
|
338 |
-
with st.spinner("
|
339 |
-
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
350 |
-
)
|
351 |
-
|
352 |
-
if image:
|
353 |
-
st.image(image, caption=image_caption)
|
354 |
-
# μ΄λ―Έμ§ μ 보λ₯Ό μλ΅μ ν¬ν¨
|
355 |
-
st.session_state.messages.append({
|
356 |
-
"role": "assistant",
|
357 |
-
"content": full_response,
|
358 |
-
"image": image,
|
359 |
-
"image_caption": image_caption
|
360 |
-
})
|
361 |
-
else:
|
362 |
-
st.error(f"μ΄λ―Έμ§ μμ± μ€ν¨: {image_caption}")
|
363 |
-
st.session_state.messages.append({
|
364 |
-
"role": "assistant",
|
365 |
-
"content": full_response
|
366 |
-
})
|
367 |
-
else:
|
368 |
-
# μ΄λ―Έμ§ μμ± μμ΄ μλ΅λ§ μ μ₯
|
369 |
-
st.session_state.messages.append({
|
370 |
-
"role": "assistant",
|
371 |
-
"content": full_response
|
372 |
-
})
|
373 |
-
|
374 |
-
# λΈλ‘κ·Έ λ€μ΄λ‘λ λ²νΌ νμ (μλ΅ λ°λ‘ μλμ)
|
375 |
-
st.subheader("μ΄ λΈλ‘κ·Έ λ€μ΄λ‘λ:")
|
376 |
-
col1, col2, col3 = st.columns(3)
|
377 |
-
|
378 |
-
with col1:
|
379 |
-
st.download_button(
|
380 |
-
label="λ§ν¬λ€μ΄μΌλ‘ μ μ₯",
|
381 |
-
data=full_response,
|
382 |
-
file_name=f"{prompt[:30]}.md",
|
383 |
-
mime="text/markdown"
|
384 |
-
)
|
385 |
-
|
386 |
-
with col2:
|
387 |
-
html_content = convert_md_to_html(full_response, prompt[:30])
|
388 |
-
st.download_button(
|
389 |
-
label="HTMLλ‘ μ μ₯",
|
390 |
-
data=html_content,
|
391 |
-
file_name=f"{prompt[:30]}.html",
|
392 |
-
mime="text/html"
|
393 |
-
)
|
394 |
-
|
395 |
-
with col3:
|
396 |
-
try:
|
397 |
-
pdf_data = convert_md_to_pdf(full_response, prompt[:30])
|
398 |
-
st.download_button(
|
399 |
-
label="PDFλ‘ μ μ₯",
|
400 |
-
data=pdf_data,
|
401 |
-
file_name=f"{prompt[:30]}.pdf",
|
402 |
-
mime="application/pdf"
|
403 |
-
)
|
404 |
-
except Exception as e:
|
405 |
-
st.error(f"PDF μμ± μ€λ₯: {e}")
|
406 |
-
logging.error(f"PDF μμ± μ€λ₯: {e}")
|
407 |
-
|
408 |
-
# μλ μ μ₯ κΈ°λ₯
|
409 |
-
if st.session_state.auto_save:
|
410 |
-
try:
|
411 |
-
# μ΄λ―Έμ§ μ 보λ μ μ₯νμ§ μμ (JSONμλ λ°μ΄λ리 λ°μ΄ν°λ₯Ό μ§μ μ μ₯ν μ μμ)
|
412 |
-
save_messages = []
|
413 |
-
for msg in st.session_state.messages:
|
414 |
-
save_msg = {"role": msg["role"], "content": msg["content"]}
|
415 |
-
save_messages.append(save_msg)
|
416 |
-
|
417 |
-
with open('chat_history_auto_save.json', 'w', encoding='utf-8') as f:
|
418 |
-
json.dump(save_messages, f, ensure_ascii=False, indent=4)
|
419 |
-
except Exception as e:
|
420 |
-
st.sidebar.error(f"μλ μ μ₯ μ€ μ€λ₯ λ°μ: {str(e)}")
|
421 |
-
|
422 |
-
# λν κΈ°λ‘ λ€μ΄λ‘λ
|
423 |
-
if st.sidebar.button("λν κΈ°λ‘ λ€μ΄λ‘λ"):
|
424 |
-
# μ΄λ―Έμ§ μ 보λ μ μ₯νμ§ μμ
|
425 |
-
save_messages = []
|
426 |
-
for msg in st.session_state.messages:
|
427 |
-
save_msg = {"role": msg["role"], "content": msg["content"]}
|
428 |
-
save_messages.append(save_msg)
|
429 |
-
|
430 |
-
json_history = json.dumps(save_messages, indent=4, ensure_ascii=False)
|
431 |
-
st.sidebar.download_button(
|
432 |
-
label="λν κΈ°λ‘ μ μ₯νκΈ°",
|
433 |
-
data=json_history,
|
434 |
-
file_name="chat_history.json",
|
435 |
-
mime="application/json"
|
436 |
-
)
|
437 |
|
438 |
-
|
439 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
440 |
|
441 |
if __name__ == "__main__":
|
442 |
-
# requirements.txt
|
443 |
with open("requirements.txt", "w") as f:
|
444 |
-
f.write("
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
|
449 |
-
|
450 |
-
|
451 |
-
|
452 |
main()
|
|
|
1 |
+
# ββββββββββββββββββββββββββββββββ Imports ββββββββββββββββββββββββββββββββ
|
2 |
+
import os, json, re, logging, requests, markdown, time
|
3 |
+
from datetime import datetime
|
4 |
+
|
5 |
import streamlit as st
|
|
|
6 |
import anthropic
|
|
|
|
|
7 |
from gradio_client import Client
|
8 |
+
# from bs4 import BeautifulSoup # νμ μ μ£Όμ ν΄μ
|
|
|
|
|
|
|
9 |
|
10 |
+
# ββββββββββββββββββββββββββββββββ νκ²½ λ³μ / μμ βββββββββββββββββββββββββββ
|
11 |
+
ANTHROPIC_KEY = os.getenv("API_KEY", "")
|
12 |
+
BRAVE_KEY = os.getenv("SERPHOUSE_API_KEY", "") # μ΄λ¦ μ μ§
|
13 |
+
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 |
+
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")
|
36 |
|
37 |
+
# ββββββββββββββββββββββββββββββββ Anthropic Client βββββββββββββββββββββββββ
|
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 |
+
λ
μ μ°κ²° λ¨κ³
|
46 |
+
1.1. 곡κ°λ νμ±μ μν μΉκ·Όν μΈμ¬
|
47 |
+
1.2. λ
μμ μ€μ κ³ λ―Όμ λ°μν λμ
μ§λ¬Έ
|
48 |
+
1.3. μ£Όμ μ λν μ¦κ°μ κ΄μ¬ μ λ
|
49 |
|
50 |
+
λ¬Έμ μ μ λ¨κ³
|
51 |
+
2.1. λ
μμ νμΈν¬μΈνΈ ꡬ체ν
|
52 |
+
2.2. λ¬Έμ μ μκΈμ±κ³Ό μν₯λ λΆμ
|
53 |
+
2.3. ν΄κ²° νμμ±μ λν 곡κ°λ νμ±
|
54 |
|
55 |
+
μ λ¬Έμ± μ
μ¦ λ¨κ³
|
56 |
+
3.1. κ°κ΄μ λ°μ΄ν° κΈ°λ° λΆμ
|
57 |
+
3.2. μ λ¬Έκ° κ²¬ν΄μ μ°κ΅¬ κ²°κ³Ό μΈμ©
|
58 |
+
3.3. μ€μ μ¬λ‘λ₯Ό ν΅ν λ¬Έμ ꡬ체ν
|
59 |
|
60 |
+
μ루μ
μ 곡 λ¨κ³
|
61 |
+
4.1. λ¨κ³λ³ οΏ½οΏ½οΏ½μ² κ°μ΄λλΌμΈ μ μ
|
62 |
+
4.2. μ¦μ μ μ© κ°λ₯ν ꡬ체μ ν
|
63 |
+
4.3. μμ μ₯μ λ¬Όκ³Ό 극볡 λ°©μ ν¬ν¨
|
64 |
|
65 |
+
μ λ’°λ κ°ν λ¨κ³
|
66 |
+
5.1. μ€μ μ±κ³΅ μ¬λ‘ μ μ
|
67 |
+
5.2. ꡬ체μ μ¬μ©μ νκΈ° μΈμ©
|
68 |
+
5.3. κ°κ΄μ λ°μ΄ν°λ‘ ν¨κ³Ό μ
μ¦
|
69 |
|
70 |
+
νλ μ λ λ¨κ³
|
71 |
+
6.1. λͺ
νν 첫 μ€μ² λ¨κ³ μ μ
|
72 |
+
6.2. μκΈμ±μ κ°μ‘°ν νλ μ΄κ΅¬
|
73 |
+
6.3. μ€μ² λκΈ° λΆμ¬ μμ ν¬ν¨
|
74 |
|
75 |
+
μ§μ μ± κ°ν λ¨κ³
|
76 |
+
7.1. μ루μ
μ νκ³ ν¬λͺ
νκ² κ³΅κ°
|
77 |
+
7.2. κ°μΈλ³ μ°¨μ΄ μ‘΄μ¬ μΈμ
|
78 |
+
7.3. νμ 쑰건과 μ£Όμμ¬ν λͺ
μ
|
79 |
|
80 |
+
κ΄κ³ μ§μ λ¨κ³
|
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):
|
159 |
+
"""
|
160 |
+
Brave Web Search API νΈμΆ β list[dict]
|
161 |
+
λ°ν νλ: index, title, link, snippet, displayed_link
|
162 |
+
"""
|
163 |
+
if not BRAVE_KEY:
|
164 |
+
raise RuntimeError("β οΈ SERPHOUSE_API_KEY (Brave API Key) νκ²½λ³μκ° λΉμ΄ μμ΅λλ€.")
|
165 |
|
166 |
+
headers = {
|
167 |
+
"Accept": "application/json",
|
168 |
+
"Accept-Encoding": "gzip",
|
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, "ν둬ννΈ λΆμ‘±"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
243 |
try:
|
244 |
+
res = Client(IMAGE_API_URL).predict(
|
245 |
+
prompt=prompt, width=w, height=h, guidance=g,
|
246 |
+
inference_steps=steps, seed=seed,
|
247 |
+
do_img2img=False, init_image=None,
|
248 |
+
image2image_strength=0.8, resize_img=True,
|
249 |
+
api_name="/generate_image")
|
250 |
+
return res[0], f"Seed: {res[1]}"
|
251 |
+
except Exception as e:
|
252 |
+
logging.error(e); return None, str(e)
|
253 |
+
|
254 |
+
def extract_image_prompt(blog: str, topic: str):
|
255 |
+
sys = f"λ€μ κΈλ‘λΆν° μμ΄ 1μ€ μ΄λ―Έμ§ ν둬ννΈ μμ±:\n{topic}"
|
256 |
+
try:
|
257 |
+
res = client.messages.create(
|
258 |
model="claude-3-7-sonnet-20250219",
|
259 |
+
max_tokens=80, system=sys,
|
260 |
+
messages=[{"role": "user", "content": blog}]
|
|
|
261 |
)
|
262 |
+
return res.content[0].text.strip()
|
263 |
+
except Exception:
|
264 |
+
return f"A professional photo related to {topic}, high quality"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
265 |
|
266 |
+
def md_to_html(md: str, title="Ginigen Blog"):
|
267 |
+
return f"<!DOCTYPE html><html><head><title>{title}</title><meta charset='utf-8'></head><body>{markdown.markdown(md)}</body></html>"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
268 |
|
269 |
+
def keywords(text: str, top=5):
|
270 |
+
return " ".join(re.sub(r"[^κ°-ν£a-zA-Z0-9\\s]", "", text).split()[:top])
|
|
|
|
|
|
|
271 |
|
272 |
+
# ββββββββββββββββββββββββββββββββ Streamlit UI ββββββββββββββββββββββββββββ
|
273 |
+
def ginigen_app():
|
274 |
st.title("Ginigen Blog")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
275 |
|
276 |
+
# μΈμ
κΈ°λ³Έκ°
|
277 |
+
defaults = dict(
|
278 |
+
ai_model="claude-3-7-sonnet-20250219",
|
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(
|
317 |
+
(m["content"] for m in reversed(st.session_state.messages)
|
318 |
+
if m["role"] == "assistant" and m["content"].strip()), None)
|
319 |
+
|
320 |
if latest_blog:
|
321 |
+
title = re.search(r"# (.*?)(\n|$)", latest_blog)
|
322 |
+
title = title.group(1).strip() if title else "blog"
|
323 |
+
sb.subheader("μ΅κ·Ό λΈλ‘κ·Έ λ€μ΄λ‘λ")
|
324 |
+
c1, c2 = sb.columns(2)
|
325 |
+
c1.download_button("Markdown", latest_blog,
|
326 |
+
file_name=f"{title}.md", mime="text/markdown")
|
327 |
+
c2.download_button("HTML", md_to_html(latest_blog, title),
|
328 |
+
file_name=f"{title}.html", mime="text/html")
|
329 |
+
|
330 |
+
# ββ JSON λν κΈ°λ‘ μ
λ‘λ
|
331 |
+
up = sb.file_uploader("λν κΈ°λ‘ λΆλ¬μ€κΈ° (.json)", type=["json"])
|
332 |
+
if up:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
333 |
try:
|
334 |
+
st.session_state.messages = json.load(up)
|
335 |
+
sb.success("λν κΈ°λ‘ λΆλ¬μ€κΈ° μλ£")
|
|
|
|
|
|
|
|
|
|
|
|
|
336 |
except Exception as e:
|
337 |
+
sb.error(f"λΆλ¬μ€κΈ° μ€ν¨: {e}")
|
338 |
+
|
339 |
+
# ββ JSON λν κΈ°λ‘ λ€μ΄λ‘λ
|
340 |
+
if sb.button("λν κΈ°λ‘ JSON λ€μ΄λ‘λ"):
|
341 |
+
sb.download_button("μ μ₯", json.dumps(st.session_state.messages,
|
342 |
+
ensure_ascii=False, indent=2),
|
343 |
+
file_name="chat_history.json",
|
344 |
+
mime="application/json")
|
345 |
+
|
346 |
+
# ββ κΈ°μ‘΄ λ©μμ§ λ λλ§
|
347 |
+
for m in st.session_state.messages:
|
348 |
+
with st.chat_message(m["role"]):
|
349 |
+
st.markdown(m["content"])
|
350 |
+
if "image" in m:
|
351 |
+
st.image(m["image"], caption=m.get("image_caption", ""))
|
352 |
+
|
353 |
+
# ββ μ¬μ©μ μ
λ ₯
|
354 |
if prompt := st.chat_input("무μμ λμλ릴κΉμ?"):
|
355 |
st.session_state.messages.append({"role": "user", "content": prompt})
|
356 |
+
with st.chat_message("user"): st.markdown(prompt)
|
|
|
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("μΉ κ²μ μ€β¦"):
|
370 |
+
search_md = do_web_search(keywords(prompt))
|
371 |
+
sys_prompt += f"\n\nκ²μ κ²°κ³Ό:\n{search_md}\n"
|
372 |
+
|
373 |
+
# Claude μ€νΈλ¦¬λ°
|
374 |
with client.messages.stream(
|
375 |
+
model=st.session_state.ai_model, max_tokens=MAX_TOKENS,
|
376 |
+
system=sys_prompt,
|
377 |
+
messages=[{"role": m["role"], "content": m["content"]}
|
378 |
+
for m in st.session_state.messages]
|
379 |
) as stream:
|
380 |
+
for t in stream.text_stream:
|
381 |
+
answer += t or ""
|
382 |
+
placeholder.markdown(answer + "β")
|
383 |
+
placeholder.markdown(answer)
|
384 |
+
|
385 |
+
# μ΄λ―Έμ§ μ΅μ
|
|
|
386 |
if st.session_state.generate_image:
|
387 |
+
with st.spinner("μ΄λ―Έμ§ μμ± μ€β¦"):
|
388 |
+
ip = extract_image_prompt(answer, prompt)
|
389 |
+
img, cap = generate_image(ip)
|
390 |
+
if img:
|
391 |
+
st.image(img, caption=cap)
|
392 |
+
st.session_state.messages.append(
|
393 |
+
{"role": "assistant", "content": answer,
|
394 |
+
"image": img, "image_caption": cap})
|
395 |
+
answer_entry_saved = True
|
396 |
+
if not st.session_state.generate_image:
|
397 |
+
st.session_state.messages.append(
|
398 |
+
{"role": "assistant", "content": answer})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
399 |
|
400 |
+
# λ³Έλ¬Έ λ€μ΄λ‘λ λ²νΌ (MD / HTML)
|
401 |
+
st.subheader("μ΄ λΈλ‘κ·Έ λ€μ΄λ‘λ")
|
402 |
+
b1, b2 = st.columns(2)
|
403 |
+
b1.download_button("Markdown", answer,
|
404 |
+
file_name=f"{prompt[:30]}.md", mime="text/markdown")
|
405 |
+
b2.download_button("HTML", md_to_html(answer, prompt[:30]),
|
406 |
+
file_name=f"{prompt[:30]}.html", mime="text/html")
|
407 |
+
|
408 |
+
# ββ μλ λ°±μ
μ μ₯
|
409 |
+
if st.session_state.auto_save and st.session_state.messages:
|
410 |
+
try:
|
411 |
+
fn = f"chat_history_auto_{datetime.now():%Y%m%d_%H%M%S}.json"
|
412 |
+
with open(fn, "w", encoding="utf-8") as fp:
|
413 |
+
json.dump(st.session_state.messages, fp,
|
414 |
+
ensure_ascii=False, indent=2)
|
415 |
+
except Exception as e:
|
416 |
+
logging.error(f"μλ μ μ₯ μ€ν¨: {e}")
|
417 |
+
|
418 |
+
# ββββββββββββββββββββββββββββββββ main / requirements ββββββββββββββββββββββ
|
419 |
+
def main(): ginigen_app()
|
420 |
|
421 |
if __name__ == "__main__":
|
422 |
+
# requirements.txt λμ μμ±
|
423 |
with open("requirements.txt", "w") as f:
|
424 |
+
f.write("\n".join([
|
425 |
+
"streamlit>=1.31.0",
|
426 |
+
"anthropic>=0.18.1",
|
427 |
+
"gradio-client>=1.8.0",
|
428 |
+
"requests>=2.32.3",
|
429 |
+
"markdown>=3.5.1",
|
430 |
+
"pillow>=10.1.0"
|
431 |
+
]))
|
432 |
main()
|