import gradio as gr
import random
import os
import re
import openai
from fpdf import FPDF
from datetime import datetime
from zoneinfo import ZoneInfo
from sklearn.feature_extraction.text import CountVectorizer
# OpenAI API 클라이언트 설정
openai.api_key = os.getenv("OPENAI_API_KEY")
def call_api(system_message, max_tokens, temperature, top_p, content):
try:
messages = [{"role": "system", "content": system_message}, {"role": "user", "content": content}]
response = openai.ChatCompletion.create(
model="gpt-4o-mini",
messages=messages,
max_tokens=max_tokens,
temperature=temperature,
top_p=top_p,
request_timeout=300 # 5분 타임아웃 설정
)
return response.choices[0].message['content']
except Exception as e:
print(f"API 호출 중 오류 발생: {str(e)}")
raise
def analyze_info(data):
return f"선택한 카테고리: {data['category']}\n선택한 포스팅 스타일: {data['style']}\n참고 글1: {data['references1']}\n참고 글2: {data['references2']}\n참고 글3: {data['references3']}"
def suggest_title(data):
full_content = analyze_info(data)
combined_prompt = f"{get_title_prompt(data['category'])}\n\n{get_style_prompt(data['style'])}"
modified_text = call_api(combined_prompt, 1500, 0.75, 0.95, full_content)
return modified_text
def generate_outline(data):
full_content = analyze_info(data)
content = f"{full_content}\nTitle: {data['title']}"
combined_prompt = f"{get_outline_prompt(data['category'])}\n\n{get_style_prompt(data['style'])}"
modified_text = call_api(combined_prompt, 2000, 0.7, 0.95, content)
return modified_text
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 extract_keywords(text, top_n=5):
vectorizer = CountVectorizer(stop_words='english', ngram_range=(1,2))
count_matrix = vectorizer.fit_transform([text])
terms = vectorizer.get_feature_names_out()
counts = count_matrix.sum(axis=0).A1
term_counts = sorted(zip(terms, counts), key=lambda x: x[1], reverse=True)
return [term for term, count in term_counts[:top_n]]
def get_group_instruction(group_index):
instructions = [
"반드시 도입부와 초기 본론만 작성하세요. 도입부에서는 주제를 소개하고, 독자의 관심을 끌어야 합니다. 초기 본론에서는 주요 논점을 제시하되, 깊이 있는 논의는 다음 섹션으로 미루세요.",
"반드시 중간 본론만 작성하세요. 이전 섹션에서 제시된 논점을 깊이 있게 발전시키고, 추가적인 논점을 제시하세요. 결론으로 넘어가지 않도록 주의하세요.",
"본론의 마지막 부분과 결론을 작성하세요. 마지막 본론에서는 남은 논점을 다루고, 결론에서는 전체 내용을 요약하고 최종 메시지를 전달하세요."
]
return instructions[group_index]
import re
def generate_blog_post(category, style, references1, references2, references3, title, outline):
try:
# 입력 데이터를 딕셔너리로 구성
data = {
'category': category,
'style': style,
'references1': references1,
'references2': references2,
'references3': references3,
'title': title,
'outline': outline
}
# 그룹 생성 지침 정의
group_instruction = "각 섹션을 연결하여 일관성 있게 작성하고, 각 그룹 간 자연스러운 흐름을 유지하세요."
# 시스템 및 스타일 프롬프트 설정
system_prompt = get_blog_post_prompt(data['category'])
style_prompt = get_style_prompt(data['style'])
# 아웃라인 섹션 나누기
outline_sections = data['outline'].split('\n')
# 섹션 그룹화 (개선된 방식 적용)
grouped_sections = [
outline_sections[:3], # 도입부 + 본문1 + 본문2
outline_sections[3:5], # 본문3 + 본문4
outline_sections[5:] # 본문5 + 결론
]
sections = [] # 섹션 결과 저장 리스트
previous_content = "" # 이전 섹션 내용 저장 변수
# 각 섹션 그룹별로 글 생성 수행
for i, section_group in enumerate(grouped_sections):
print(f"섹션 그룹 {i+1}/{len(grouped_sections)} 생성 중...")
# 동적으로 토큰 할당 (개선된 방식 적용)
max_tokens = [2300, 2000, 1200][i]
# 사용자 프롬프트 설정 (group_instruction 포함)
user_prompt = f"""
카테고리: {data['category']}
포스팅 스타일: {data['style']}
참고글1: {data['references1']}
참고글2: {data['references2']}
참고글3: {data['references3']}
현재 섹션 그룹: {' / '.join(section_group)}
이전 섹션의 마지막 내용: {previous_content}
위의 정보를 바탕으로 '{' / '.join(section_group)}' 섹션 그룹의 내용을 작성하되, 다음 지침을 반드시 준수하라:
1. {group_instruction} # group_instruction 반영
2. 전체 아웃라인의 흐름을 고려하여 일관성 있게 작성.
3. 약 300-400단어로 작성(반드시 참고글 안에 있는 내용으로만 작성).
4. 반드시 참고글의 내용을 바탕으로 구성하되, 표현을 그대로 복사하지 말 것.
5. 각 섹션의 주요 내용을 충분히 다루되, 전체 글의 흐름을 해치지 않도록 주의.
6. 이전 섹션과의 연결성을 고려하여 자연스럽게 이어지도록 작성.
7. 각 섹션의 제목을 작성하라.
"""
try:
# API 호출을 통해 섹션 내용 생성
section_content = call_api(system_prompt + "\n" + style_prompt, max_tokens, 0.7, 1, user_prompt)
sections.append(section_content)
# 이전 섹션의 마지막 3개의 문장 저장
sentences = section_content.split('.')
previous_content = '. '.join(sentences[-3:]) + '.'
except Exception as e:
print(f"섹션 그룹 {i+1} 생성 중 오류 발생: {str(e)}")
sections.append(f"섹션 그룹 {i+1} 생성 중 오류 발생")
# 최종 블로그 글 생성
full_post = data['title'] + "\n\n" + "\n\n".join(sections)
print(f"GPT가 생성한 원본 블로그 글:\n{full_post}")
# 불필요한 표현 제거
filtered_post = remove_unwanted_phrases(full_post)
# 최종 결과 및 세션 상태 반환
return filtered_post, True # 새 세션 시작을 위해 True 반환
except Exception as e:
print(f"글 생성 중 오류 발생: {str(e)}")
return "", False
def remove_unwanted_phrases(text):
unwanted_phrases = [
'여러분', '최근', '마지막으로', '결론적으로', '결국',
'종합적으로', '따라서', '마무리', '요약'
]
# 문단별로 나누어 처리
lines = text.split('\n')
result_lines = []
for line in lines:
if "다음 섹션에서는" in line:
parts = line.split("다음 섹션에서는")
if parts[0].strip():
result_lines.append(parts[0].strip())
else:
# 불필요한 표현 제거 (구두점 포함)
for phrase in unwanted_phrases:
# 불필요한 표현 앞뒤의 구두점과 공백까지 포함하여 제거
pattern = rf'(\b{re.escape(phrase)}\b[\s,.!?]*)|([,.!?]*\b{re.escape(phrase)}\b)'
line = re.sub(pattern, '', line)
# 문장 내 잔여 공백 및 구두점 정리
line = re.sub(r'\s{2,}', ' ', line) # 연속 공백 제거
line = line.strip() # 앞뒤 공백 제거
result_lines.append(line)
return '\n'.join(result_lines)
def get_title_prompt(category):
if (category == "여행 단일"):
return """
[여행 블로그 제목 작성 규칙]
1. 반드시 한국어(한글)로 작성하라
2. 너는 가장 주목받는 여행 블로거이며 여행 마케팅 전문가이다
3. 특히 너는 '여행 단일지점 리뷰'에 대한 전문적인 블로그 마케터이다
4. 여행지의 특징과 가치를 강조하며, 꼭 가고 싶은 여행 정보를 제공하는 데 초점을 맞추어 작성하라
5. 독자의 클릭을 유발할 수 있는 제목으로 작성하라
[여행 단일지점 리뷰형]
1. 블로그 제목 10개를 작성하고 반드시 제목 10개만 출력하라
2. 제목은 40자 이내로 작성하라
3. 입력된 블로그 주제, 핵심키워드(Topic)가 문장 앞쪽에 위치하도록 작성하라
4. 단일 여행지의 핵심 명소, 숨겨진 포인트 등을 강조하라
5. 여행지의 이름과 관련 키워드를 포함하여 작성하라
[예시]
1. 부산 아이와 가볼만 한 곳 실내 동물원 '주주타임'
2. 2024 경복궁 야간개장 하반기 야간관람 예약 예매 한복대여 입장 기본정보
3. 2024 한화 서울 여의도 불꽃축제 명당 세계불꽃축제 시간 행사 정보
4. 임실치즈테마파크 숙박, 축제 현황 및 지도 공유
"""
elif (category == "여행 코스"):
return """
[여행 블로그 제목 작성 규칙]
1. 반드시 한국어(한글)로 작성하라
2. 너는 가장 주목받는 여행 블로거이며 여행 마케팅 전문가이다
3. 특히 너는 '여행 코스 추천'에 대한 전문적인 블로그 마케터이다
4. 여행지의 특징과 가치를 강조하며, 꼭 가고 싶은 여행 정보를 제공하는 데 초점을 맞추어 작성하라
5. 독자의 클릭을 유발할 수 있는 제목으로 작성하라
[여행 코스 추천형]
1. 블로그 제목 10개를 작성하고 제목 10개만 출력하라.
2. 제목은 40자 이내로 작성하라.
3. 입력된 블로그 주제, 핵심키워드(Topic)가 문장 앞쪽에 위치하도록 작성하라.
4. 여행 일정, 코스, 또는 추천 루트가 돋보이게 작성하라.
5. 코스 내 주요 명소와 일정의 흐름을 포함하여 제목을 구성하라.
[예시]
1. 강원도 여행 2박3일 설악산, 삼척, 묵호 가족 여행 코스 추천
2. 속초 가볼만한곳 9월 강원도 여행
3. 강화 가볼만한곳 인천 1박2일 강화도 여행 코스 5곳
4. 인천 여행 아이와 함께 가성비 호캉스 청라호텔 원티드
5. 제주 성산 가볼만한곳 성산일출봉 제주도 여행 코스
6. 부산여행 2박 3일 뚜벅이 여행 경비 및 일정 공유
7. 10월 제주도 여행 억새 핑크뮬리 명소 제주도 서쪽 코스
"""
def get_outline_prompt(category):
if (category == "여행 단일"):
return """
[여행 블로그 소주제(Outline) 생성 규칙]
[기본규칙]
1. 반드시 한국어(한글)로 작성하라
2. 너는 가장 주목받는 여행 블로거이며 여행 마케팅 전문가이다
3. 특히 너는 '여행 단일지점 리뷰'에 대한 전문적인 블로그 마케터이다
4. 입력된 여행지의 주제와 제목을 바탕으로 해당 여행지의 매력을 부각할 수 있는 소주제를 작성하라
5. 방문 시 필요한 정보, 숨겨진 명소, 추천 포인트등을 반영한 소주제(섹션)를 구성하라
6. 반드시 마크다운 형식이 아닌 순수한 텍스트로 출력하라
[아웃라인 작성 규칙]
1. 블로그 글을 작성하기 위한 소주제(섹션)만을 작성하라
2. 반드시 입력된 참고글과 블로그 주제, 제목을 바탕으로 핵심 주제를 파악하여 소주제(섹션)를 구성하라
3. 전체 맥락에 맞게 소주제(섹션)를 작성
4. 반드시 소제목으로 사용할 수 있도록 30자 이내로 작성하라
5. 독자가 얻고자 하는 정보와 흥미로운 정보를 제공하도록 소주제를 작성하라.
6. 반드시 [소주제 구성]에 맞게 소주제만 출력하라
[아웃라인 구성]
1. 반드시 [도입부]-1개, [본론]-5개, [결론]-1개로 구성하여 출력하라
2. 반드시 [도입부]와 [결론]의 제목이 중복되지 않도록 작성하라
출력력예시:
- 도입부:
- 본론1:
...
- 본론5:
- 결론:
3. 입력된 제목은 출력하지 말 것
"""
elif (category == "여행 코스"):
return """
[여행 블로그 소주제(Outline) 생성 규칙]
[기본규칙]
1. 반드시 한국어(한글)로 작성하라
2. 너는 가장 주목받는 여행 블로거이며 여행 마케팅 전문가이다
3. 특히 너는 '여행 코스 추천'에 대한 전문적인 블로그 마케터이다
4. 입력된 여행지 정보와 주제, 제목을 바탕으로 여행할 때 꼭 필요한 정보와 추천 루트등을 부각할 수 있는 소주제를 작성하라
5. 여행의 일정이나 코스, 이동 순서와 특징을 고려하여 흥미롭고 유용한 소주제(섹션)를 구성하라
6. 반드시 마크다운 형식이 아닌 순수한 텍스트로 출력하라
[아웃라인 작성 규칙]
1. 블로그 글을 작성하기 위한 소주제(섹션)만을 작성하라
2. 반드시 입력된 참고글과 블로그 주제, 제목을 바탕으로 핵심 주제를 파악하여 소주제(섹션)를 구성하라
3. 전체 맥락에 맞게 소주제(섹션)를 작성
4. 소제목으로 사용할 수 있도록 30자 이내로 작성하라
5. 독자가 얻고자 하는 정보와 흥미로운 정보를 제공하도록 소주제를 작성하라.
6. 반드시 [소주제 구성]에 맞게 소주제만 출력하라
[아웃라인 구성]
1. 반드시 [도입부]-1개, [본론]-5개, [결론]-1개로 구성하여 출력하라
2. 반드시 [도입부]와 [결론]의 제목이 중복되지 않도록 작성하라
3. 입력된 제목은 출력하지 말 것
"""
def get_blog_post_prompt(category):
if (category == "여행 단일"):
return """
[여행 단일지점 텍스트 생성 규칙]
[기본규칙]
1. 반드시 한국어(한글)로 작성하라
2. 너는 전문전익 콘텐츠 작가이다. 주어진 주제에 대해 풍부하고 매력적인 내용을 작성하라
3. 각 섹션을 독립적인 정보 단위로 취급하라
4. 다른 섹션과의 연결성을 고려하되, 각 섹션이 독립적으로도 이해될 수 있게 작성하라
5. 여행지의 매력과 가치를 부각하여 독자가 꼭 방문하고 싶도록 작성하라
6. 여행지의 정보와 특징을 정확하게 설명하라
7. 여행지의 구체적인 방문 포인트, 숨겨진 명소, 각종정보 그리고 실제 경험을 반영한 글을 작성하라
8. 이 섹션은 더 큰 콘텐츠의 일부임을 명심하라, 전체적인 흐름을 해치지 않으면서 주어진 주제를 철저히 작성하라라
[텍스트 작성 규칙]
1. 반드시 입력된 소주제(아웃라인)에 맞게 글을 작성하라
2. 반드시 입력된 참고글의 내용으로만 구성
3. 전체 맥락을 이해하고 문장의 일관성을 유지하라
4. 제공된 참고글의 어투를 반영하되, 절대로 한 문장 이상 그대로 출력하지 말 것
5. 여행지의 숨겨진 매력과 실용적인 정보(교통, 주차, 위치(주소), 행사, 시간(운영시간), 팁 등)를 포함하여 작성하라
6. 객관적인 여행 정보와 주관적인 고객 반응을 균형있게 제시하라
7. 쉽게 읽힐 수 있도록 쉬운 어휘로 작성
"""
elif (category == "여행 코스"):
return """
# 여행 코스 추천형 텍스트 생성 규칙
[기본규칙]
1. 반드시 한국어(한글)로 작성하라
2. 너는 가장 주목받는 마케터이며 블로그 마케팅 전문가이다
3. 특히 너는 '여행 코스 추천'에 특화된 블로그 마케터이다
4. 반드시 마크다운 형식이 아닌 순수한 텍스트로 출력하라
5. 여행 코스의 흐름과 특징을 부각하여 독자가 쉽게 따라갈 수 있도록 작성하라
6. 여행 코스별 주요 포인트와 명소들을 중심으로 글을 작성하라
[텍스트 작성 규칙]
1. 반드시 입력된 [소주제]에 맞게 텍스트를 작성하라
2. [본론]의 소주제에 맞는 내용을 각각 **500자 이상**으로 작성하라
3. 전체 맥락을 이해하고 문장의 일관성을 유지하라
4. 제공된 참고글의 어투를 반영하되, 절대로 한 문장 이상 그대로 출력하지 말 것
6. 해당 여행의 매력과 특징을 부각시키도록 작성하라
7. 객관적인 여행 정보와 주관적인 고객 반응을 균형있게 제시하라
8. 각 코스와 추천 명소를 중심으로 상세한 정보를 제공하라
- 여행 타겟에 맞는 각종 정보(팁)
- 시간, 주차, 주소(장소), 가격, 행사, 맛집(음식) 등
7. 쉽게 읽힐 수 있도록 쉬운 어휘로 작성
[제외 규칙]
1. 반드시 참고글의 링크(URL)는 제외하라
2. 참고글에서 '링크를 확인해주세요'와 같은 링크 이동의 문구는 제외하라
3. 참고글에 있는 작성자, 화자, 유튜버, 기자의 이름, 애칭, 닉네임은 반드시 제외하라
4. '업체로 부터 제공 받아서 작성', '쿠팡 파트너스'등의 표현을 반드시 제외하라
5. 제목(title)은 출력하지 말 것
"""
def get_style_prompt(style):
prompts = {
"친근한": """
[친근한 포스팅 스타일 가이드]
1. 톤과 어조
- 대화하듯 편안하고 친근한 말투 사용
2. 문장 및 어투
- 반드시 '해요체'로 작성, 절대 '습니다'체를 사용하지 말 것.
- '~요'로 끝나도록 작성, '~다'로 끝나지 않게 하라
- 구어체 표현 사용 (예: "~했어요", "~인 것 같아요")
3. 용어 및 설명 방식
- 전문 용어 대신 쉬운 단어로 풀어서 설명
- 비유나 은유를 활용하여 복잡한 개념 설명
- 수사의문문 활용하여 독자와 소통하는 느낌 주기
주의사항: 너무 가벼운 톤은 지양하고, 주제의 중요성을 해치지 않는 선에서 친근함 유지
(예시: 잇님들~ 오레오 코카콜라맛이새로 출시가 됐다는거 알고 계셨나요?!ㅎ 오레오 코카콜라맛은 어떤지 솔직평과구매정보, 가격, 칼로리 등에 대해 자세~ 히 적어보도록 할께요! 오레오를 좋아하는 아들에게간식으로 오레오 코카콜라맛을 줬더니맛있다고 좋아하더라구요. 콜라향이 나서 더 마음에 든다며ㅎ개인적으로는 별 ⭐️⭐️⭐️.요건 개인차가 있을거 같아요~)
""",
"일반": """
#일반적인 블로그 포스팅 스타일 가이드
1. 톤과 어조
- 중립적이고 객관적인 톤 유지
- 적절한 존댓말 사용 (예: "~합니다", "~입니다")
2. 내용 구조 및 전개
- 명확한 주제 제시로 시작
- 논리적인 순서로 정보 전개
- 주요 포인트를 강조하는 소제목 활용
- 적절한 길이의 단락으로 구성
3. 용어 및 설명 방식
- 일반적으로 이해하기 쉬운 용어 선택
- 필요시 간단한 설명 추가
- 객관적인 정보 제공에 중점
4. 텍스트 구조화
- 불릿 포인트나 번호 매기기를 활용하여 정보 구조화
- 중요한 정보는 굵은 글씨나 기울임꼴로 강조
5. 독자 상호작용
- 적절히 독자의 생각을 묻는 질문 포함
- 추가 정보를 찾을 수 있는 키워드 제시
6. 마무리
- 주요 내용 간단히 요약
- 추가 정보에 대한 안내 제공
주의사항: 너무 딱딱하거나 지루하지 않도록 균형 유지
예시: "최근 환경 문제가 대두되면서 '제로 웨이스트' 라이프스타일에 대한 관심이 높아지고 있습니다. 제로 웨이스트란 일상생활에서 발생하는 쓰레기를 최소화하는 생활 방식을 말합니다. 이 글에서는 제로 웨이스트의 개념, 실천 방법, 그리고 그 효과에 대해 알아보겠습니다. 먼저 제로 웨이스트의 정의부터 살펴보면...
""",
"전문적인": """
#전문적인 블로그 포스팅 스타일 가이드
1. 톤과 구조
- 공식적이고 학술적인 톤 사용
- 객관적이고 분석적인 접근 유지
- 명확한 서론, 본론, 결론 구조
- 체계적인 논점 전개
- 세부 섹션을 위한 명확한 소제목 사용
2. 내용 구성 및 전개
- 복잡한 개념을 정확히 전달할 수 있는 문장 구조 사용
- 논리적 연결을 위한 전환어 활용
- 해당 분야의 전문 용어 적극 활용 (필요시 간략한 설명 제공)
- 심층적인 분석과 비판적 사고 전개
- 다양한 관점 제시 및 비교
3. 데이터 및 근거 활용
- 통계, 연구 결과, 전문가 의견 등 신뢰할 수 있는 출처 인용
- 필요시 각주나 참고문헌 목록 포함
- 수치 데이터는 텍스트로 명확히 설명
4. 텍스트 구조화
- 논리적 구조를 강조하기 위해 번호 매기기 사용
- 핵심 개념이나 용어는 기울임꼴로 강조
- 긴 인용문은 들여쓰기로 구분
5. 마무리
- 핵심 논점 재강조
- 향후 연구 방향이나 실무적 함의 제시
주의사항: 전문성을 유지하되, 완전히 이해하기 어려운 수준은 지양
예시: "본 연구에서는 인공지능(AI)의 윤리적 함의에 대해 고찰한다. 특히, 자율주행 자동차의 의사결정 알고리즘에서 발생할 수 있는 윤리적 딜레마에 초점을 맞춘다. Bonnefon et al. (2016)의 연구에 따르면, 자율주행 차량의 알고리즘이 직면할 수 있는 윤리적 선택의 복잡성이 지적된 바 있다. 본고에서는 이러한 윤리적 딜레마를 세 가지 주요 관점에서 분석한다: 1) 공리주의적 접근, 2) 의무론적 접근, 3) 덕 윤리적 접근. 각 접근법의 장단점을 비교 분석하고, 이를 바탕으로 자율주행 차량의 윤리적 의사결정 프레임워크를 제안하고자 한다...
"""
}
return prompts.get(style, "포스팅 스타일 프롬프트")
def split_titles(suggested_titles):
titles = suggested_titles.split('\n')
titles = [re.sub(r'^(1\.|2\.|3\.|4\.|5\.|6\.|7\.|8\.|9\.|10\.|## |# |\* |\*\* |\*\*\*)', '', title.strip()) for title in titles if title.strip()]
titles = titles[::-1] # 리스트를 역순으로 정렬
titles += [""] * (10 - len(titles)) # 10개보다 적으면 빈 문자열로 채우기
return titles[:10] # 최대 10개의 제목만 반환
class PDF(FPDF):
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 format_filename(text):
if not isinstance(text, str):
text = str(text) # 문자열이 아닌 경우 문자열로 변환
text = re.sub(r'[^\w\s-]', '', text)
return text[:50].strip()
def save_content_to_pdf(blog_post):
return save_to_pdf(blog_post, "") # user_topic 파라미터 제거
# 기존의 save_to_pdf 함수를 다음과 같이 수정
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(title)}.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
title = "여행"
with gr.Blocks() as demo:
gr.Markdown(f"# {title}")
gr.Markdown("### 1단계: 포스팅 카테고리를 지정해주세요", elem_id="step-title")
category = gr.Radio(choices=["여행 단일", "여행 코스"], label="포스팅 카테고리", value="여행 단일")
gr.Markdown("---\n\n")
gr.Markdown("### 2단계: 포스팅 스타일을 선택해주세요", elem_id="step-title")
style = gr.Radio(choices=["친근한", "일반", "전문적인"], label="포스팅 스타일", value="친근한")
gr.Markdown("---\n\n")
gr.Markdown("### 3단계: 참고 글을 입력하세요", elem_id="step-title")
references1 = gr.Textbox(label="참고 글 1", placeholder="참고할 글을 복사하여 붙여넣으세요", lines=10, visible=True)
references2 = gr.Textbox(label="참고 글 2", placeholder="참고할 글을 복사하여 붙여넣으세요", lines=10, visible=True)
references3 = gr.Textbox(label="참고 글 3", placeholder="참고할 글을 복사하여 붙여넣으세요", lines=10, visible=True)
gr.Markdown("---\n\n")
gr.Markdown("### 4단계: 제목 추천하기", elem_id="step-title")
title_suggestions = gr.Textbox(label="제목 추천", lines=10)
# 10개의 텍스트 출력창 추가
title_output_1 = gr.Textbox(label="제목 1", lines=1)
title_output_2 = gr.Textbox(label="제목 2", lines=1)
title_output_3 = gr.Textbox(label="제목 3", lines=1)
title_output_4 = gr.Textbox(label="제목 4", lines=1)
title_output_5 = gr.Textbox(label="제목 5", lines=1)
title_output_6 = gr.Textbox(label="제목 6", lines=1)
title_output_7 = gr.Textbox(label="제목 7", lines=1)
title_output_8 = gr.Textbox(label="제목 8", lines=1)
title_output_9 = gr.Textbox(label="제목 9", lines=1)
title_output_10 = gr.Textbox(label="제목 10", lines=1)
title_btn = gr.Button("제목 추천하기")
title_btn.click(fn=lambda cat, sty, ref1, ref2, ref3: suggest_title({"category": cat, "style": sty, "references1": ref1, "references2": ref2, "references3": ref3}),
inputs=[category, style, references1, references2, references3], outputs=[title_suggestions])
title_suggestions.change(fn=split_titles,
inputs=[title_suggestions],
outputs=[title_output_1, title_output_2, title_output_3, title_output_4, title_output_5, title_output_6, title_output_7, title_output_8, title_output_9, title_output_10])
gr.Markdown("---\n\n")
gr.Markdown("### 5단계: 블로그 제목을 입력하세요", elem_id="step-title")
blog_title = gr.Textbox(label="블로그 제목", placeholder="블로그 제목을 입력해주세요")
gr.Markdown("---\n\n")
gr.Markdown("### 6단계: 아웃라인을 작성해주세요", elem_id="step-title")
gr.HTML("[아웃라인에서 나온 결과를 수정해서 사용해주세요]")
outline_generate_btn = gr.Button("아웃라인 생성하기")
outline_result = gr.Textbox(label="아웃라인 결과", lines=15)
outline_input = gr.Textbox(label="작성할 아웃라인을 입력해주세요", placeholder="생성된 아웃라인 복사, 수정해서 사용하세요", lines=10)
outline_generate_btn.click(fn=lambda cat, sty, ref1, ref2, ref3, tit: generate_outline({"category": cat, "style": sty, "references1": ref1, "references2": ref2, "references3": ref3, "title": tit}),
inputs=[category, style, references1, references2, references3, blog_title], outputs=[outline_result])
gr.Markdown("---\n\n")
gr.Markdown("### 7단계: 글 생성하기", elem_id="step-title")
gr.HTML("[아웃라인을 확인하세요]")
generate_btn = gr.Button("블로그 글 생성하기")
output = gr.Textbox(label="생성된 블로그 글", lines=30)
new_session_flag = gr.State(False)
save_pdf_btn = gr.Button("PDF로 저장하기")
pdf_output = gr.File(label="생성된 PDF 파일")
save_pdf_btn.click(fn=save_content_to_pdf, inputs=[output], outputs=[pdf_output])
demo.launch()
gr.HTML("""
""")