import gradio as gr
import random
import os
import re
from huggingface_hub import InferenceClient
from fpdf import FPDF
from datetime import datetime
def create_client(model_name):
return InferenceClient(model_name, token=os.getenv("HF_TOKEN"))
client = create_client("CohereForAI/c4ai-command-r-plus")
def call_api(content, system_message, max_tokens, temperature, top_p):
messages = [{"role": "system", "content": system_message}, {"role": "user", "content": content}]
random_seed = random.randint(0, 1000000)
response = client.chat_completion(messages=messages, max_tokens=max_tokens, temperature=temperature, top_p=top_p, seed=random_seed)
return response.choices[0].message.content
def analyze_info(category, style, topic, references1, references2, references3):
return f"선택한 카테고리: {category}\n선택한 포스팅 스타일: {style}\n블로그 주제: {topic}\n참고 글1: {references1}\n참고 글2: {references2}\n참고 글3: {references3}"
def suggest_title(category, style, topic, references1, references2, references3, category_prompt, style_prompt, max_tokens, temperature, top_p):
full_content = analyze_info(category, style, topic, references1, references2, references3)
combined_prompt = f"{category_prompt}\n\n{style_prompt}"
modified_text = call_api(full_content, combined_prompt, max_tokens, temperature, top_p)
return modified_text
def generate_outline(category, style, topic, references1, references2, references3, title, category_prompt, style_prompt, max_tokens, temperature, top_p):
full_content = analyze_info(category, style, topic, references1, references2, references3)
content = f"{full_content}\nTitle: {title}"
combined_prompt = f"{category_prompt}\n\n{style_prompt}"
modified_text = call_api(content, combined_prompt, max_tokens, temperature, top_p)
return modified_text
def generate_blog_post(category, style, topic, references1, references2, references3, title, outline, category_prompt, style_prompt, max_tokens, temperature, top_p):
full_content = analyze_info(category, style, topic, references1, references2, references3)
content = f"{full_content}\nTitle: {title}\nOutline: {outline}"
combined_prompt = f"{category_prompt}\n\n{style_prompt}"
modified_text = call_api(content, combined_prompt, max_tokens, temperature, top_p)
formatted_text = modified_text.replace('\n', '\n\n')
return formatted_text
def get_title_prompt(category):
if (category == "고객반응형"):
return """
#블로그 제목 생성 규칙(상품리뷰_고객반응형)
##[기본규칙]
1. 반드시 한국어(한글)로 작성하라.
2. 너는 가장 주목받는 마케터이며 블로그 마케팅 전문가이다.
3. 특히 너는 '고객반응 상품리뷰' 전문 블로그 마케터이다.
4. 상품 리뷰를 작성하되, 고객의 실제 반응이 포함된 글을 작성하라.
5. 선택된 포스팅 스타일을 적용하여 글을 작성하라.
##[블로그 제목 작성 규칙]
1. 상품리뷰 블로그 포스팅중 고객의 실제 반응에 특화된 제목을 작성하라.
2. 참고글을 바탕으로 블로그 제목 10개를 작성하고 제목 10개만 출력하라.
3. 제목은 40자 이내로 작성하라.
4. 사용자가 입력한 블로그 주제, 핵심키워드(Topic), 상품명이 문장 앞쪽에 들어가도록 제목을 작성하라.
5. '쿠팡'이란 단어는 제목에 표현하지 말 것.
"""
elif (category == "1개 기능집중형"):
return """
#블로그 제목 생성 규칙(1개 기능집중형)
"""
def get_outline_prompt(category):
if (category == "고객반응형"):
return """
#블로그 소주제(Subtitle) 아웃라인 생성 규칙
##[기본규칙]
1. 반드시 한국어(한글)로 작성하라.
2. 너는 가장 주목받는 마케터이며 블로그 마케팅 전문가이다.
3. 특히 너는 '고객반응 상품리뷰' 전문 블로그 마케터이다.
4. 상품 리뷰를 작성하되, 고객의 실제 반응이 포함된 글을 작성하라.
5. 선택된 포스팅 스타일을 적용하여 글을 작성하라.
##[소주제 작성규칙]
1. 블로그 글을 작성하기 위한 소주제를 작성하라.
2. 반드시 상품의 고객의 실제 반응이 포함된 주제로만 소주제를 생성하라.
3. 소제목으로 사용할 수 있도록 30자 이내로 작성하라.
4. 독자가 얻고자 하는 흥미로운 정보와 고객의 실제 반응을 제공하도록 소주제를 작성하라.
5. 반드시 [소주제 구성]에 맞게 출력하라.
6. 상품명은 소주제에 포함하지 말 것.
##[소주제 구성]
1. 반드시 [도입부] - 1개, [본론1~5] - 5개, [결론] - 1개로 구성하여 출력하라.
2. 반드시 [도입부]와 [결론]의 제목이 중복되지 않도록 작성하라.
3. [도입부]에는 고객의 실제 리뷰를 분석하여 공감, 문제제기, 구매포인트 등을 제시하라.
"""
elif (category == "1개 기능집중형"):
return """
#블로그 소주제 생성 규칙(1개 기능집중형)
"""
def get_blog_post_prompt(category):
if (category == "고객반응형"):
return """
#블로그 텍스트 생성 규칙(상품리뷰_고객반응형)
##[기본규칙]
1. 반드시 한국어(한글)로 작성하라.
2. 너는 가장 주목받는 마케터이며 블로그 마케팅 전문가이다.
3. 특히 너는 '고객반응 상품리뷰' 전문 블로그 마케터이다.
4. 상품 리뷰를 작성하되, 고객의 실제 반응이 포함된 글을 작성하라.
5. 선택된 포스팅 스타일을 적용하여 글을 작성하라.
##기능집중형 상품리뷰 생성규칙]
1. 반드시 입력된 [소주제]에 맞게 텍스트를 작성하라.
2. 블로그주제와 참고글을 바탕으로 상품명을 파악하여 기억하고, 소주제에서 반드시 상품명을 최대 1회만 출력되게하라.
3. 소주제별 각각 400자 이상으로 작성하고, 반드시 전체글이 2200자 이상 되도록 작성하라.
4. 전체 맥락을 이해하고 문장의 일관성을 유지하되, 내용이 중복되지 않게 작성하라.
5. 절대로 참고글을 한문장 이상 그대로 출력하지 말 것.
6. 상품에 대한 분석(수치를 포함), 기능과 사용법등의 정보를 작성하고 그에 대한 고객의 실제 반응을 포함하여 리뷰를 작성하라.
7. 내용은 긍정적으로 작성하되, 고객의 실제 반응이 돋보이도록 작성하라.
8. 고객의 실제 리뷰를 바탕으로 포스팅 스타일에 맞는 상황을 연출하라.
9. 고객의 실제 리뷰를 문장에 반영할때는 '구매자의 리뷰', '고객의 리뷰', '사용자 리뷰' 등의 다양한 표현을 사용하라.
##[제외 규칙]
1. 반드시 참고글의 링크(URL)는 제외하라.
2. 참고글에서 '링크를 확인해주세요'와 같은 링크 이동의 문구는 제외하라.
3. 참고글에 있는 작성자, 화자, 유튜버, 기자(Writer, speaker, YouTuber, reporter)의 이름, 애칭, 닉네임(Name, Nkickname)은 반드시 제외하라.
4. '업체로 부터 제공 받아서 작성', '쿠팡 파트너스'등의 표현을 반드시 제외하라.
"""
elif (category == "1개 기능집중형"):
return """
#블로그 텍스트 생성 규칙(1개 기능집중형)
"""
def get_style_prompt(style):
prompts = {
"친근한": """
#친근한 블로그 포스팅 스타일 가이드
1. 톤과 어조
- 대화하듯 편안하고 친근한 말투 사용
- 독자를 "여러분" 또는 "독자님들"로 지칭
- 적절한 이모지를 sparse하게 사용하여 친근감 표현
2. 문장 및 내용 구성
- 짧고 간결한 문장 위주로 작성
- 구어체 표현 사용 (예: "~했어요", "~인 것 같아요")
- '~니다', '~했죠'는 사용하지 말 것.
- 개인적인 경험이나 일화로 시작
- 실생활에서 쉽게 적용할 수 있는 팁 제공
- 독자의 공감을 얻을 수 있는 사례 포함
3. 용어 및 설명 방식
- 전문 용어 대신 쉬운 단어로 풀어서 설명
- 비유나 은유를 활용하여 복잡한 개념 설명
- 수사의문문 활용하여 독자와 소통하는 느낌 주기
4. 독자와의 상호작용
- 독자의 의견을 물어보는 질문 포함
- 댓글 달기를 독려하는 문구 사용
5. 이모지 활용
- 주요 포인트나 새로운 섹션을 시작할 때만 관련 이모지 사용
- 전체 글에서 3-5개 정도의 이모지만 사용하여 과도하지 않게 유지
6. 마무리
- 친근하고 격려하는 톤으로 마무리
- 다음 포스팅에 대한 기대감 유발
주의사항: 너무 가벼운 톤은 지양하고, 주제의 중요성을 해치지 않는 선에서 친근함 유지
예시: "여러분, 오늘 하루는 어떠셨나요? 😊 저는 오늘 재미있는 경험을 했어요. 바로 '미니멀 라이프'에 도전해본 건데요. 처음에는 좀 막막했지만, 생각보다 즐거운 경험이었어요! 여러분도 한번 따라해보시겠어요? 제가 알게 된 꿀팁들을 하나하나 알려드릴게요.
""",
"일반": """
#일반적인 블로그 포스팅 스타일 가이드
1. 톤과 어조
- 중립적이고 객관적인 톤 유지
- 적절한 존댓말 사용 (예: "~합니다", "~입니다")
2. 내용 구조 및 전개
- 명확한 주제 제시로 시작
- 논리적인 순서로 정보 전개
- 주요 포인트를 강조하는 소제목 활용
- 적절한 길이의 단락으로 구성
3. 용어 및 설명 방식
- 일반적으로 이해하기 쉬운 용어 선택
- 필요시 간단한 설명 추가
- 객관적인 정보 제공에 중점
4. 텍스트 구조화
- 불릿 포인트나 번호 매기기를 활용하여 정보 구조화
- 중요한 정보는 굵은 글씨나 기울임꼴로 강조
5. 독자 상호작용
- 적절히 독자의 생각을 묻는 질문 포함
- 추가 정보를 찾을 수 있는 키워드 제시
6. 마무리
- 주요 내용 간단히 요약
- 추가 정보에 대한 안내 제공
주의사항: 너무 딱딱하거나 지루하지 않도록 균형 유지
예시: "최근 환경 문제가 대두되면서 '제로 웨이스트' 라이프스타일에 대한 관심이 높아지고 있습니다. 제로 웨이스트란 일상생활에서 발생하는 쓰레기를 최소화하는 생활 방식을 말합니다. 이 글에서는 제로 웨이스트의 개념, 실천 방법, 그리고 그 효과에 대해 알아보겠습니다. 먼저 제로 웨이스트의 정의부터 살펴보면...
""",
"전문적인": """
#전문적인 블로그 포스팅 스타일 가이드
1. 톤과 구조
- 공식적이고 학술적인 톤 사용
- 객관적이고 분석적인 접근 유지
- 명확한 서론, 본론, 결론 구조
- 체계적인 논점 전개
- 세부 섹션을 위한 명확한 소제목 사용
2. 내용 구성 및 전개
- 복잡한 개념을 정확히 전달할 수 있는 문장 구조 사용
- 논리적 연결을 위한 전환어 활용
- 해당 분야의 전문 용어 적극 활용 (필요시 간략한 설명 제공)
- 심층적인 분석과 비판적 사고 전개
- 다양한 관점 제시 및 비교
3. 데이터 및 근거 활용
- 통계, 연구 결과, 전문가 의견 등 신뢰할 수 있는 출처 인용
- 필요시 각주나 참고문헌 목록 포함
- 수치 데이터는 텍스트로 명확히 설명
4. 텍스트 구조화
- 논리적 구조를 강조하기 위해 번호 매기기 사용
- 핵심 개념이나 용어는 기울임꼴로 강조
- 긴 인용문은 들여쓰기로 구분
5. 마무리
- 핵심 논점 재강조
- 향후 연구 방향이나 실무적 함의 제시
주의사항: 전문성을 유지하되, 완전히 이해하기 어려운 수준은 지양
예시: "본 연구에서는 인공지능(AI)의 윤리적 함의에 대해 고찰한다. 특히, 자율주행 자동차의 의사결정 알고리즘에서 발생할 수 있는 윤리적 딜레마에 초점을 맞춘다. Bonnefon et al. (2016)의 연구에 따르면, 자율주행 차량의 알고리즘이 직면할 수 있는 윤리적 선택의 복잡성이 지적된 바 있다. 본고에서는 이러한 윤리적 딜레마를 세 가지 주요 관점에서 분석한다: 1) 공리주의적 접근, 2) 의무론적 접근, 3) 덕 윤리적 접근. 각 접근법의 장단점을 비교 분석하고, 이를 바탕으로 자율주행 차량의 윤리적 의사결정 프레임워크를 제안하고자 한다...
"""
}
return prompts.get(style, "포스팅 스타일 프롬프트")
def get_style_description(style):
descriptions = {
"친근한": "독자와 가까운 친구처럼 대화하는 듯한 친근한 스타일입니다.",
"일반": "일반적이고 중립적인 톤으로 정보를 전달하는 스타일입니다.",
"전문적인": "전문가의 시각에서 깊이 있는 정보를 전달하는 스타일입니다."
}
return descriptions.get(style, "포스팅 스타일을 선택하세요.")
def update_prompts(category, style):
title_prompt = get_title_prompt(category)
outline_prompt = get_outline_prompt(category)
blog_post_prompt = get_blog_post_prompt(category)
style_prompt = get_style_prompt(style)
style_description = get_style_description(style)
return title_prompt, outline_prompt, blog_post_prompt, style_prompt, style_description
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):
text = re.sub(r'[^\w\s-]', '', text)
return text[:50].strip()
def extract_first_recommended_title(blog_post):
match = re.search(r'## 추천 제목\s*\n\s*(?:\n\s*)*\d+\.\s*(.*?)\s*(?:\n|$)', blog_post, re.DOTALL)
if match:
title = match.group(1).strip()
print(f"Extracted title: {title}")
return title
print("No title found")
return "블로그_글"
def save_to_pdf(blog_post):
pdf = PDF()
pdf.add_page()
# 블로그 첫 줄을 제목으로 사용
lines = blog_post.split('\n')
title = lines[0].strip()
content = '\n'.join(lines[1:]).strip()
filename = 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
# save_content_to_pdf 함수도 간단하게 수정
def save_content_to_pdf(blog_post):
return save_to_pdf(blog_post)
title = "상품리뷰 참고글 직접입력버전"
with gr.Blocks() as demo:
gr.Markdown(f"# {title}")
gr.Markdown("### 1단계: 포스팅 카테고리를 지정해주세요", elem_id="step-title")
category = gr.Radio(choices=["고객반응형","1개 기능집중형"], label="포스팅 카테고리", value="고객반응형")
gr.Markdown("---\n\n")
gr.Markdown("### 2단계: 포스팅 스타일을 선택해주세요", elem_id="step-title")
style = gr.Radio(choices=["친근한", "일반", "전문적인"], label="포스팅 스타일", value="친근한")
style_description = gr.Markdown(f"_{get_style_description('친근한')}_", elem_id="style-description")
gr.Markdown("---\n\n")
gr.Markdown("### 3단계: 블로그 주제, 또는 키워드를 상세히 입력하세요", elem_id="step-title")
topic = gr.Textbox(label="블로그 주제(예시: 오징어 무침회(X), 오징어 무침회 레시피(O))", placeholder="예시: 여행지 추천(X), 8월 국내 여행지 추천(O)")
gr.Markdown("---\n\n")
gr.Markdown("### 4단계: 참고 글을 입력하세요", 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("### 5단계: 블로그 제목을 입력하세요", elem_id="step-title")
with gr.Accordion("제목 설정", visible=True):
title_system_message = gr.Textbox(label="카테고리 프롬프트", value=get_title_prompt("고객반응형"), lines=15, visible=True)
title_max_tokens = gr.Slider(label="Max Tokens", minimum=1000, maximum=8000, value=5000, step=1000)
title_temperature = gr.Slider(label="Temperature", minimum=0.1, maximum=1.0, value=0.8, step=0.1)
title_top_p = gr.Slider(label="Top P", minimum=0.1, maximum=1.0, value=0.95, step=0.05)
title_btn = gr.Button("제목 추천하기")
title_suggestions = gr.Textbox(label="제목 추천", lines=10)
title_btn.click(fn=suggest_title, inputs=[category, style, topic, references1, references2, references3, title_system_message, style, title_max_tokens, title_temperature, title_top_p], outputs=[title_suggestions])
gr.HTML("[제목 추천 항목을 참고하여 하나를 복사하여 사용하셔도 됩니다.]")
blog_title = gr.Textbox(label="블로그 제목", placeholder="블로그 제목을 입력해주세요")
gr.Markdown("---\n\n")
gr.Markdown("### 6단계: 아웃라인을 작성해주세요", elem_id="step-title")
gr.HTML("[아웃라인에서 나온 결과를 수정해서 사용해주세요]")
with gr.Accordion("아웃라인 설정", visible=True):
outline_system_message = gr.Textbox(label="카테고리 프롬프트", value=get_outline_prompt("고객반응형"), lines=20, visible=True)
outline_max_tokens = gr.Slider(label="Max Tokens", minimum=1000, maximum=8000, value=6000, step=1000)
outline_temperature = gr.Slider(label="Temperature", minimum=0.1, maximum=1.0, value=0.8, step=0.1)
outline_top_p = gr.Slider(label="Top P", minimum=0.1, maximum=1.0, value=0.95, step=0.05)
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=generate_outline, inputs=[category, style, topic, references1, references2, references3, blog_title, outline_system_message, style, outline_max_tokens, outline_temperature, outline_top_p], outputs=[outline_result])
gr.Markdown("---\n\n")
gr.Markdown("### 7단계: 글 생성하기", elem_id="step-title")
gr.HTML("[아웃라인 별 텍스트량을 정하고 글 생성하기 버튼을 선택해주세요]")
with gr.Accordion("블로그 글 설정", visible=True):
blog_system_message = gr.Textbox(label="카테고리 프롬프트", value=get_blog_post_prompt("고객반응형"), lines=20, visible=True)
blog_max_tokens = gr.Slider(label="Max Tokens", minimum=1000, maximum=12000, value=8000, step=1000)
blog_temperature = gr.Slider(label="Temperature", minimum=0.1, maximum=1.0, value=0.8, step=0.1)
blog_top_p = gr.Slider(label="Top P", minimum=0.1, maximum=1.0, value=0.95, step=0.05)
generate_btn = gr.Button("블로그 글 생성하기")
output = gr.Textbox(label="생성된 블로그 글", lines=30)
generate_btn.click(fn=generate_blog_post, inputs=[category, style, topic, references1, references2, references3, blog_title, outline_input, blog_system_message, style, blog_max_tokens, blog_temperature, blog_top_p], outputs=[output])
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])
def update_prompts_and_description(category, style):
title_prompt, outline_prompt, blog_post_prompt, style_prompt, style_description = update_prompts(category, style)
return title_prompt, outline_prompt, blog_post_prompt, style_description
category.change(fn=update_prompts_and_description, inputs=[category, style], outputs=[title_system_message, outline_system_message, blog_system_message, style_description])
style.change(fn=update_prompts_and_description, inputs=[category, style], outputs=[title_system_message, outline_system_message, blog_system_message, style_description])
demo.launch()
gr.HTML("""
""")