AIRider's picture
Update app.py
8591d17 verified
import os
import random
import time
import re
import json
import requests
from bs4 import BeautifulSoup
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
import openai
import gradio as gr
from fpdf import FPDF as FPDF2
from datetime import datetime
from zoneinfo import ZoneInfo
import sys
import logging
# API ํ‚ค ์„ค์ •
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
# OpenAI ์„ค์ •
openai.api_key = OPENAI_API_KEY
def setup_session():
try:
session = requests.Session()
retries = Retry(total=5, backoff_factor=1, status_forcelist=[502, 503, 504])
session.mount('https://', HTTPAdapter(max_retries=retries))
return session
except Exception as e:
return None
def generate_naver_search_url(query):
base_url = "https://search.naver.com/search.naver?"
params = {"ssc": "tab.blog.all", "sm": "tab_jum", "query": query}
url = base_url + "&".join(f"{key}={value}" for key, value in params.items())
return url
def crawl_blog_content(url, session):
try:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"Accept-Language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
"Referer": "https://search.naver.com/search.naver",
}
delay = random.uniform(1, 2)
time.sleep(delay)
response = session.get(url, headers=headers)
if response.status_code != 200:
return ""
soup = BeautifulSoup(response.content, "html.parser")
content = soup.find("div", attrs={'class': 'se-main-container'})
if content:
return clean_text(content.get_text())
else:
return ""
except Exception as e:
return ""
def crawl_naver_search_results(url, session):
try:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"Accept-Language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
"Referer": "https://search.naver.com/search.naver",
}
response = session.get(url, headers=headers)
if response.status_code != 200:
return []
soup = BeautifulSoup(response.content, "html.parser")
results = []
count = 0
for li in soup.find_all("li", class_=re.compile("bx.*")):
if count >= 10:
break
for div in li.find_all("div", class_="detail_box"):
for div2 in div.find_all("div", class_="title_area"):
title = div2.text.strip()
for a in div2.find_all("a", href=True):
link = a["href"]
if "blog.naver" in link:
link = link.replace("https://", "https://m.")
results.append({"์ œ๋ชฉ": title, "๋งํฌ": link})
count += 1
if count >= 10:
break
if count >= 10:
break
if count >= 10:
break
return results
except Exception as e:
return []
def clean_text(text):
text = re.sub(r'\s+', ' ', text).strip()
return text
def fetch_references(topic):
search_url = generate_naver_search_url(topic)
session = setup_session()
if session is None:
return ["์„ธ์…˜ ์„ค์ • ์‹คํŒจ"] * 3
results = crawl_naver_search_results(search_url, session)
if len(results) < 3:
return ["์ถฉ๋ถ„ํ•œ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ์ฐพ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค."] * 3
selected_results = random.sample(results, 3)
references = []
for result in selected_results:
content = crawl_blog_content(result['๋งํฌ'], session)
references.append(f"์ œ๋ชฉ: {result['์ œ๋ชฉ']}\n๋‚ด์šฉ: {content}")
return references
def fetch_crawl_results(query):
references = fetch_references(query)
return references[0], references[1], references[2]
def get_style_prompt(style="์นœ๊ทผํ•œ"):
prompts = {
"์นœ๊ทผํ•œ": """
[์นœ๊ทผํ•œ ํฌ์ŠคํŒ… ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ]
1. ํ†ค๊ณผ ์–ด์กฐ
- ๋Œ€ํ™”ํ•˜๋“ฏ ํŽธ์•ˆํ•˜๊ณ  ์นœ๊ทผํ•œ ๋งํˆฌ ์‚ฌ์šฉ
2. ๋ฌธ์žฅ ๋ฐ ์–ดํˆฌ
- ๋ฐ˜๋“œ์‹œ 'ํ•ด์š”์ฒด'๋กœ ์ž‘์„ฑ, ์ ˆ๋Œ€ '์Šต๋‹ˆ๋‹ค'์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ๋ง ๊ฒƒ.
- '~์š”'๋กœ ๋๋‚˜๋„๋ก ์ž‘์„ฑ, '~๋‹ค'๋กœ ๋๋‚˜์ง€ ์•Š๊ฒŒ ํ•˜๋ผ
- ๊ตฌ์–ด์ฒด ํ‘œํ˜„ ์‚ฌ์šฉ (์˜ˆ: "~ํ–ˆ์–ด์š”", "~์ธ ๊ฒƒ ๊ฐ™์•„์š”")
3. ์šฉ์–ด ๋ฐ ์„ค๋ช… ๋ฐฉ์‹
- ์ „๋ฌธ ์šฉ์–ด ๋Œ€์‹  ์‰ฌ์šด ๋‹จ์–ด๋กœ ํ’€์–ด์„œ ์„ค๋ช…
- ๋น„์œ ๋‚˜ ์€์œ ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ณต์žกํ•œ ๊ฐœ๋… ์„ค๋ช…
- ์ˆ˜์‚ฌ์˜๋ฌธ๋ฌธ ํ™œ์šฉํ•˜์—ฌ ๋…์ž์™€ ์†Œํ†ตํ•˜๋Š” ๋Š๋‚Œ ์ฃผ๊ธฐ
4. ๋…์ž์™€์˜ ์ƒํ˜ธ์ž‘์šฉ
- ๋…์ž์˜ ์˜๊ฒฌ์„ ๋ฌผ์–ด๋ณด๋Š” ์งˆ๋ฌธ ํฌํ•จ
- ๋Œ“๊ธ€ ๋‹ฌ๊ธฐ๋ฅผ ๋…๋ คํ•˜๋Š” ๋ฌธ๊ตฌ ์‚ฌ์šฉ
์ฃผ์˜์‚ฌํ•ญ: ๋„ˆ๋ฌด ๊ฐ€๋ฒผ์šด ํ†ค์€ ์ง€์–‘ํ•˜๊ณ , ์ฃผ์ œ์˜ ์ค‘์š”์„ฑ์„ ํ•ด์น˜์ง€ ์•Š๋Š” ์„ ์—์„œ ์นœ๊ทผํ•จ ์œ ์ง€
(์˜ˆ์‹œ: ์ž‡๋‹˜๋“ค~ ์˜ค๋ ˆ์˜ค ์ฝ”์นด์ฝœ๋ผ๋ง›์ด์ƒˆ๋กœ ์ถœ์‹œ๊ฐ€ ๋๋‹ค๋Š”๊ฑฐ ์•Œ๊ณ  ๊ณ„์…จ๋‚˜์š”?!ใ…Ž ์˜ค๋ ˆ์˜ค ์ฝ”์นด์ฝœ๋ผ๋ง›์€ ์–ด๋–ค์ง€ ์†”์งํ‰๊ณผ๊ตฌ๋งค์ •๋ณด, ๊ฐ€๊ฒฉ, ์นผ๋กœ๋ฆฌ ๋“ฑ์— ๋Œ€ํ•ด ์ž์„ธ~ ํžˆ ์ ์–ด๋ณด๋„๋ก ํ• ๊ป˜์š”! ์˜ค๋ ˆ์˜ค๋ฅผ ์ข‹์•„ํ•˜๋Š” ์•„๋“ค์—๊ฒŒ๊ฐ„์‹์œผ๋กœ ์˜ค๋ ˆ์˜ค ์ฝ”์นด์ฝœ๋ผ๋ง›์„ ์คฌ๋”๋‹ˆ๋ง›์žˆ๋‹ค๊ณ  ์ข‹์•„ํ•˜๋”๋ผ๊ตฌ์š”. ์ฝœ๋ผํ–ฅ์ด ๋‚˜์„œ ๋” ๋งˆ์Œ์— ๋“ ๋‹ค๋ฉฐใ…Ž๊ฐœ์ธ์ ์œผ๋กœ๋Š” ๋ณ„ โญ๏ธโญ๏ธโญ๏ธ.์š”๊ฑด ๊ฐœ์ธ์ฐจ๊ฐ€ ์žˆ์„๊ฑฐ ๊ฐ™์•„์š”~)
""",
"์ผ๋ฐ˜": """
#์ผ๋ฐ˜์ ์ธ ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŒ… ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ
1. ํ†ค๊ณผ ์–ด์กฐ
- ์ค‘๋ฆฝ์ ์ด๊ณ  ๊ฐ๊ด€์ ์ธ ํ†ค ์œ ์ง€
- ์ ์ ˆํ•œ ์กด๋Œ“๋ง ์‚ฌ์šฉ (์˜ˆ: "~ํ•ฉ๋‹ˆ๋‹ค", "~์ž…๋‹ˆ๋‹ค")
2. ๋‚ด์šฉ ๊ตฌ์กฐ ๋ฐ ์ „๊ฐœ
- ๋ช…ํ™•ํ•œ ์ฃผ์ œ ์ œ์‹œ๋กœ ์‹œ์ž‘
- ๋…ผ๋ฆฌ์ ์ธ ์ˆœ์„œ๋กœ ์ •๋ณด ์ „๊ฐœ
- ์ฃผ์š” ํฌ์ธํŠธ๋ฅผ ๊ฐ•์กฐํ•˜๋Š” ์†Œ์ œ๋ชฉ ํ™œ์šฉ
- ์ ์ ˆํ•œ ๊ธธ์ด์˜ ๋‹จ๋ฝ์œผ๋กœ ๊ตฌ์„ฑ
3. ์šฉ์–ด ๋ฐ ์„ค๋ช… ๋ฐฉ์‹
- ์ผ๋ฐ˜์ ์œผ๋กœ ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šด ์šฉ์–ด ์„ ํƒ
- ํ•„์š”์‹œ ๊ฐ„๋‹จํ•œ ์„ค๋ช… ์ถ”๊ฐ€
- ๊ฐ๊ด€์ ์ธ ์ •๋ณด ์ œ๊ณต์— ์ค‘์ 
4. ํ…์ŠคํŠธ ๊ตฌ์กฐํ™”
- ๋ถˆ๋ฆฟ ํฌ์ธํŠธ๋‚˜ ๋ฒˆํ˜ธ ๋งค๊ธฐ๊ธฐ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ •๋ณด ๊ตฌ์กฐํ™”
- ์ค‘์š”ํ•œ ์ •๋ณด๋Š” ๊ตต์€ ๊ธ€์”จ๋‚˜ ๊ธฐ์šธ์ž„๊ผด๋กœ ๊ฐ•์กฐ
5. ๋…์ž ์ƒํ˜ธ์ž‘์šฉ
- ์ ์ ˆํžˆ ๋…์ž์˜ ์ƒ๊ฐ์„ ๋ฌป๋Š” ์งˆ๋ฌธ ํฌํ•จ
- ์ถ”๊ฐ€ ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ๋Š” ํ‚ค์›Œ๋“œ ์ œ์‹œ
6. ๋งˆ๋ฌด๋ฆฌ
- ์ฃผ์š” ๋‚ด์šฉ ๊ฐ„๋‹จํžˆ ์š”์•ฝ
- ์ถ”๊ฐ€ ์ •๋ณด์— ๋Œ€ํ•œ ์•ˆ๋‚ด ์ œ๊ณต
์ฃผ์˜์‚ฌํ•ญ: ๋„ˆ๋ฌด ๋”ฑ๋”ฑํ•˜๊ฑฐ๋‚˜ ์ง€๋ฃจํ•˜์ง€ ์•Š๋„๋ก ๊ท ํ˜• ์œ ์ง€
""",
"์ „๋ฌธ์ ์ธ": """
#์ „๋ฌธ์ ์ธ ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŒ… ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ
1. ํ†ค๊ณผ ๊ตฌ์กฐ
- ๊ณต์‹์ ์ด๊ณ  ํ•™์ˆ ์ ์ธ ํ†ค ์‚ฌ์šฉ
- ๊ฐ๊ด€์ ์ด๊ณ  ๋ถ„์„์ ์ธ ์ ‘๊ทผ ์œ ์ง€
- ๋ช…ํ™•ํ•œ ์„œ๋ก , ๋ณธ๋ก , ๊ฒฐ๋ก  ๊ตฌ์กฐ
- ์ฒด๊ณ„์ ์ธ ๋…ผ์  ์ „๊ฐœ
- ์„ธ๋ถ€ ์„น์…˜์„ ์œ„ํ•œ ๋ช…ํ™•ํ•œ ์†Œ์ œ๋ชฉ ์‚ฌ์šฉ
2. ๋‚ด์šฉ ๊ตฌ์„ฑ ๋ฐ ์ „๊ฐœ
- ๋ณต์žกํ•œ ๊ฐœ๋…์„ ์ •ํ™•ํžˆ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์žฅ ๊ตฌ์กฐ ์‚ฌ์šฉ
- ๋…ผ๋ฆฌ์  ์—ฐ๊ฒฐ์„ ์œ„ํ•œ ์ „ํ™˜์–ด ํ™œ์šฉ
- ํ•ด๋‹น ๋ถ„์•ผ์˜ ์ „๋ฌธ ์šฉ์–ด ์ ๊ทน ํ™œ์šฉ (ํ•„์š”์‹œ ๊ฐ„๋žตํ•œ ์„ค๋ช… ์ œ๊ณต)
- ์‹ฌ์ธต์ ์ธ ๋ถ„์„๊ณผ ๋น„ํŒ์  ์‚ฌ๊ณ  ์ „๊ฐœ
- ๋‹ค์–‘ํ•œ ๊ด€์  ์ œ์‹œ ๋ฐ ๋น„๊ต
3. ๋ฐ์ดํ„ฐ ๋ฐ ๊ทผ๊ฑฐ ํ™œ์šฉ
- ํ†ต๊ณ„, ์—ฐ๊ตฌ ๊ฒฐ๊ณผ, ์ „๋ฌธ๊ฐ€ ์˜๊ฒฌ ๋“ฑ ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ์ถœ์ฒ˜ ์ธ์šฉ
- ํ•„์š”์‹œ ๊ฐ์ฃผ๋‚˜ ์ฐธ๊ณ ๋ฌธํ—Œ ๋ชฉ๋ก ํฌํ•จ
- ์ˆ˜์น˜ ๋ฐ์ดํ„ฐ๋Š” ํ…์ŠคํŠธ๋กœ ๋ช…ํ™•ํžˆ ์„ค๋ช…
4. ํ…์ŠคํŠธ ๊ตฌ์กฐํ™”
- ๋…ผ๋ฆฌ์  ๊ตฌ์กฐ๋ฅผ ๊ฐ•์กฐํ•˜๊ธฐ ์œ„ํ•ด ๋ฒˆํ˜ธ ๋งค๊ธฐ๊ธฐ ์‚ฌ์šฉ
- ํ•ต์‹ฌ ๊ฐœ๋…์ด๋‚˜ ์šฉ์–ด๋Š” ๊ธฐ์šธ์ž„๊ผด๋กœ ๊ฐ•์กฐ
- ๊ธด ์ธ์šฉ๋ฌธ์€ ๋“ค์—ฌ์“ฐ๊ธฐ๋กœ ๊ตฌ๋ถ„
5. ๋งˆ๋ฌด๋ฆฌ
- ํ•ต์‹ฌ ๋…ผ์  ์žฌ๊ฐ•์กฐ
- ํ–ฅํ›„ ์—ฐ๊ตฌ ๋ฐฉํ–ฅ์ด๋‚˜ ์‹ค๋ฌด์  ํ•จ์˜ ์ œ์‹œ
์ฃผ์˜์‚ฌํ•ญ: ์ „๋ฌธ์„ฑ์„ ์œ ์ง€ํ•˜๋˜, ์™„์ „ํžˆ ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ค์šด ์ˆ˜์ค€์€ ์ง€์–‘
"""
}
return prompts.get(style, prompts["์นœ๊ทผํ•œ"])
def remove_unwanted_phrases(text):
unwanted_phrases = [
'์—ฌ๋Ÿฌ๋ถ„', '์ตœ๊ทผ', '๋งˆ์ง€๋ง‰์œผ๋กœ', '๊ฒฐ๋ก ์ ์œผ๋กœ', '๊ฒฐ๊ตญ',
'์ข…ํ•ฉ์ ์œผ๋กœ', '๋”ฐ๋ผ์„œ', '๋งˆ๋ฌด๋ฆฌ', '๋์œผ๋กœ', '์š”์•ฝ'
]
words = re.findall(r'\S+|\n', text)
result_words = [word for word in words if not any(phrase in word for phrase in unwanted_phrases)]
return ' '.join(result_words).replace(' \n ', '\n').replace(' \n', '\n').replace('\n ', '\n')
def generate_blog_post(query, prompt_template, style="์นœ๊ทผํ•œ"):
try:
# ๋ชฉํ‘œ ๊ธ€์ž์ˆ˜ ์„ค์ • (๋ฌธ์ž ์ˆ˜)
target_char_length = 3000
max_attempts = 2 # ์ตœ๋Œ€ ์‹œ๋„ ํšŸ์ˆ˜
# ์ฐธ๊ณ ๊ธ€ ๊ฐ€์ ธ์˜ค๊ธฐ
references = fetch_references(query)
ref1, ref2, ref3 = references
# OpenAI API ์„ค์ •
model_name = "gpt-4o-mini"
temperature = 0.85
max_tokens = 15000
top_p = 0.9
frequency_penalty = 0.5
presence_penalty = 0.3
# ์Šคํƒ€์ผ ํ”„๋กฌํ”„ํŠธ ๊ฐ€์ ธ์˜ค๊ธฐ
style_prompt = get_style_prompt(style)
# ์ดˆ๊ธฐ ํ”„๋กฌํ”„ํŠธ ๊ตฌ์„ฑ
initial_prompt = f"""
{prompt_template}
{style_prompt}
์ฃผ์ œ: {query}
์ฐธ๊ณ ๊ธ€ 1: {ref1}
์ฐธ๊ณ ๊ธ€ 2: {ref2}
์ฐธ๊ณ ๊ธ€ 3: {ref3}
๋ชฉํ‘œ ๊ธ€์ž์ˆ˜: {target_char_length}
"""
# ์ฒซ ๋ฒˆ์งธ ์‹œ๋„
messages = [{"role": "user", "content": initial_prompt}]
response = openai.ChatCompletion.create(
model=model_name,
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
top_p=top_p,
frequency_penalty=frequency_penalty,
presence_penalty=presence_penalty,
)
first_attempt = response['choices'][0]['message']['content'].strip()
# ๋ถˆํ•„์š”ํ•œ ํ‘œํ˜„ ์ œ๊ฑฐ ๋ฐ ๊ธ€์ž์ˆ˜ ํ™•์ธ
first_attempt_cleaned = remove_unwanted_phrases(first_attempt)
first_attempt_length = len(first_attempt_cleaned)
# ์ฒซ ๋ฒˆ์งธ ์‹œ๋„์—์„œ ๋ชฉํ‘œ ๊ธ€์ž์ˆ˜ ์ถฉ์กฑ ์‹œ
if first_attempt_length >= target_char_length:
final_post = f"์ฃผ์ œ: {query}\n\n{first_attempt_cleaned}"
return final_post, ref1, ref2, ref3, first_attempt_length
# ๊ฐ€์žฅ ๊ธด ์ฐธ๊ณ ๊ธ€ ์„ ํƒ
longest_ref = max([ref1, ref2, ref3], key=len)
# ๋‘ ๋ฒˆ์งธ ์‹œ๋„ (ํ‡ด๊ณ )๋ฅผ ์œ„ํ•œ ์ถ”๊ฐ€ ํ”„๋กฌํ”„ํŠธ
revision_prompt = f"""
์ด์ „์— ์ƒ์„ฑ๋œ ๊ธ€์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋‹ค์Œ ์ง€์นจ์„ ๋ฐ˜๋“œ์‹œ ๋”ฐ๋ผ์„œ ๊ธ€์„ ํ‡ด๊ณ (revision)ํ•˜๋ผ:
1. ๋ฐ˜๋“œ์‹œ ์ด์ „ ๊ธ€์˜ ๊ตฌ์กฐ์™€ ๋‚ด์šฉ์„ ์œ ์ง€ํ•˜๋ฉด์„œ ์ฐธ๊ณ ๊ธ€์˜ ๋‚ด์šฉ์œผ๋กœ๋งŒ ๋ณด์™„ํ•˜๋ผ
2. ๋ฐ˜๋“œ์‹œ ๋ชฉํ‘œ ๊ธ€์ž์ˆ˜({target_char_length}์ž)๋ฅผ ์ถฉ์กฑํ•˜๋„๋ก ๋‚ด์šฉ์„ ๋ณด์™„ํ•˜๋ผ
- ์ ˆ๋Œ€ ๊ธ€ ํ•˜๋‹จ๋ถ€์— ๋‹จ์ˆœ ์ฒจ๊ฐ€์‹ ๊ธ€์ž์ˆ˜ ๋Š˜๋ฆฌ๊ธฐ ๊ธˆ์ง€
- ์ ˆ๋Œ€ ๊ธ€ ๋งˆ๋ฌด๋ฆฌ์— ์˜๋ฏธ์—†๋Š” ๊ฐ์ •์ ์ธ ๋‚ด์šฉ ์ถ”๊ฐ€ ๊ธˆ์ง€
- ๋ฐ˜๋“œ์‹œ ์ด์ „ ๊ธ€์˜ ๋ณธ๋ก ๋ถ€์— ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ณด์™„ํ•˜๋ผ
3. ๋ฐ˜๋“œ์‹œ ๋งˆํฌ๋‹ค์šด ํ˜•์‹์ด ์•„๋‹Œ ์ˆœ์ˆ˜ํ•œ ํ…์ŠคํŠธ๋กœ๋งŒ ์ถœ๋ ฅํ•˜์„ธ์š”.
4. ๋ฐ˜๋“œ์‹œ ์ด ํ‘œํ˜„๋“ค์€ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š”: ์—ฌ๋Ÿฌ๋ถ„, ์ตœ๊ทผ, ๋งˆ์ง€๋ง‰์œผ๋กœ, ๊ฒฐ๋ก ์ ์œผ๋กœ, ๊ฒฐ๊ตญ, ์ข…ํ•ฉ์ ์œผ๋กœ, ๋”ฐ๋ผ์„œ, ๋งˆ๋ฌด๋ฆฌ, ์š”์•ฝ.
5. ๊ธ€์˜ ํ๋ฆ„์„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋งŒ๋“ค๊ณ , ๊ฐ ๋‹จ๋ฝ ๊ฐ„์˜ ์—ฐ๊ฒฐ์„ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ํ•ด์ฃผ์„ธ์š”.
6. ์„ ํƒ๋œ ํฌ์ŠคํŒ… ์Šคํƒ€์ผ์„ ๋ฐ˜๋“œ์‹œ ์ ์šฉํ•˜์„ธ์š”.
์ด์ „ ๊ธ€:
{first_attempt_cleaned}
์ฐธ๊ณ ๊ธ€: {longest_ref}
ํฌ์ŠคํŒ… ์Šคํƒ€์ผ:
{style_prompt}
"""
# ๋‘ ๋ฒˆ์งธ ์‹œ๋„ (ํ‡ด๊ณ )
messages = [{"role": "user", "content": revision_prompt}]
response = openai.ChatCompletion.create(
model=model_name,
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
top_p=top_p,
frequency_penalty=frequency_penalty,
presence_penalty=presence_penalty,
)
revised_attempt = response['choices'][0]['message']['content'].strip()
# ๋ถˆํ•„์š”ํ•œ ํ‘œํ˜„ ์ œ๊ฑฐ
final_post = remove_unwanted_phrases(revised_attempt)
# ์ตœ์ข… ๊ฒฐ๊ณผ๋ฌผ ๊ตฌ์„ฑ
final_post = f"์ฃผ์ œ: {query}\n\n{final_post}"
actual_char_length = len(final_post)
return final_post, ref1, ref2, ref3, actual_char_length
except Exception as e:
return f"๋ธ”๋กœ๊ทธ ๊ธ€ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}", "", "", "", 0
# PDF ํด๋ž˜์Šค ๋ฐ ๊ด€๋ จ ํ•จ์ˆ˜ ์ •์˜
class PDF(FPDF2):
def __init__(self):
super().__init__()
current_dir = os.path.dirname(__file__)
self.add_font("NanumGothic", "", os.path.join(current_dir, "NanumGothic.ttf"))
self.add_font("NanumGothic", "B", os.path.join(current_dir, "NanumGothicBold.ttf"))
self.add_font("NanumGothicExtraBold", "", os.path.join(current_dir, "NanumGothicExtraBold.ttf"))
self.add_font("NanumGothicLight", "", os.path.join(current_dir, "NanumGothicLight.ttf"))
def header(self):
self.set_font('NanumGothic', '', 10)
def footer(self):
self.set_y(-15)
self.set_font('NanumGothic', '', 8)
self.cell(0, 10, f'Page {self.page_no()}', 0, 0, 'C')
def save_to_pdf(blog_post, user_topic):
pdf = PDF()
pdf.add_page()
lines = blog_post.split('\n')
title = lines[0].strip()
content = '\n'.join(lines[1:]).strip()
# ํ˜„์žฌ ๋‚ ์งœ์™€ ์‹œ๊ฐ„์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค (๋Œ€ํ•œ๋ฏผ๊ตญ ์‹œ๊ฐ„ ๊ธฐ์ค€)
now = datetime.now(ZoneInfo("Asia/Seoul"))
date_str = now.strftime("%y%m%d")
time_str = now.strftime("%H%M")
# ํŒŒ์ผ๋ช… ์ƒ์„ฑ
filename = f"{date_str}_{time_str}_{format_filename(user_topic)}.pdf"
pdf.set_font("NanumGothic", 'B', size=14)
pdf.cell(0, 10, title, ln=True, align='C')
pdf.ln(10)
pdf.set_font("NanumGothic", '', size=11)
pdf.multi_cell(0, 5, content)
print(f"Saving PDF as: {filename}")
pdf.output(filename)
return filename
def format_filename(text):
text = re.sub(r'[^\w\s-]', '', text)
return text[:50].strip()
def save_content_to_pdf(blog_post, user_topic):
return save_to_pdf(blog_post, user_topic)
# ๊ธฐ๋ณธ ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ
DEFAULT_PROMPT_TEMPLATE = """
[๋ธ”๋กœ๊ทธ ๊ธ€ ์ž‘์„ฑ ๊ธฐ๋ณธ ๊ทœ์น™]
1. ๋ฐ˜๋“œ์‹œ ํ•œ๊ธ€๋กœ ์ž‘์„ฑํ•˜๋ผ
2. ์ฃผ์–ด์ง„ ์ฐธ๊ณ ๊ธ€์„ ๋ฐ”ํƒ•์œผ๋กœ ์—ฌํ–‰ ๋ธ”๋กœ๊ทธ๋ฅผ ์ž‘์„ฑ
3. ๊ธ€์˜ ์ œ๋ชฉ์„ ์—ฌํ–‰ ๋ธ”๋กœ๊ทธ ํ˜•ํƒœ์— ๋งž๋Š” ์ ์ ˆํ•œ ์ œ๋ชฉ์œผ๋กœ ์ถœ๋ ฅ
- ์ฐธ๊ณ ๊ธ€์˜ ์ œ๋ชฉ๋„ ์ฐธ๊ณ ํ•˜๋˜, ๋™์ผํ•˜๊ฒŒ ์ž‘์„ฑํ•˜์ง€ ๋ง ๊ฒƒ
4. ๋ฐ˜๋“œ์‹œ ๋งˆํฌ๋‹ค์šด ํ˜•์‹์ด ์•„๋‹Œ ์ˆœ์ˆ˜ํ•œ ํ…์ŠคํŠธ๋กœ๋งŒ ์ถœ๋ ฅํ•˜๋ผ
5. ๋ฐ˜๋“œ์‹œ 3000์ž ์ด์ƒ ์ž‘์„ฑํ•˜๋ผ
6. ์ฃผ์ œ์™€ ์ฐธ๊ณ ๊ธ€์„ ๋ณด๊ณ  ์—ฌํ–‰ ์Šคํƒ€์ผ(๋šœ๋ฒ…์ด, ๊ฐ€์กฑ(์•„์ด, ๋ถ€๋ชจ๋‹˜), ์ปคํ”Œ, ์†”๋กœ ๋“ฑ)์„ ํ•œ๊ฐ€์ง€ ์„ ์ •ํ•˜์—ฌ ์ž‘์„ฑํ•˜๋ผ
7. ์–ดํˆฌ๋Š” ์ฐธ๊ณ ๊ธ€์˜ ์–ดํˆฌ๋ฅผ ๋ฐ˜์˜ํ•˜๋˜ ์—ฌํ–‰์— ๋Œ€ํ•œ ์„ค๋ ˆ์ž„์ด ๋‹ด๊ธด ์–ดํˆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ผ
* ๋ชจ๋“  ๋‚ด์šฉ๋“ค์€ ์„น์…˜์„ ๊ตฌ๋ถ„ํ•˜์ง€ ๋ง๊ณ  ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์–ด์šฐ๋Ÿฌ์ง€๊ฒŒ ์ž‘์„ฑํ•˜๋ผ
[์—ฌํ–‰ ๊ธ€ ์ž‘์„ฑ ์„ธ๋ถ€ ๊ทœ์น™]
1. ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ์ฃผ์ œ์™€ ์ฃผ์–ด์ง„ ์ฐธ๊ณ ๊ธ€์„ ๋ฐ”ํƒ•์œผ๋กœ ์—ฌํ–‰ ๋ธ”๋กœ๊ทธ ๊ธ€ 1๊ฐœ๋ฅผ ์ž‘์„ฑํ•˜๋ผ
2. ๊ธ€์˜ ์ฃผ์ œ๋Š” ์ž…๋ ฅ๋œ ์ฃผ์ œ์™€ ์ฐธ๊ณ ๊ธ€์— ๋งž๊ฒŒ ๋‹ค์–‘ํ•œ ํ˜•ํƒœ๋กœ ๊ธ€์„ ์ž‘์„ฑํ•˜๋ผ
- ์ฝ”์Šค, ์ผ์ •๋“ฑ์˜ ํ˜•ํƒœ(2๋ฐ•3์ผ ์—ฌํ–‰ ์ฝ”์Šค, ๋ฐ์ดํŠธ ์ฝ”์Šค ๋“ฑ)
- ํ๋ ˆ์ด์…˜ ํ˜•ํƒœ(์—ฌํ–‰์ง€ ์ถ”์ฒœ Best5 ๋“ฑ, ๋‹จ ์—ฌํ–‰์ง€๋Š” ์ตœ๋Œ€ 5๊ณณ)
- ๋งž์ถคํ˜• ์—ฌํ–‰์ง€ ์ถ”์ฒœ(์ปคํ”Œ, ๋ฐ์ดํŠธ, ๊ฐ€์กฑ์—ฌํ–‰, ์•„์ด์™€ ํ•จ๊ป˜ํ•˜๋Š” ์—ฌํ–‰, ๋ถ€๋ชจ๋‹˜๊ณผ ์—ฌํ–‰ ๋“ฑ)
- ๋‹จ์ˆœ ์—ฌํ–‰์ง€ ๋‚˜์—ด ๊ธˆ์ง€
- ์ผ์ •์ด๋‚˜ ์ฝ”์Šค์— ๋”ฐ๋ฅธ ์„น์…˜ ๊ตฌ๋ถ„ ๊ธˆ์ง€
3. ๋…์ž๊ฐ€ ์ง์ ‘ ์ฒดํ—˜ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ์ƒ์ƒํ•˜๊ฒŒ ์ „๋‹ฌํ•˜๋ผ
4. ๊ฐœ์ธ์ ์ธ ๊ฒฝํ—˜๊ณผ ์ •๋ณด ์ œ๊ณต์˜ ๊ท ํ˜•์„ ๋งž์ถฐ, ๋…์ž๋“ค์ด ์ •๋ณด๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋„๋ก ์ž‘์„ฑ
5. ์—ฌํ–‰์˜ ์ฃผ์š” ํ™œ๋™(๊ด€๊ด‘, ์ฒดํ—˜, ๋ง›์ง‘ ํƒ๋ฐฉ ๋“ฑ)์„ ์ž‘์„ฑ
6. ๊ฐ ํ™œ๋™์—์„œ ๊ฒช์€ ๊ฐœ์ธ์ ์ธ ๊ฒฝํ—˜(๋Œ€๊ธฐ ์‹œ๊ฐ„, ๊ตํ†ต, ๋‚ ์”จ ๋“ฑ)์„ ๊ตฌ์ฒด์ ์œผ๋กœ ์„ค๋ช…ํ•˜๋ผ
7. ์—ฌํ–‰ ์ค‘ ๋จน์€ ์Œ์‹์ด๋‚˜ ์ฒดํ—˜์„ ์ค‘์‹ฌ์œผ๋กœ, ๊ฒฝํ—˜๊ณผ ๋Š๋‚Œ๋“ฑ์„ ์ถ”๊ฐ€ํ•˜๊ณ  ๊ตฌ์ฒด์ ์ธ ์ •๋ณด(๋ฉ”๋‰ด, ๊ฐ€๊ฒฉ, ์œ„์น˜ ๋“ฑ)๋ฅผ ์ž‘์„ฑ
8. ์—ฌํ–‰๊ณผ ํ™œ๋™์— ๋Œ€ํ•œ ๊ฐ์ข… ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๋ผ
[์—ฌํ–‰๊ณผ ๊ด€๋ จ๋œ ๊ฐ์ข… ์ •๋ณด]
1. ์ž…์žฅ๋ฃŒ, ์ค€๋น„๋ฌผ, ์‹œ๊ฐ„, ์ฃผ์ฐจ, ๊ตํ†ต์ˆ˜๋‹จ, ํ–‰์‚ฌ, ์ผ์ •, ๊ฐ€๊ฒฉ, ๋ง›์ง‘์ •๋ณด, ๊ฟ€ํŒ, ์ˆ™์†Œ ์„ ํƒ ๊ธฐ์ค€, ์ฃผ๋ณ€ ํ™˜๊ฒฝ ๋“ฑ
2. ๊ณ„์ ˆ๋ณ„๋กœ ๋‹ฌ๋ผ์ง€๋Š” ๊ด€๊ด‘์ง€์˜ ๋ชจ์Šต, ์ฆ๊ธธ ๊ฑฐ๋ฆฌ, ์ฃผ์˜์‚ฌํ•ญ ๋“ฑ
3. ์—ฌํ–‰์ง€์˜ ๋Œ€ํ‘œ์ ์ธ ํŠน์‚ฐ๋ฌผ์˜ ์œ ๋ž˜์™€ ๋ง›์˜ ํŠน์ง•
4. ์—ฌํ–‰ ์ „ ์ค€๋น„ ๊ณผ์ •, ์˜ˆ์•ฝ ํŒ, ํ•„์ˆ˜ ์ค€๋น„๋ฌผ ๋“ฑ
5. ์ธ์Šคํƒ€๊ทธ๋žจ์ด๋‚˜ SNS์— ์˜ฌ๋ฆฌ๊ธฐ ์ข‹์€ ์žฅ์†Œ๋‚˜ ํฌํ†  ์ŠคํŒŸ ๋“ฑ
6. ํ˜„์ง€์ธ๋“ค์ด ์ž์ฃผ ๊ฐ€๋Š” ์ˆจ์€ ๋ง›์ง‘์ด๋‚˜ ๋ช…์†Œ
7. ๋Œ€์ค‘๊ตํ†ต, ๋ Œํ„ฐ์นด ๋“ฑ ์ด๋™ ์ˆ˜๋‹จ์— ๋”ฐ๋ฅธ ์—ฌํ–‰ ํŒ
8. ์—ฌํ–‰ ์ค‘ ๊ฒช์„ ์ˆ˜ ์žˆ๋Š” ์–ด๋ ค์›€(์˜ˆ: ์›จ์ดํŒ…, ๋‚ ์”จ ๋ณ€ํ™”)๊ณผ ๋Œ€์ฒ˜ ๋ฐฉ๋ฒ• ๋“ฑ
9. ์—ฌํ–‰์ง€์˜ ์—ญ์‚ฌ๋‚˜ ๋ฌธํ™”์  ๋ฐฐ๊ฒฝ์„ ๊ฐ„๋‹จํžˆ ์†Œ๊ฐœ
[๋ฐ˜๋“œ์‹œ ์ œ์™ธํ•ด์•ผ ํ•  ํ‘œํ˜„]
1. ๋ฐ˜๋“œ์‹œ ์ฐธ๊ณ ๊ธ€์˜ ํฌํ•จ๋œ ๋งํฌ(URL)๋Š” ์ œ์™ธ
2. ์ฐธ๊ณ ๊ธ€์—์„œ '๋งํฌ๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”'์™€ ๊ฐ™์€ ๋งํฌ ์ด๋™์˜ ๋ฌธ๊ตฌ๋Š” ์ œ์™ธ
3. ์ฐธ๊ณ ๊ธ€์— ์žˆ๋Š” ์ž‘์„ฑ์ž, ํ™”์ž, ์œ ํŠœ๋ฒ„, ๊ธฐ์ž(Writer, speaker, YouTuber, reporter)์˜ ์ด๋ฆ„, ์• ์นญ, ๋‹‰๋„ค์ž„(Name, Nkickname)์€ ๋ฐ˜๋“œ์‹œ ์ œ์™ธ
4. '์—…์ฒด๋กœ ๋ถ€ํ„ฐ ์ œ๊ณต ๋ฐ›์•„์„œ ์ž‘์„ฑ', '์ฟ ํŒก ํŒŒํŠธ๋„ˆ์Šค'๋“ฑ์˜ ํ‘œํ˜„์„ ๋ฐ˜๋“œ์‹œ ์ œ์™ธํ•˜๋ผ.
"""
# Gradio ์•ฑ ์ƒ์„ฑ
with gr.Blocks() as iface:
gr.Markdown("# ๋ธ”๋กœ๊ทธ ๊ธ€ ์ž‘์„ฑ๊ธฐ_์—ฌํ–‰(์ฝ”์Šคํ˜•) ๋ธ”๋กœ๊ทธ")
gr.Markdown("์ฃผ์ œ๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ๋ธ”๋กœ๊ทธ ๊ธ€ ์ƒ์„ฑ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ์ž๋™์œผ๋กœ ๋ธ”๋กœ๊ทธ ๊ธ€์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.")
query_input = gr.Textbox(lines=1, placeholder="ํ‚ค์›Œ๋“œ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”...", label="ํ‚ค์›Œ๋“œ")
style_input = gr.Radio(["์นœ๊ทผํ•œ", "์ผ๋ฐ˜", "์ „๋ฌธ์ ์ธ"], label="ํฌ์ŠคํŒ… ์Šคํƒ€์ผ", value="์นœ๊ทผํ•œ")
prompt_input = gr.Textbox(lines=10, value=DEFAULT_PROMPT_TEMPLATE, label="ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ", visible=False)
generate_button = gr.Button("๋ธ”๋กœ๊ทธ ๊ธ€ ์ƒ์„ฑ")
output_text = gr.Textbox(label="์ƒ์„ฑ๋œ ๋ธ”๋กœ๊ทธ ๊ธ€")
ref1_text = gr.Textbox(label="์ฐธ๊ณ ๊ธ€ 1", lines=10, visible=False)
ref2_text = gr.Textbox(label="์ฐธ๊ณ ๊ธ€ 2", lines=10, visible=False)
ref3_text = gr.Textbox(label="์ฐธ๊ณ ๊ธ€ 3", lines=10, visible=False)
save_pdf_button = gr.Button("PDF๋กœ ์ €์žฅ")
pdf_output = gr.File(label="์ƒ์„ฑ๋œ PDF ํŒŒ์ผ")
generate_button.click(
generate_blog_post,
inputs=[query_input, prompt_input, style_input],
outputs=[output_text, ref1_text, ref2_text, ref3_text],
show_progress=True
)
save_pdf_button.click(
save_content_to_pdf,
inputs=[output_text, query_input],
outputs=[pdf_output],
show_progress=True
)
# Gradio ์•ฑ ์‹คํ–‰
if __name__ == "__main__":
iface.launch()