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()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|