Spaces:
Running
Running
Delete app-BACKUP2.py
Browse files- app-BACKUP2.py +0 -432
app-BACKUP2.py
DELETED
@@ -1,432 +0,0 @@
|
|
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()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|