Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -20,8 +20,8 @@ logging.basicConfig(level=logging.INFO)
|
|
| 20 |
# -------------------------------
|
| 21 |
# 상수 정의 (향후 조정 및 유지보수 용이하도록)
|
| 22 |
# -------------------------------
|
| 23 |
-
TARGET_CHAR_LENGTH =
|
| 24 |
-
MIN_SECTION_LENGTH =
|
| 25 |
MAX_TOKENS = 15000 # Gemini API 최대 토큰 수
|
| 26 |
TEMPERATURE = 0.85 # Gemini API 온도 값
|
| 27 |
TOP_P = 0.9 # Gemini API top_p 값
|
|
@@ -86,112 +86,109 @@ def fetch_crawl_results(query):
|
|
| 86 |
def get_style_prompt(style="친근한"):
|
| 87 |
prompts = {
|
| 88 |
"친근한": """
|
| 89 |
-
[친근한
|
| 90 |
1. 톤과 어조
|
| 91 |
- 대화하듯 편안하고 친근한 말투 사용
|
| 92 |
-
-
|
| 93 |
2. 문장 및 어투
|
| 94 |
- 반드시 '해요체'로 작성, 절대 '습니다'체를 사용하지 말 것
|
| 95 |
- '~요'로 끝나도록 작성, '~다'로 끝나지 않게 하라
|
| 96 |
- 구어체 표현 사용 (예: "~했어요", "~인 것 같아요")
|
| 97 |
- 이모티콘은 사용하지 마세요
|
| 98 |
3. 용어 및 설명 방식
|
| 99 |
-
- 전문 용어
|
| 100 |
- 비유나 은유를 활용하여 복잡한 개념 설명
|
| 101 |
-
- 수사의문문 활용하여 독자와 소통하는 느낌 주기 (예: "여러분도 이런
|
| 102 |
-
-
|
| 103 |
-
4. 정보 전달 방식
|
| 104 |
-
- 개인
|
| 105 |
-
-
|
| 106 |
-
-
|
| 107 |
5. 독자와의 상호작용
|
| 108 |
-
- 독자의 의견을 물어보는 질문 포함
|
| 109 |
-
-
|
| 110 |
-
|
|
|
|
| 111 |
""",
|
| 112 |
"일반적인": """
|
| 113 |
-
#일반적인
|
| 114 |
1. 톤과 어조
|
| 115 |
- 중립적이고 객관적인 톤 유지
|
| 116 |
- 적절한 존댓말 사용 (예: "~합니다", "~입니다")
|
| 117 |
-
- 정보 전달
|
| 118 |
2. 내용 구조 및 전개
|
| 119 |
-
- 명확한
|
| 120 |
-
- 논리적인 순서로 정보 전개 (
|
| 121 |
-
-
|
| 122 |
- 적절한 길이의 단락으로 구성
|
| 123 |
3. 용어 및 설명 방식
|
| 124 |
- 일반적으로 이해하기 쉬운 용어 선택
|
| 125 |
-
- 필요시
|
| 126 |
- 객관적인 정보 제공에 중점
|
| 127 |
-
-
|
| 128 |
-
4. 정보 전달 방식
|
| 129 |
-
-
|
| 130 |
-
-
|
| 131 |
-
- 시
|
| 132 |
-
-
|
| 133 |
5. 독자 상호작용
|
| 134 |
- 적절히 독자의 생각을 묻는 질문 포함
|
| 135 |
- 추가 정보를 찾을 수 있는 키워드 제시
|
| 136 |
-
- 실용적인
|
| 137 |
6. 마무리
|
| 138 |
- 주요 내용 간단히 요약
|
| 139 |
-
-
|
| 140 |
-
-
|
| 141 |
-
주의사항: 너무 딱딱하거나 지루하지 않도록
|
| 142 |
""",
|
| 143 |
"전문적인": """
|
| 144 |
-
#전문적인
|
| 145 |
1. 톤과 구조
|
| 146 |
- 공식적이고 전문적인 톤 사용
|
| 147 |
- 객관적이고 분석적인 접근 유지
|
| 148 |
-
- 명확한 서론(개요), 본론(상세 분석), 결론(종합 평가) 구조
|
| 149 |
- 체계적인 정보 전개
|
| 150 |
- 세부 섹션을 위한 명확한 소제목 사용
|
| 151 |
2. 내용 구성 및 전개
|
| 152 |
-
-
|
| 153 |
- 논리적 연결을 위한 전환어 활용
|
| 154 |
-
- 전문 용어 적절히 활용 (필요시 간략한 설명 제공)
|
| 155 |
- 심층적인 분석과 비판적 평가 제공
|
| 156 |
-
- 다양한 관점
|
| 157 |
3. 데이터 및 근거 활용
|
| 158 |
-
- 통계,
|
| 159 |
-
-
|
| 160 |
-
- 수치 데이터는 명확히 설명 (
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
-
|
| 164 |
-
-
|
| 165 |
-
-
|
| 166 |
-
- 체계적인 문제 해결 접근법 제시
|
| 167 |
5. 마무리
|
| 168 |
- 핵심 정보 요약 및 종합 평가
|
| 169 |
-
-
|
| 170 |
-
-
|
| 171 |
-
주의사항: 전문성을 유지하되,
|
| 172 |
"""
|
| 173 |
}
|
| 174 |
return prompts.get(style, prompts["친근한"])
|
| 175 |
|
| 176 |
-
def
|
| 177 |
prompts = [
|
| 178 |
"""
|
| 179 |
-
[중요:
|
| 180 |
이 규칙을 반드시 따르세요. 어떤 상황에서도 예외는 없습니다:
|
| 181 |
1. 마크다운 문법(**, *, #, -, 1., 2., 3.)을 절대 사용하지 마세요.
|
| 182 |
2. 모든 소제목은 번호 없이 일반 문장으로 작성하세요.
|
| 183 |
3. 모든 목록은 불릿이나 번호 없이 자연스러운 문장으로 서술하세요.
|
| 184 |
4. "참고글", "참고글에 따르면" 등의 표현을 절대 사용하지 마세요.
|
| 185 |
-
5.
|
| 186 |
-
6. 글의 주제는 반드시 주어진 참고글
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
- 실용적인 적용 방법이나 사례
|
| 193 |
-
- 다양한 관점이나 비교 분석
|
| 194 |
-
- 향후 전망이나 결론
|
| 195 |
"""
|
| 196 |
]
|
| 197 |
return random.choice(prompts)
|
|
@@ -200,10 +197,10 @@ def remove_unwanted_phrases(text):
|
|
| 200 |
unwanted_phrases = [
|
| 201 |
'여러분', '최근', '마지막으로', '결론적으로', '결국',
|
| 202 |
'종합적으로', '따라서', '마무리', '끝으로', '요약',
|
| 203 |
-
'한 줄 요약', '정리하자면', '총정리', '
|
| 204 |
-
'이상으로', '추천드립니다', '
|
| 205 |
-
'
|
| 206 |
-
'
|
| 207 |
]
|
| 208 |
words = re.findall(r'\S+|\n', text)
|
| 209 |
result_words = [word for word in words if not any(phrase in word for phrase in unwanted_phrases)]
|
|
@@ -227,32 +224,43 @@ def post_process_blog(blog_content, style="친근한"):
|
|
| 227 |
blog_content = re.sub(r'됩니다', '돼요', blog_content)
|
| 228 |
blog_content = re.sub(r'입니다', '이에요', blog_content)
|
| 229 |
|
| 230 |
-
# 과장된 표현 정리
|
| 231 |
exaggerated_expressions = [
|
| 232 |
-
(r'
|
| 233 |
-
(r'
|
| 234 |
-
(r'
|
| 235 |
-
(r'
|
| 236 |
-
(r'
|
| 237 |
-
(r'
|
| 238 |
-
(r'
|
| 239 |
-
(r'
|
| 240 |
-
(r'
|
| 241 |
-
(r'
|
| 242 |
-
(r'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
(r'환상적인', r'좋은'),
|
| 244 |
-
(r'
|
| 245 |
-
(r'
|
| 246 |
-
(r'전례없는', r'특별한'),
|
| 247 |
-
(r'압도적인', r'주목할 만한'),
|
| 248 |
-
(r'황홀한', r'좋은'),
|
| 249 |
-
(r'천상의', r'우수한'),
|
| 250 |
-
(r'기가 막힌', r'효과적인'),
|
| 251 |
-
(r'끝판왕', r'최상위'),
|
| 252 |
(r'그 자체', r''),
|
|
|
|
| 253 |
(r'이 .{1,10} 그 자체였어요', r'이 \1였어요'),
|
| 254 |
(r'가 .{1,10} 그 자체였어요', r'가 \1였어요'),
|
| 255 |
-
(r'
|
|
|
|
|
|
|
| 256 |
(r'천국', r'좋은 곳'),
|
| 257 |
(r'황홀했어요', r'좋았어요'),
|
| 258 |
(r'환상의', r'좋은')
|
|
@@ -261,12 +269,12 @@ def post_process_blog(blog_content, style="친근한"):
|
|
| 261 |
for pattern, replacement in exaggerated_expressions:
|
| 262 |
blog_content = re.sub(pattern, replacement, blog_content, flags=re.IGNORECASE)
|
| 263 |
|
| 264 |
-
blog_content = re.sub(r'참고글에 따르면', r'알
|
| 265 |
blog_content = re.sub(r'참고글', r'관련 정보', blog_content)
|
| 266 |
|
| 267 |
# Gemini API를 활용한 추가 후처리 프롬프트 (텍스트 보완 목적)
|
| 268 |
prompt = f"""
|
| 269 |
-
다음
|
| 270 |
|
| 271 |
원본 글:
|
| 272 |
{blog_content}
|
|
@@ -275,7 +283,7 @@ def post_process_blog(blog_content, style="친근한"):
|
|
| 275 |
1. 마크다운 형식 및 번호 목록, 불릿 표현 제거
|
| 276 |
2. 소제목은 5개 이하로, 각 소제목 아래 내용은 최소 {MIN_SECTION_LENGTH}자 이상으로 상세하게 서술
|
| 277 |
3. "참고글" 관련 표현 제거
|
| 278 |
-
4.
|
| 279 |
5. 스타일: {style} (자연스러운 문체 사용)
|
| 280 |
"""
|
| 281 |
|
|
@@ -347,7 +355,7 @@ def format_blog_post(blog_post, query=""):
|
|
| 347 |
if title.endswith(f" {ending}"):
|
| 348 |
title = title[:-len(ending)-1]
|
| 349 |
if len(title) < 20 and query:
|
| 350 |
-
title = f"{query}
|
| 351 |
return title
|
| 352 |
|
| 353 |
for i, line in enumerate(lines):
|
|
@@ -389,7 +397,7 @@ def format_blog_post(blog_post, query=""):
|
|
| 389 |
if in_paragraph:
|
| 390 |
formatted_lines.append("</p>")
|
| 391 |
if not title_found:
|
| 392 |
-
default_title = f"{query}
|
| 393 |
if first_non_empty_line:
|
| 394 |
default_title = optimize_title(first_non_empty_line)
|
| 395 |
formatted_lines.insert(0, f'<h1 style="font-size: 1.8em; margin-bottom: 20px; font-weight: bold; color: #222;">{html.escape(default_title)}</h1>')
|
|
@@ -413,7 +421,7 @@ def generate_blog_post(query, style="친근한"):
|
|
| 413 |
# 참고글 수집
|
| 414 |
ref1, ref2, ref3 = fetch_crawl_results(query)
|
| 415 |
style_prompt = get_style_prompt(style)
|
| 416 |
-
format_prompt =
|
| 417 |
|
| 418 |
# 스타일 세부 지시사항 (원본 내용의 특성을 유지)
|
| 419 |
style_specific_instructions = ""
|
|
@@ -424,9 +432,9 @@ def generate_blog_post(query, style="친근한"):
|
|
| 424 |
- 격식체(예: "~합니다", "~입니다") 사용 금지
|
| 425 |
- 대화하듯 편안하게 작성
|
| 426 |
- 짧고 간결한 문장 사용
|
| 427 |
-
-
|
| 428 |
-
-
|
| 429 |
-
-
|
| 430 |
"""
|
| 431 |
elif style == "일반적인":
|
| 432 |
style_specific_instructions = """
|
|
@@ -434,22 +442,20 @@ def generate_blog_post(query, style="친근한"):
|
|
| 434 |
- '습니다체' 사용: "~했습니다", "~입니다", "~하였습니다"
|
| 435 |
- 격식적이지 않은 명확한 표현 사용
|
| 436 |
- 간결하고 명료한 문장 사용
|
| 437 |
-
- 객관적인 사실과
|
| 438 |
-
- 균형 잡힌 시각에서 다양한 관점 제시
|
| 439 |
"""
|
| 440 |
elif style == "전문적인":
|
| 441 |
style_specific_instructions = """
|
| 442 |
이 블로그는 반드시 전문적이고 분석적인 어투로 작성해야 합니다.
|
| 443 |
- '습니다체' 사용: "~했습니다", "~입니다", "~하였습니다"
|
| 444 |
-
- 역사,
|
| 445 |
- 객관적이고 논리적인 분석 중심 작성
|
| 446 |
- 구체적인 데이터 및 수치 포함하여 설명
|
| 447 |
-
- 다양한 관점과 이론적 프레임워크 제시
|
| 448 |
"""
|
| 449 |
|
| 450 |
# Phase 1: 초기 생성
|
| 451 |
initial_prompt = f"""
|
| 452 |
-
주제: {query}
|
| 453 |
참고글 1: {ref1}
|
| 454 |
참고글 2: {ref2}
|
| 455 |
참고글 3: {ref3}
|
|
@@ -460,25 +466,15 @@ def generate_blog_post(query, style="친근한"):
|
|
| 460 |
스타일 세부 지시사항:
|
| 461 |
{style_specific_instructions}
|
| 462 |
특별 지시사항:
|
| 463 |
-
1. 반드
|
| 464 |
2. 마크다운 문법(#, *, -, 1., 2., 등) 사용 금지.
|
| 465 |
3. 소제목은 번호 없이 작성하고, 5개 이하로 제한.
|
| 466 |
4. 모든 목록은 불릿이나 번호 없이 자연스럽게 서술.
|
| 467 |
5. "참고글" 관련 표현 사용 금지.
|
| 468 |
-
6.
|
| 469 |
-
7. 구체적인 정보(
|
| 470 |
8. 글자수가 최소 {TARGET_CHAR_LENGTH}자 이상이어야 함.
|
| 471 |
-
9. 각 소제목 아래 내용은 최소 {MIN_SECTION_LENGTH}자 이상 서술.
|
| 472 |
-
10. 반드시 다음 구성 요소를 포함할 것:
|
| 473 |
-
- 주제에 대한 명확한 소개와 배경
|
| 474 |
-
- 주제의 역사적/이론적 맥락
|
| 475 |
-
- 현재 동향이나 최신 정보
|
| 476 |
-
- 주요 개념이나 원리에 대한 설명
|
| 477 |
-
- 실용적인 적용 방법이나 사례
|
| 478 |
-
- 다양한 관점이나 비교 분석
|
| 479 |
-
- 향후 전망이나 결론
|
| 480 |
"""
|
| 481 |
-
|
| 482 |
first_attempt = call_gemini_api(initial_prompt)
|
| 483 |
first_attempt_cleaned = remove_unwanted_phrases(first_attempt)
|
| 484 |
first_attempt_length = len(first_attempt_cleaned)
|
|
@@ -502,21 +498,20 @@ def generate_blog_post(query, style="친근한"):
|
|
| 502 |
이전에 작성된 글은 목표 글자수인 {TARGET_CHAR_LENGTH}자에 미치지 못합니다. 현재 글자수는 약 {first_attempt_length}자입니다.
|
| 503 |
또한, 각 소제목 아래 내용이 너무 짧고 부실합니다.
|
| 504 |
중요 요구사항:
|
| 505 |
-
1. 글의 처음에 매력적인 제목(
|
| 506 |
2. 글자수를 최소 {TARGET_CHAR_LENGTH}자 이상으로 늘리고, 각 섹션을 상세히 서술.
|
| 507 |
3. 마크다운 형식(#, *, -, 1., 2., 등) 사용 금지.
|
| 508 |
4. 소제목은 번호 없이 작성.
|
| 509 |
5. 모든 목록은 불릿이나 번호 없이 서술.
|
| 510 |
6. "참고글" 관련 표현 사용 금지.
|
| 511 |
-
7.
|
| 512 |
8. 각 소제목 아래 내용을 최소 {MIN_SECTION_LENGTH}자 이상 서술.
|
| 513 |
9. 소제목 수는 5개 이하로 제한.
|
| 514 |
상세 보완:
|
| 515 |
-
-
|
| 516 |
-
- 구체적인
|
| 517 |
-
- 역사
|
| 518 |
-
-
|
| 519 |
-
- 다양한 관점, 쟁점, 논쟁점 등 균형 잡힌 시각 제공.
|
| 520 |
"""
|
| 521 |
revised_attempt = call_gemini_api(revision_prompt)
|
| 522 |
revised_cleaned = remove_unwanted_phrases(revised_attempt)
|
|
@@ -529,7 +524,7 @@ def generate_blog_post(query, style="친근한"):
|
|
| 529 |
# Phase 3: 확장 (Expansion) 시도 (글자수가 부족할 경우)
|
| 530 |
if actual_char_length < TARGET_CHAR_LENGTH * 0.8:
|
| 531 |
expansion_prompt = f"""
|
| 532 |
-
다음
|
| 533 |
원본 글:
|
| 534 |
{final_post}
|
| 535 |
문제점:
|
|
@@ -540,17 +535,17 @@ def generate_blog_post(query, style="친근한"):
|
|
| 540 |
{style_specific_instructions}
|
| 541 |
요구사항:
|
| 542 |
1. 각 소제목 아래의 내용을 최소 {MIN_SECTION_LENGTH}자 이상 대폭 확장.
|
| 543 |
-
2. 구체적인
|
| 544 |
3. 마크다운 형식(#, *, -, 1., 2., 등) 사용 금지.
|
| 545 |
4. {style} 스타일에 맞춰 일관되게 작성.
|
| 546 |
5. 소제목 구조는 유지하되, 각 섹션 내용을 3배 이상 풍부하게 확장.
|
| 547 |
-
6.
|
| 548 |
7. 전체 글자수를 최소 {TARGET_CHAR_LENGTH}자 이상 달성.
|
| 549 |
8. 소제목 수는 최대 5개로 제한.
|
| 550 |
-
9. 실용적 정보(
|
| 551 |
-
10.
|
| 552 |
-
11. 주
|
| 553 |
-
12.
|
| 554 |
"""
|
| 555 |
expanded_attempt = call_gemini_api(expansion_prompt)
|
| 556 |
final_post = post_process_blog(expanded_attempt, style)
|
|
@@ -677,10 +672,10 @@ def save_content_to_pdf(blog_post, user_topic):
|
|
| 677 |
# Gradio 인터페이스 구성 (기존 함수 이름, 인터페이스 유지)
|
| 678 |
# -------------------------------
|
| 679 |
with gr.Blocks() as iface:
|
| 680 |
-
gr.Markdown("#
|
| 681 |
-
gr.Markdown("
|
| 682 |
|
| 683 |
-
query_input = gr.Textbox(lines=1, placeholder="
|
| 684 |
style_input = gr.Radio(["친근한", "일반적인", "전문적인"], label="포스팅 스타일", value="친근한")
|
| 685 |
|
| 686 |
generate_button = gr.Button("블로그 글 생성")
|
|
@@ -707,4 +702,4 @@ with gr.Blocks() as iface:
|
|
| 707 |
)
|
| 708 |
|
| 709 |
if __name__ == "__main__":
|
| 710 |
-
iface.launch()
|
|
|
|
| 20 |
# -------------------------------
|
| 21 |
# 상수 정의 (향후 조정 및 유지보수 용이하도록)
|
| 22 |
# -------------------------------
|
| 23 |
+
TARGET_CHAR_LENGTH = 4000 # 최종 글자수 목표
|
| 24 |
+
MIN_SECTION_LENGTH = 600 # 각 소제목 아래 최소 글자수
|
| 25 |
MAX_TOKENS = 15000 # Gemini API 최대 토큰 수
|
| 26 |
TEMPERATURE = 0.85 # Gemini API 온도 값
|
| 27 |
TOP_P = 0.9 # Gemini API top_p 값
|
|
|
|
| 86 |
def get_style_prompt(style="친근한"):
|
| 87 |
prompts = {
|
| 88 |
"친근한": """
|
| 89 |
+
[친근한 여행 블로그 스타일 가이드]
|
| 90 |
1. 톤과 어조
|
| 91 |
- 대화하듯 편안하고 친근한 말투 사용
|
| 92 |
+
- 여행에 대한 설렘과 기대감을 담은 표현 사용
|
| 93 |
2. 문장 및 어투
|
| 94 |
- 반드시 '해요체'로 작성, 절대 '습니다'체를 사용하지 말 것
|
| 95 |
- '~요'로 끝나도록 작성, '~다'로 끝나지 않게 하라
|
| 96 |
- 구어체 표현 사용 (예: "~했어요", "~인 것 같아요")
|
| 97 |
- 이모티콘은 사용하지 마세요
|
| 98 |
3. 용어 및 설명 방식
|
| 99 |
+
- 전문 용어 대신 쉬운 단어로 풀어서 설명
|
| 100 |
- 비유나 은유를 활용하여 복잡한 개념 설명
|
| 101 |
+
- 수사의문문 활용하여 독자와 소통하는 느낌 주기 (예: "여러분도 이런 경험 있으시죠?")
|
| 102 |
+
- 오감을 활용한 생생한 묘사 (예: "바다에서 느껴지는 짭조름한 향기", "귓가를 간지럽히는 파도 소리")
|
| 103 |
+
4. 여행 정보 전달 방식
|
| 104 |
+
- 개인 경험에 녹여 자연스럽게 정보 전달 (예: "주차장이 생각보다 좁아서 오전에 도착하는 게 좋더라구요")
|
| 105 |
+
- 구체적인 에피소드를 통한 생생한 정보 전달 (예: "점심 피크타임에 도착했더니 1시간 넘게 기다렸어요")
|
| 106 |
+
- 솔직한 장단점 평가 (예: "경치는 정말 예뻤지만, 화장실이 좀 아쉬웠어요")
|
| 107 |
5. 독자와의 상호작용
|
| 108 |
+
- 독자의 의견을 물어보는 질문 포함 (예: "여러분은 어떤 코스를 선호하시나요?")
|
| 109 |
+
- 여행 팁이나 조언 제공 (예: "아침 일찍 방문하시면 한적하게 즐길 수 있어요")
|
| 110 |
+
- 댓글 달기를 독려하는 문구 사용
|
| 111 |
+
주의사항: 너무 가벼운 톤은 지양하고, 여행지의 특성과 매력을 해치지 않는 선에서 친근함 유지
|
| 112 |
""",
|
| 113 |
"일반적인": """
|
| 114 |
+
#일반적인 여행 블로그 포스팅 스타일 가이드
|
| 115 |
1. 톤과 어조
|
| 116 |
- 중립적이고 객관적인 톤 유지
|
| 117 |
- 적절한 존댓말 사용 (예: "~합니다", "~입니다")
|
| 118 |
+
- 정보 전달과 개인 경험의 균형 유지
|
| 119 |
2. 내용 구조 및 전개
|
| 120 |
+
- 명확한 여행지 소개로 시작
|
| 121 |
+
- 논리적인 순서로 정보 전개 (위치 → 교통 → 주요 볼거리 → 음식 → 숙소 등)
|
| 122 |
+
- 주요 포인트를 강조하는 소제목 활용
|
| 123 |
- 적절한 길이의 단락으로 구성
|
| 124 |
3. 용어 및 설명 방식
|
| 125 |
- 일반적으로 이해하기 쉬운 용어 선택
|
| 126 |
+
- 필요시 여행 관련 용어에 간단한 설명 추가
|
| 127 |
- 객관적인 정보 제공에 중점
|
| 128 |
+
- 개인 경험을 바탕으로 한 실용적인 조언 포함
|
| 129 |
+
4. 여행 정보 전달 방식
|
| 130 |
+
- 위치, 교통편, 운영시간, 입장료 등 핵심 정보를 명확하게 제공
|
| 131 |
+
- 계절별, 시간대별 특징 소개
|
| 132 |
+
- 주변 편의시설이나 부대시설 안내
|
| 133 |
+
- 다양한 여행자 유형(가족, 연인, 친구, 혼자)을 고려한 정보 제공
|
| 134 |
5. 독자 상호작용
|
| 135 |
- 적절히 독자의 생각을 묻는 질문 포함
|
| 136 |
- 추가 정보를 찾을 수 있는 키워드 제시
|
| 137 |
+
- 여행 계획에 도움이 되는 실용적인 팁 제공
|
| 138 |
6. 마무리
|
| 139 |
- 주요 내용 간단히 요약
|
| 140 |
+
- 해당 여행지의 특별한 매력 재강조
|
| 141 |
+
- 방문 시 참고할 만한 추가 조언 제공
|
| 142 |
+
주의사항: 너무 딱딱하거나 지루하지 않도록 개인 경험과 객관적 정보 사이의 균형 유지
|
| 143 |
""",
|
| 144 |
"전문적인": """
|
| 145 |
+
#전문적인 여행 블로그 포스팅 스타일 가이드
|
| 146 |
1. 톤과 구조
|
| 147 |
- 공식적이고 전문적인 톤 사용
|
| 148 |
- 객관적이고 분석적인 접근 유지
|
| 149 |
+
- 명확한 서론(여행지 개요), 본론(상세 분석), 결론(종합 평가) 구조
|
| 150 |
- 체계적인 정보 전개
|
| 151 |
- 세부 섹션을 위한 명확한 소제목 사용
|
| 152 |
2. 내용 구성 및 전개
|
| 153 |
+
- 여행지의 역사, 문화적 배경, 지리적 특성 등 심층적 정보 포함
|
| 154 |
- 논리적 연결을 위한 전환어 활용
|
| 155 |
+
- 여행 관련 전문 용어 적절히 활용 (필요시 간략한 설명 제공)
|
| 156 |
- 심층적인 분석과 비판적 평가 제공
|
| 157 |
+
- 다양한 관점 제시 및 비교 (예: 성수기 vs 비수기, 평일 vs 주말)
|
| 158 |
3. 데이터 및 근거 활용
|
| 159 |
+
- 방문객 통계, 여행 트렌드, 역사적 중요성 등 객관적 데이터 활용
|
| 160 |
+
- 여행지 평가를 위한 체계적인 기준 제시 (접근성, 시설, 가성비, 볼거리 등)
|
| 161 |
+
- 수치 데이터는 명확히 설명 (예: 이동 거리, 소요 시간, 비용 등)
|
| 162 |
+
4. 전문적 여행 정보 제공
|
| 163 |
+
- 최적의 방문 시기, 계절별 특징 상세 분석
|
| 164 |
+
- 교통편 옵션 비교 분석 (소요 시간, 비용, 편의성 등)
|
| 165 |
+
- 숙박 옵션 상세 분석 (위치, 가격대, 시설, 서비스 등)
|
| 166 |
+
- 여행 계획 수립을 위한 체계적인 정보 제공
|
|
|
|
| 167 |
5. 마무리
|
| 168 |
- 핵심 정보 요약 및 종합 평가
|
| 169 |
+
- 여행지의 차별화된 가치 분석
|
| 170 |
+
- 여행자 유형별 추천 제안
|
| 171 |
+
주의사항: 전문성을 유지하되, 완전히 이해하기 어려운 수준은 지양하고 실용적인 정보 중심으로 구성
|
| 172 |
"""
|
| 173 |
}
|
| 174 |
return prompts.get(style, prompts["친근한"])
|
| 175 |
|
| 176 |
+
def get_travel_blog_prompt():
|
| 177 |
prompts = [
|
| 178 |
"""
|
| 179 |
+
[중요: 여행 블로그 글 작성 필수 규칙]
|
| 180 |
이 규칙을 반드시 따르세요. 어떤 상황에서도 예외는 없습니다:
|
| 181 |
1. 마크다운 문법(**, *, #, -, 1., 2., 3.)을 절대 사용하지 마세요.
|
| 182 |
2. 모든 소제목은 번호 없이 일반 문장으로 작성하세요.
|
| 183 |
3. 모든 목록은 불릿이나 번호 없이 자연스러운 문장으로 서술하세요.
|
| 184 |
4. "참고글", "참고글에 따르면" 등의 표현을 절대 사용하지 마세요.
|
| 185 |
+
5. 블로그 작성자가 직접 경험하고 느낀 내용만 작성하세요.
|
| 186 |
+
6. 글의 주제는 반드시 주어진 참고글에서 1개 여행지를 선정하여 작성하세요.
|
| 187 |
+
예시:
|
| 188 |
+
제주도 서쪽 해안 드라이브 코스, 협재해변에서 차귀도까지
|
| 189 |
+
오랜만에 휴가를 내서 제주��로 여행을 다녀왔어요. 이번에는 특별히 서쪽 해안가를 따라 드라이브 코스를 계획했답니다. 협재해변부터 시작해서 한림공원, 금능해수욕장을 거쳐 차귀도까지 이어지는 해안 길은 정말 환상적이었어요. 푸른 바다와 검은 화산암이 만들어내는 풍경이 어찌나 아름답던지, 지금도 눈을 감으면 그 광경이 떠오르네요.
|
| 190 |
+
협재해변, 왜 이토록 사랑받는 곳일까?
|
| 191 |
+
제주도 서쪽에 위치한 협재해변은 에메랄드빛 바다로 유명한 곳이더라고요. 모래가 고와서 맨발로 걷기에도 정말 좋았어요. 비양도가 바로 앞에 보이는 풍경은 그야말로 그림 같았답니다.
|
|
|
|
|
|
|
|
|
|
| 192 |
"""
|
| 193 |
]
|
| 194 |
return random.choice(prompts)
|
|
|
|
| 197 |
unwanted_phrases = [
|
| 198 |
'여러분', '최근', '마지막으로', '결론적으로', '결국',
|
| 199 |
'종합적으로', '따라서', '마무리', '끝으로', '요약',
|
| 200 |
+
'한 줄 요약', '정리하자면', '총정리', '여행을 마치며',
|
| 201 |
+
'이상으로', '추천드립니다', '방문해보세요', '도움이 되셨길',
|
| 202 |
+
'즐거운 여행 되세요', '다음 여행기에서', '도움이 되었길',
|
| 203 |
+
'좋은 하루 되세요', '즐거운 여행을 기원합니다'
|
| 204 |
]
|
| 205 |
words = re.findall(r'\S+|\n', text)
|
| 206 |
result_words = [word for word in words if not any(phrase in word for phrase in unwanted_phrases)]
|
|
|
|
| 224 |
blog_content = re.sub(r'됩니다', '돼요', blog_content)
|
| 225 |
blog_content = re.sub(r'입니다', '이에요', blog_content)
|
| 226 |
|
|
|
|
| 227 |
exaggerated_expressions = [
|
| 228 |
+
(r'미식의 향연', r'맛있는 음식들'),
|
| 229 |
+
(r'입맛을 사로잡는', r'맛있는'),
|
| 230 |
+
(r'황홀한 맛', r'맛있는'),
|
| 231 |
+
(r'비할 데 없는 맛', r'특별한 맛'),
|
| 232 |
+
(r'천상의 맛', r'맛있는'),
|
| 233 |
+
(r'혀가 기억하는', r'기억에 남는'),
|
| 234 |
+
(r'절대 잊을 수 없는 맛', r'기억에 남는 맛'),
|
| 235 |
+
(r'미식가의 천국', r'맛집이 많은 곳'),
|
| 236 |
+
(r'기가 막힌 맛', r'맛있는'),
|
| 237 |
+
(r'장관이었어요', r'예뻤어요'),
|
| 238 |
+
(r'숨이 멎을 듯한', r'예쁜'),
|
| 239 |
+
(r'압도적인 경치', r'인상적인 경치'),
|
| 240 |
+
(r'절경', r'예쁜 풍경'),
|
| 241 |
+
(r'눈부신', r'밝은'),
|
| 242 |
+
(r'환상적인 전망', r'좋은 전망'),
|
| 243 |
+
(r'천혜의 자연', r'아름다운 자연'),
|
| 244 |
+
(r'그림 같은', r'예쁜'),
|
| 245 |
+
(r'파노라마 같은', r'넓게 보이는'),
|
| 246 |
+
(r'황홀경', r'좋은 경험'),
|
| 247 |
+
(r'가슴이 벅차오르는', r'기분 좋은'),
|
| 248 |
+
(r'꿈같은 시간', r'좋은 시간'),
|
| 249 |
+
(r'잊을 수 없는 경험', r'기억에 남는 경험'),
|
| 250 |
+
(r'감동적인', r'인상적인'),
|
| 251 |
+
(r'마음을 사로잡는', r'인상적인'),
|
| 252 |
+
(r'영혼을 치유하는', r'편안한'),
|
| 253 |
+
(r'최고의 순간', r'좋은 시간'),
|
| 254 |
(r'환상적인', r'좋은'),
|
| 255 |
+
(r'경이로운', r'놀라운'),
|
| 256 |
+
(r'매혹적인', r'예쁜'),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
(r'그 자체', r''),
|
| 258 |
+
(r'상상을 초월하는', r'예상 밖의'),
|
| 259 |
(r'이 .{1,10} 그 자체였어요', r'이 \1였어요'),
|
| 260 |
(r'가 .{1,10} 그 자체였어요', r'가 \1였어요'),
|
| 261 |
+
(r'향연', r'즐거움'),
|
| 262 |
+
(r'미식', r'맛있는 음식'),
|
| 263 |
+
(r'압도적인', r'인상적인'),
|
| 264 |
(r'천국', r'좋은 곳'),
|
| 265 |
(r'황홀했어요', r'좋았어요'),
|
| 266 |
(r'환상의', r'좋은')
|
|
|
|
| 269 |
for pattern, replacement in exaggerated_expressions:
|
| 270 |
blog_content = re.sub(pattern, replacement, blog_content, flags=re.IGNORECASE)
|
| 271 |
|
| 272 |
+
blog_content = re.sub(r'참고글에 따르면', r'제가 알게 된 바로는', blog_content)
|
| 273 |
blog_content = re.sub(r'참고글', r'관련 정보', blog_content)
|
| 274 |
|
| 275 |
# Gemini API를 활용한 추가 후처리 프롬프트 (텍스트 보완 목적)
|
| 276 |
prompt = f"""
|
| 277 |
+
다음 여행 블로그 글을 더 자연스러운 형태로 변경해주세요:
|
| 278 |
|
| 279 |
원본 글:
|
| 280 |
{blog_content}
|
|
|
|
| 283 |
1. 마크다운 형식 및 번호 목록, 불릿 표현 제거
|
| 284 |
2. 소제목은 5개 이하로, 각 소제목 아래 내용은 최소 {MIN_SECTION_LENGTH}자 이상으로 상세하게 서술
|
| 285 |
3. "참고글" 관련 표현 제거
|
| 286 |
+
4. 오감에 기반한 생생한 묘사 및 실용 정보 포함
|
| 287 |
5. 스타일: {style} (자연스러운 문체 사용)
|
| 288 |
"""
|
| 289 |
|
|
|
|
| 355 |
if title.endswith(f" {ending}"):
|
| 356 |
title = title[:-len(ending)-1]
|
| 357 |
if len(title) < 20 and query:
|
| 358 |
+
title = f"{query} 여행 후기"
|
| 359 |
return title
|
| 360 |
|
| 361 |
for i, line in enumerate(lines):
|
|
|
|
| 397 |
if in_paragraph:
|
| 398 |
formatted_lines.append("</p>")
|
| 399 |
if not title_found:
|
| 400 |
+
default_title = f"{query} 여행 후기" if query else "여행 후기"
|
| 401 |
if first_non_empty_line:
|
| 402 |
default_title = optimize_title(first_non_empty_line)
|
| 403 |
formatted_lines.insert(0, f'<h1 style="font-size: 1.8em; margin-bottom: 20px; font-weight: bold; color: #222;">{html.escape(default_title)}</h1>')
|
|
|
|
| 421 |
# 참고글 수집
|
| 422 |
ref1, ref2, ref3 = fetch_crawl_results(query)
|
| 423 |
style_prompt = get_style_prompt(style)
|
| 424 |
+
format_prompt = get_travel_blog_prompt()
|
| 425 |
|
| 426 |
# 스타일 세부 지시사항 (원본 내용의 특성을 유지)
|
| 427 |
style_specific_instructions = ""
|
|
|
|
| 432 |
- 격식체(예: "~합니다", "~입니다") 사용 금지
|
| 433 |
- 대화하듯 편안하게 작성
|
| 434 |
- 짧고 간결한 문장 사용
|
| 435 |
+
- 여행에 대한 설렘과 기대감 표현
|
| 436 |
+
- 오감을 활용한 생생한 묘사 포함
|
| 437 |
+
- 시간의 흐름에 따른 여행 경험 서술
|
| 438 |
"""
|
| 439 |
elif style == "일반적인":
|
| 440 |
style_specific_instructions = """
|
|
|
|
| 442 |
- '습니다체' 사용: "~했습니다", "~입니다", "~하였습니다"
|
| 443 |
- 격식적이지 않은 명확한 표현 사용
|
| 444 |
- 간결하고 명료한 문장 사용
|
| 445 |
+
- 객관적인 사실과 개인 경험 균형 있게 서술
|
|
|
|
| 446 |
"""
|
| 447 |
elif style == "전문적인":
|
| 448 |
style_specific_instructions = """
|
| 449 |
이 블로그는 반드시 전문적이고 분석적인 어투로 작성해야 합니다.
|
| 450 |
- '습니다체' 사용: "~했습니다", "~입니다", "~하였습니다"
|
| 451 |
+
- 역사, 문화, 지리적 정보 등 심층 정보 포함
|
| 452 |
- 객관적이고 논리적인 분석 중심 작성
|
| 453 |
- 구체적인 데이터 및 수치 포함하여 설명
|
|
|
|
| 454 |
"""
|
| 455 |
|
| 456 |
# Phase 1: 초기 생성
|
| 457 |
initial_prompt = f"""
|
| 458 |
+
주제: {query} 여행
|
| 459 |
참고글 1: {ref1}
|
| 460 |
참고글 2: {ref2}
|
| 461 |
참고글 3: {ref3}
|
|
|
|
| 466 |
스타일 세부 지시사항:
|
| 467 |
{style_specific_instructions}
|
| 468 |
특별 지시사항:
|
| 469 |
+
1. 반드�� 글의 처음에 매력적인 제목 포함 (여행지명과 핵심 특징).
|
| 470 |
2. 마크다운 문법(#, *, -, 1., 2., 등) 사용 금지.
|
| 471 |
3. 소제목은 번호 없이 작성하고, 5개 이하로 제한.
|
| 472 |
4. 모든 목록은 불릿이나 번호 없이 자연스럽게 서술.
|
| 473 |
5. "참고글" 관련 표현 사용 금지.
|
| 474 |
+
6. 블로그 작성자가 직접 경험하고 느낀 내용만 작성.
|
| 475 |
+
7. 구체적인 정보(위치, 교통, 시간, 가격, 주차, 추천코스, 주변 맛집 등) 자연스럽게 포함.
|
| 476 |
8. 글자수가 최소 {TARGET_CHAR_LENGTH}자 이상이어야 함.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 477 |
"""
|
|
|
|
| 478 |
first_attempt = call_gemini_api(initial_prompt)
|
| 479 |
first_attempt_cleaned = remove_unwanted_phrases(first_attempt)
|
| 480 |
first_attempt_length = len(first_attempt_cleaned)
|
|
|
|
| 498 |
이전에 작성된 글은 목표 글자수인 {TARGET_CHAR_LENGTH}자에 미치지 못합니다. 현재 글자수는 약 {first_attempt_length}자입니다.
|
| 499 |
또한, 각 소제목 아래 내용이 너무 짧고 부실합니다.
|
| 500 |
중요 요구사항:
|
| 501 |
+
1. 글의 처음에 매력적인 제목(여행지명({query})와 핵심 특징 포함) 추가.
|
| 502 |
2. 글자수를 최소 {TARGET_CHAR_LENGTH}자 이상으로 늘리고, 각 섹션을 상세히 서술.
|
| 503 |
3. 마크다운 형식(#, *, -, 1., 2., 등) 사용 금지.
|
| 504 |
4. 소제목은 번호 없이 작성.
|
| 505 |
5. 모든 목록은 불릿이나 번호 없이 서술.
|
| 506 |
6. "참고글" 관련 표현 사용 금지.
|
| 507 |
+
7. 블로그 작성자의 실제 경험만 서술.
|
| 508 |
8. 각 소제목 아래 내용을 최소 {MIN_SECTION_LENGTH}자 이상 서술.
|
| 509 |
9. 소제목 수는 5개 이하로 제한.
|
| 510 |
상세 보완:
|
| 511 |
+
- 여행지 외관 및 분위기, 오감 묘사 추가.
|
| 512 |
+
- 구체적인 에피소드, 상황, 대화 등 구체적 이야기 추가.
|
| 513 |
+
- 역사, 문화적 배경 등 흥미로운 정보 추가.
|
| 514 |
+
- 다양한 체험 활동, 맛집, 교통, 운영시간 등 실용 정보 추가.
|
|
|
|
| 515 |
"""
|
| 516 |
revised_attempt = call_gemini_api(revision_prompt)
|
| 517 |
revised_cleaned = remove_unwanted_phrases(revised_attempt)
|
|
|
|
| 524 |
# Phase 3: 확장 (Expansion) 시도 (글자수가 부족할 경우)
|
| 525 |
if actual_char_length < TARGET_CHAR_LENGTH * 0.8:
|
| 526 |
expansion_prompt = f"""
|
| 527 |
+
다음 여행 블로그 글의 내용을 크게 확장해주세요:
|
| 528 |
원본 글:
|
| 529 |
{final_post}
|
| 530 |
문제점:
|
|
|
|
| 535 |
{style_specific_instructions}
|
| 536 |
요구사항:
|
| 537 |
1. 각 소제목 아래의 내용을 최소 {MIN_SECTION_LENGTH}자 이상 대폭 확장.
|
| 538 |
+
2. 구체적인 에피소드, 상황, 대화, 감정 등 상세 추가.
|
| 539 |
3. 마크다운 형식(#, *, -, 1., 2., 등) 사용 금지.
|
| 540 |
4. {style} 스타일에 맞춰 일관되게 작성.
|
| 541 |
5. 소제목 구조는 유지하되, 각 섹션 내용을 3배 이상 풍부하게 확장.
|
| 542 |
+
6. 여행지의 모습, 분위기, 음식, 사람들, 날씨, 느낌 등을 오감으로 생생하게 묘사.
|
| 543 |
7. 전체 글자수를 최소 {TARGET_CHAR_LENGTH}자 이상 달성.
|
| 544 |
8. 소제목 수는 최대 5개로 제한.
|
| 545 |
+
9. 실용적 정보(교통, 비용, 운영시간, 숙박, 맛집 등) 추가.
|
| 546 |
+
10. 계절별, 시간대별 여행 팁 추가.
|
| 547 |
+
11. 주변 관광지나 추가 코스 정보도 포함.
|
| 548 |
+
12. 역사적, 문화적 배경을 흥미롭게 추가.
|
| 549 |
"""
|
| 550 |
expanded_attempt = call_gemini_api(expansion_prompt)
|
| 551 |
final_post = post_process_blog(expanded_attempt, style)
|
|
|
|
| 672 |
# Gradio 인터페이스 구성 (기존 함수 이름, 인터페이스 유지)
|
| 673 |
# -------------------------------
|
| 674 |
with gr.Blocks() as iface:
|
| 675 |
+
gr.Markdown("# 여행 후기 블로그")
|
| 676 |
+
gr.Markdown("여행지 키워드를 입력해 주세요.")
|
| 677 |
|
| 678 |
+
query_input = gr.Textbox(lines=1, placeholder="제주도 여행, 부산 해운대, 남산타워, 통영, 경주 불국사, 강원도 속초", label="키워드")
|
| 679 |
style_input = gr.Radio(["친근한", "일반적인", "전문적인"], label="포스팅 스타일", value="친근한")
|
| 680 |
|
| 681 |
generate_button = gr.Button("블로그 글 생성")
|
|
|
|
| 702 |
)
|
| 703 |
|
| 704 |
if __name__ == "__main__":
|
| 705 |
+
iface.launch()
|